diff --git a/src/FingerprintTest/MainActivity.cs b/src/FingerprintTest/MainActivity.cs
index bebb77ae..68404b22 100644
--- a/src/FingerprintTest/MainActivity.cs
+++ b/src/FingerprintTest/MainActivity.cs
@@ -35,6 +35,8 @@ namespace FingerprintTest
RequestPermissions(new[] { Manifest.Permission.UseFingerprint }, FINGERPRINT_PERMISSION_REQUEST_CODE);
}
+
+
string prefKey = "enc_pref_key";
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
diff --git a/src/FingerprintTest/Properties/AndroidManifest.xml b/src/FingerprintTest/Properties/AndroidManifest.xml
index ff9420a4..0c4eabdf 100644
--- a/src/FingerprintTest/Properties/AndroidManifest.xml
+++ b/src/FingerprintTest/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs
index 59b552e6..f25b0f12 100644
--- a/src/Kp2aBusinessLogic/database/Database.cs
+++ b/src/Kp2aBusinessLogic/database/Database.cs
@@ -19,12 +19,15 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
+using System.Security.Cryptography;
+using System.Text;
using Android.Content;
using Java.Lang;
using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using keepass2android.Io;
+using KeePassLib.Utility;
using Exception = System.Exception;
using String = System.String;
@@ -148,6 +151,29 @@ namespace keepass2android
set { _databaseFormat = value; }
}
+ public static string GetFingerprintPrefKey(IOConnectionInfo ioc)
+ {
+ SHA256Managed sha256 = new SHA256Managed();
+ string iocAsHexString = MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray())));
+
+ return "kp2a_ioc_" + iocAsHexString;
+ }
+
+ public static string GetFingerprintModePrefKey(IOConnectionInfo ioc)
+ {
+ return GetFingerprintPrefKey(ioc) + "_mode";
+ }
+
+ public string CurrentFingerprintPrefKey
+ {
+ get { return GetFingerprintPrefKey(Ioc); }
+ }
+
+ public string CurrentFingerprintModePrefKey
+ {
+ get { return GetFingerprintModePrefKey(Ioc); }
+ }
+
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
{
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs
index a1765ca6..e1f8081c 100644
--- a/src/keepass2android/PasswordActivity.cs
+++ b/src/keepass2android/PasswordActivity.cs
@@ -35,9 +35,12 @@ using Android.Preferences;
using Android.Text;
using Android.Content.PM;
using Android.Graphics;
+using Android.Hardware.Fingerprints;
+using Android.Provider;
using Android.Support.Design.Widget;
using Android.Support.V4.Widget;
using Android.Support.V7.App;
+using Java.Lang;
using keepass2android;
using KeePassLib.Keys;
using KeePassLib.Serialization;
@@ -55,6 +58,9 @@ using Process = Android.OS.Process;
using KeeChallenge;
using AlertDialog = Android.App.AlertDialog;
+using Enum = System.Enum;
+using Exception = System.Exception;
+using String = System.String;
using Toolbar = Android.Support.V7.Widget.Toolbar;
namespace keepass2android
@@ -64,7 +70,8 @@ namespace keepass2android
LaunchMode = LaunchMode.SingleInstance,
WindowSoftInputMode = SoftInput.AdjustResize,
Theme = "@style/MyTheme_Blue")] /*caution: also contained in AndroidManifest.xml*/
- public class PasswordActivity : LockingActivity {
+ public class PasswordActivity : LockingActivity, IFingerprintAuthCallback
+ {
enum KeyProviders
{
@@ -689,7 +696,9 @@ namespace keepass2android
private string mDrawerTitle;
private MeasuringRelativeLayout.MeasureArgs _measureArgs;
private ActivityDesign _activityDesign;
-
+ private FingerprintDecryption _fingerprintDec;
+ private bool _fingerprintPermissionGranted;
+
internal class MyActionBarDrawerToggle : ActionBarDrawerToggle
{
@@ -899,9 +908,71 @@ namespace keepass2android
this._measureArgs = args;
};
+ RequestPermissions(new[] { Android.Manifest.Permission.UseFingerprint }, FingerprintPermissionRequestCode);
+
+ }
+ const int FingerprintPermissionRequestCode = 0;
+
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ if (requestCode == FingerprintPermissionRequestCode && grantResults[0] == Permission.Granted)
+ {
+ var btn = FindViewById(Resource.Id.fingerprintbtn);
+ btn.Click += (sender, args) =>
+ {
+ AlertDialog.Builder b = new AlertDialog.Builder(this);
+ b.SetTitle(Resource.String.fingerprint_prefs);
+ b.SetMessage(btn.Tag.ToString());
+ b.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => ((Dialog)o).Dismiss());
+ b.Show();
+ };
+ _fingerprintPermissionGranted = true;
+ }
}
- private void InitializeNavDrawerButtons()
+ private void ClearFingerprintUnlockData()
+ {
+ ISharedPreferencesEditor edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
+ edit.PutString(Database.GetFingerprintPrefKey(_ioConnection), "");
+ edit.PutString(Database.GetFingerprintModePrefKey(_ioConnection), FingerprintUnlockMode.Disabled.ToString());
+ edit.Commit();
+ }
+
+
+ public void OnFingerprintError(string message)
+ {
+ var btn = FindViewById(Resource.Id.fingerprintbtn);
+
+ btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
+ btn.PostDelayed(() =>
+ {
+ btn.SetImageResource(Resource.Drawable.ic_fp_40px);
+ btn.Tag = GetString(Resource.String.fingerprint_unlock_hint);
+ }, 1300);
+ Toast.MakeText(this, message, ToastLength.Long).Show();
+ }
+
+ public void OnFingerprintAuthSucceeded()
+ {
+ var btn = FindViewById(Resource.Id.fingerprintbtn);
+
+ btn.SetImageResource(Resource.Drawable.ic_fingerprint_success);
+
+ var masterPassword = _fingerprintDec.DecryptStored(Database.GetFingerprintPrefKey(_ioConnection));
+ _password = FindViewById(Resource.Id.password_edit).Text = masterPassword;
+
+ btn.PostDelayed(() =>
+ {
+ //re-init fingerprint unlock in case something goes wrong with opening the database
+ InitFingerprintUnlock();
+ //fire
+ OnOk();
+ }, 1000);
+
+
+ }
+
+ private void InitializeNavDrawerButtons()
{
FindViewById(Resource.Id.btn_nav_change_db).Click += (sender, args) =>
{
@@ -1402,7 +1473,16 @@ namespace keepass2android
}
- private void SetPasswordTypeface(TextView textView)
+ protected override void OnPause()
+ {
+ base.OnPause();
+ if (_fingerprintDec != null)
+ {
+ _fingerprintDec.StopListening();
+ }
+ }
+
+ private void SetPasswordTypeface(TextView textView)
{
if (_passwordFont == null)
{
@@ -1682,8 +1762,65 @@ namespace keepass2android
}
}
}
+
+ if (_fingerprintPermissionGranted)
+ {
+ InitFingerprintUnlock();
+ }
+ else
+ {
+ FindViewById(Resource.Id.fingerprintbtn).Visibility = ViewStates.Gone;
+
+ }
+
}
-
+
+ private void InitFingerprintUnlock()
+ {
+ var btn = FindViewById(Resource.Id.fingerprintbtn);
+ try
+ {
+ FingerprintUnlockMode um;
+ Enum.TryParse(_prefs.GetString(Database.GetFingerprintModePrefKey(_ioConnection), ""), out um);
+ btn.Visibility = (um == FingerprintUnlockMode.FullUnlock) ? ViewStates.Visible : ViewStates.Gone;
+
+ if (um != FingerprintUnlockMode.FullUnlock)
+ {
+ return;
+ }
+
+ FingerprintModule fpModule = new FingerprintModule(this);
+ _fingerprintDec = new FingerprintDecryption(fpModule, Database.GetFingerprintPrefKey(_ioConnection), this,
+ Database.GetFingerprintPrefKey(_ioConnection));
+
+ btn.Tag = GetString(Resource.String.fingerprint_unlock_hint);
+
+ if (_fingerprintDec.InitCipher())
+ {
+ btn.SetImageResource(Resource.Drawable.ic_fp_40px);
+ _fingerprintDec.StartListening(new FingerprintAuthCallbackAdapter(this, this));
+ }
+ else
+ {
+ //key invalidated permanently
+ btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
+ btn.Tag = GetString(Resource.String.fingerprint_unlock_failed);
+ _fingerprintDec = null;
+
+ ClearFingerprintUnlockData();
+ }
+ }
+ catch (Exception e)
+ {
+ btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
+ btn.Tag = "Error initializing Fingerprint Unlock: " + e;
+
+ _fingerprintDec = null;
+ }
+
+
+ }
+
private void InitializeOptionCheckboxes() {
CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true);
@@ -1788,15 +1925,16 @@ namespace keepass2android
public override void Run()
{
- if ( Success )
+ if (Success)
{
-
+
_act.ClearEnteredPassword();
_act.BroadcastOpenDatabase();
GC.Collect(); // Ensure temporary memory used while loading is collected
- }
+ }
+
if ((Message != null) && (Message.Length > 150)) //show long messages as dialog
{
new AlertDialog.Builder(_act).SetMessage(Message)
@@ -1886,5 +2024,11 @@ namespace keepass2android
}
}
+
+ public interface IFingerprintAuthCallback
+ {
+ void OnFingerprintAuthSucceeded();
+ void OnFingerprintError(string toString);
+ }
}
diff --git a/src/keepass2android/Properties/AndroidManifest_debug.xml b/src/keepass2android/Properties/AndroidManifest_debug.xml
index 2e67d0d5..eb16b722 100644
--- a/src/keepass2android/Properties/AndroidManifest_debug.xml
+++ b/src/keepass2android/Properties/AndroidManifest_debug.xml
@@ -98,4 +98,5 @@
+
\ No newline at end of file
diff --git a/src/keepass2android/Properties/AndroidManifest_net.xml b/src/keepass2android/Properties/AndroidManifest_net.xml
index 1b9109b9..e3bafa41 100644
--- a/src/keepass2android/Properties/AndroidManifest_net.xml
+++ b/src/keepass2android/Properties/AndroidManifest_net.xml
@@ -101,4 +101,5 @@
+
\ No newline at end of file
diff --git a/src/keepass2android/Properties/AndroidManifest_nonet.xml b/src/keepass2android/Properties/AndroidManifest_nonet.xml
index a263886d..1508c61a 100644
--- a/src/keepass2android/Properties/AndroidManifest_nonet.xml
+++ b/src/keepass2android/Properties/AndroidManifest_nonet.xml
@@ -79,4 +79,5 @@
+
\ No newline at end of file
diff --git a/src/keepass2android/Resources/layout/password.xml b/src/keepass2android/Resources/layout/password.xml
index 13946b1f..625d16a4 100644
--- a/src/keepass2android/Resources/layout/password.xml
+++ b/src/keepass2android/Resources/layout/password.xml
@@ -160,13 +160,24 @@
android:paddingBottom="12dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
- android:orientation="vertical">
+ android:orientation="horizontal">
+
+
diff --git a/src/keepass2android/Resources/values/colors.xml b/src/keepass2android/Resources/values/colors.xml
index 6eb70dc7..1cec8d52 100644
--- a/src/keepass2android/Resources/values/colors.xml
+++ b/src/keepass2android/Resources/values/colors.xml
@@ -38,5 +38,9 @@
#31b6e7
#4f7a8a
+
+ #f4511e
+ #42000000
+ #009688
diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml
index 572cd415..193ac52d 100644
--- a/src/keepass2android/Resources/values/strings.xml
+++ b/src/keepass2android/Resources/values/strings.xml
@@ -49,6 +49,7 @@
File handling
Keyboard
Export database...
+ Fingerprint unlock
Import database to internal folder
Import key file to internal folder
Keyboard switching
@@ -79,6 +80,26 @@
Keepass2Android comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under the conditions of the GPL version 2 or later.
\u2026
Copy to clipboard
+ Touch sensor
+ Confirm fingerprint to continue
+ Cannot setup Fingerprint Unlock:
+ Fingerprint not recognized. Try again
+ Fingerprint recognized
+ Fingerprint Unlock requires Android 6.0 or later.
+ No fingerprint hardware detected.
+ You have no enrolled fingerprints on this device. Please go to system settings first.
+ Disable Fingerprint Unlock
+ Enable full Fingerprint Unlock
+ Enable Fingerprint Unlock for QuickUnlock
+ Touch sensor to unlock database
+ Fingerprint Unlock failed. Decryption key was invalidated by Android OS. This usually happens if a new fingerprint was enrolled or security settings were changed. Please unlock with your password and then re-enabled Fingerprint Unlock in the database settings.
+
+
+ This will store your master password on this device,
+ encrypted with the Android Keystore and protected
+ using fingerprint authentication. Allows to unlock your database only with your fingerprint.
+
+ Allows to use fingerprint authentication instead of the QuickUnlock code. Does not require to store any information related to your master password.
Enter database filename
Accessed
diff --git a/src/keepass2android/Resources/xml/preferences.xml b/src/keepass2android/Resources/xml/preferences.xml
index 58294482..08a8dd5a 100644
--- a/src/keepass2android/Resources/xml/preferences.xml
+++ b/src/keepass2android/Resources/xml/preferences.xml
@@ -51,7 +51,14 @@
-
+
+
+
+
+
+
keepass2android
OnLoad
Properties\AndroidManifest.xml
- v5.0
+ v6.0
1G
+ False
True
@@ -134,6 +135,8 @@
+
+
@@ -142,6 +145,7 @@
+
@@ -269,6 +273,9 @@
AndroidResource
+
+ Designer
+
False
@@ -722,6 +729,7 @@
+
@@ -839,12 +847,6 @@
Designer
-
-
- 22.2.0.0
- False
-
-
@@ -1641,5 +1643,23 @@
+
+
+ False
+ 23.0.1.3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file