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