493 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			493 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using Android.Content;
 | |
| using Javax.Crypto;
 | |
| using Java.Security;
 | |
| using Java.Lang;
 | |
| using Android.Views.InputMethods;
 | |
| using Android.App;
 | |
| using Android.OS;
 | |
| using Android.Security.Keystore;
 | |
| using Android.Preferences;
 | |
| using Android.Util;
 | |
| using Android.Widget;
 | |
| using AndroidX.Biometric;
 | |
| using AndroidX.Fragment.App;
 | |
| using Java.IO;
 | |
| using Java.Security.Cert;
 | |
| using Java.Util.Concurrent;
 | |
| using Javax.Crypto.Spec;
 | |
| using keepass2android;
 | |
| using Exception = System.Exception;
 | |
| using File = System.IO.File;
 | |
| 
 | |
| namespace keepass2android
 | |
| {
 | |
|     public interface IBiometricAuthCallback
 | |
|     {
 | |
|         void OnBiometricAuthSucceeded();
 | |
|         void OnBiometricError(string toString);
 | |
|         void OnBiometricAttemptFailed(string message);
 | |
|     }
 | |
| 
 | |
|     public class BiometricModule
 | |
|     {
 | |
|         public AndroidX.Fragment.App.FragmentActivity Activity { get; set; }
 | |
| 
 | |
|         public BiometricModule(AndroidX.Fragment.App.FragmentActivity activity)
 | |
|         {
 | |
|             Activity = activity;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public KeyguardManager KeyguardManager
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return (KeyguardManager)Activity.GetSystemService("keyguard");
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public KeyStore Keystore
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     return KeyStore.GetInstance("AndroidKeyStore");
 | |
|                 }
 | |
|                 catch (KeyStoreException e)
 | |
|                 {
 | |
|                     throw new RuntimeException("Failed to get an instance of KeyStore", e);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public KeyGenerator KeyGenerator
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     return KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, "AndroidKeyStore");
 | |
|                 }
 | |
|                 catch (NoSuchAlgorithmException e)
 | |
|                 {
 | |
|                     throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
 | |
|                 }
 | |
|                 catch (NoSuchProviderException e)
 | |
|                 {
 | |
|                     throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public Cipher Cipher
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     return Cipher.GetInstance(KeyProperties.KeyAlgorithmAes + "/"
 | |
|                                               + KeyProperties.BlockModeCbc + "/"
 | |
|                                               + KeyProperties.EncryptionPaddingPkcs7);
 | |
|                 }
 | |
|                 catch (NoSuchAlgorithmException e)
 | |
|                 {
 | |
|                     throw new RuntimeException("Failed to get an instance of Cipher", e);
 | |
|                 }
 | |
|                 catch (NoSuchPaddingException e)
 | |
|                 {
 | |
|                     throw new RuntimeException("Failed to get an instance of Cipher", e);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public ISharedPreferences SharedPreferences
 | |
|         {
 | |
|             get { return PreferenceManager.GetDefaultSharedPreferences(Activity); }
 | |
|         }
 | |
| 
 | |
|         public bool IsAvailable
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return BiometricManager.From(Activity).CanAuthenticate() ==
 | |
|                        BiometricManager.BiometricSuccess;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public bool IsHardwareAvailable
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 var result = BiometricManager.From(Activity).CanAuthenticate();
 | |
|                 Kp2aLog.Log("BiometricHardware available = " + result);
 | |
|                 return result == BiometricManager.BiometricSuccess 
 | |
|                        || result == BiometricManager.BiometricErrorNoneEnrolled;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public abstract class BiometricCrypt : IBiometricIdentifier
 | |
|     {
 | |
|         protected const string FailedToInitCipher = "Failed to init Cipher";
 | |
| 
 | |
|         protected readonly string _keyId;
 | |
| 
 | |
|         protected Cipher _cipher;
 | |
|         private CancellationSignal _cancellationSignal;
 | |
|         protected BiometricPrompt.CryptoObject _cryptoObject;
 | |
| 
 | |
|         protected KeyStore _keystore;
 | |
| 
 | |
|         private BiometricPrompt _biometricPrompt;
 | |
|         private FragmentActivity _activity;
 | |
|         private BiometricAuthCallbackAdapter _biometricAuthCallbackAdapter;
 | |
| 
 | |
|         public BiometricCrypt(BiometricModule biometric, string keyId)
 | |
|         {
 | |
|             Kp2aLog.Log("FP: Create " + this.GetType().Name);
 | |
|             _keyId = keyId;
 | |
|             _cipher = biometric.Cipher;
 | |
|             _keystore = biometric.Keystore;
 | |
|             _activity = biometric.Activity;
 | |
| 
 | |
| 
 | |
| 
 | |
|         }
 | |
| 
 | |
|         public abstract bool Init();
 | |
| 
 | |
| 
 | |
|         protected static string GetAlias(string keyId)
 | |
|         {
 | |
|             return "keepass2android." + keyId;
 | |
|         }
 | |
|         protected static string GetIvPrefKey(string prefKey)
 | |
|         {
 | |
|             return prefKey + "_iv";
 | |
|         }
 | |
| 
 | |
|         public void StartListening(IBiometricAuthCallback callback)
 | |
|         {
 | |
|             _biometricAuthCallbackAdapter = new BiometricAuthCallbackAdapter(callback, _activity);
 | |
|             StartListening(_biometricAuthCallbackAdapter);
 | |
|         }
 | |
| 
 | |
|         public void StopListening()
 | |
|         {
 | |
|             Kp2aLog.Log("Fingerprint: StopListening " + (_biometricPrompt != null ? " having prompt " : " without prompt"));
 | |
|             _biometricAuthCallbackAdapter?.IgnoreNextError();
 | |
|             _biometricPrompt?.CancelAuthentication();
 | |
|         }
 | |
| 
 | |
|         public bool HasUserInterface
 | |
|         {
 | |
|             get { return true; }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         public void StartListening(BiometricPrompt.AuthenticationCallback callback)
 | |
|         {
 | |
| 
 | |
|             Kp2aLog.Log("Fingerprint: StartListening ");
 | |
| 
 | |
|             var executor = Executors.NewSingleThreadExecutor();
 | |
|             _biometricPrompt = new BiometricPrompt(_activity, executor, callback);
 | |
| 
 | |
|             BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
 | |
|                 .SetTitle(_activity.GetString(AppNames.AppNameResource))
 | |
|                 .SetSubtitle(_activity.GetString(Resource.String.unlock_database_title))
 | |
|                 .SetNegativeButtonText(_activity.GetString(Android.Resource.String.Cancel))
 | |
|                 .SetConfirmationRequired(false)
 | |
|                 .Build();
 | |
| 
 | |
| 
 | |
|             _biometricPrompt.Authenticate(promptInfo, _cryptoObject);
 | |
| 
 | |
|         }
 | |
| 
 | |
|         public string Encrypt(string textToEncrypt)
 | |
|         {
 | |
|             Kp2aLog.Log("FP: Encrypting");
 | |
|             return Base64.EncodeToString(_cipher.DoFinal(System.Text.Encoding.UTF8.GetBytes(textToEncrypt)), 0);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public void StoreEncrypted(string textToEncrypt, string prefKey, Context context)
 | |
|         {
 | |
|             var edit = PreferenceManager.GetDefaultSharedPreferences(context).Edit();
 | |
|             StoreEncrypted(textToEncrypt, prefKey, edit);
 | |
|             edit.Commit();
 | |
|         }
 | |
| 
 | |
|         public void StoreEncrypted(string textToEncrypt, string prefKey, ISharedPreferencesEditor edit)
 | |
|         {
 | |
|             edit.PutString(prefKey, Encrypt(textToEncrypt));
 | |
|             edit.PutString(GetIvPrefKey(prefKey), Base64.EncodeToString(CipherIv, 0));
 | |
| 
 | |
|         }
 | |
| 
 | |
| 
 | |
|         private byte[] CipherIv
 | |
|         {
 | |
|             get { return _cipher.GetIV(); }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public interface IBiometricIdentifier
 | |
|     {
 | |
|         bool Init();
 | |
|         void StartListening(IBiometricAuthCallback callback);
 | |
| 
 | |
|         void StopListening();
 | |
|         bool HasUserInterface { get; }
 | |
|     }
 | |
| 
 | |
|     public class BiometricDecryption : BiometricCrypt
 | |
|     {
 | |
|         private readonly Context _context;
 | |
|         private readonly byte[] _iv;
 | |
| 
 | |
| 
 | |
|         public BiometricDecryption(BiometricModule biometric, string keyId, byte[] iv) : base(biometric, keyId)
 | |
|         {
 | |
|             _iv = iv;
 | |
|         }
 | |
| 
 | |
|         public BiometricDecryption(BiometricModule biometric, string keyId, Context context, string prefKey)
 | |
|             : base(biometric, keyId)
 | |
|         {
 | |
|             _context = context;
 | |
|             _iv = Base64.Decode(PreferenceManager.GetDefaultSharedPreferences(context).GetString(GetIvPrefKey(prefKey), null), 0);
 | |
|         }
 | |
| 
 | |
|         public static bool IsSetUp(Context context, string prefKey)
 | |
|         {
 | |
|             return PreferenceManager.GetDefaultSharedPreferences(context).GetString(GetIvPrefKey(prefKey), null) != null;
 | |
|         }
 | |
| 
 | |
|         public override bool Init()
 | |
|         {
 | |
|             Kp2aLog.Log("FP: Init for Dec");
 | |
|             try
 | |
|             {
 | |
|                 _keystore.Load(null);
 | |
|                 var aliases = _keystore.Aliases();
 | |
|                 if (aliases == null)
 | |
|                 {
 | |
|                     Kp2aLog.Log("KS: no aliases");
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     while (aliases.HasMoreElements)
 | |
|                     {
 | |
|                         var o = aliases.NextElement();
 | |
|                         Kp2aLog.Log("alias: " + o?.ToString());
 | |
|                     }
 | |
|                     Kp2aLog.Log("KS: end aliases");
 | |
| 
 | |
|                 }
 | |
|                 var key = _keystore.GetKey(GetAlias(_keyId), null);
 | |
|                 if (key == null)
 | |
|                     throw new Exception("Failed to init cipher for fingerprint Init: key is null");
 | |
|                 var ivParams = new IvParameterSpec(_iv);
 | |
|                 _cipher.Init(CipherMode.DecryptMode, key, ivParams);
 | |
| 
 | |
|                 _cryptoObject = new BiometricPrompt.CryptoObject(_cipher);
 | |
|                 return true;
 | |
|             }
 | |
|             catch (KeyPermanentlyInvalidatedException e)
 | |
|             {
 | |
|                 Kp2aLog.Log("FP: KeyPermanentlyInvalidatedException." + e.ToString());
 | |
|                 return false;
 | |
|             }
 | |
|             catch (KeyStoreException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher + " (keystore)", e);
 | |
|             }
 | |
|             catch (CertificateException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher + " (CertificateException)", e);
 | |
|             }
 | |
|             catch (UnrecoverableKeyException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher + " (UnrecoverableKeyException)", e);
 | |
|             }
 | |
|             catch (Java.IO.IOException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher + " (IOException)", e);
 | |
|             }
 | |
|             catch (NoSuchAlgorithmException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher + " (NoSuchAlgorithmException)", e);
 | |
|             }
 | |
|             catch (InvalidKeyException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher + " (InvalidKeyException)" + e.ToString(), e);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public string Decrypt(string encryted)
 | |
|         {
 | |
|             Kp2aLog.Log("FP: Decrypting ");
 | |
|             byte[] encryptedBytes = Base64.Decode(encryted, 0);
 | |
|             return System.Text.Encoding.UTF8.GetString(_cipher.DoFinal(encryptedBytes));
 | |
|         }
 | |
| 
 | |
|         public string DecryptStored(string prefKey)
 | |
|         {
 | |
|             string enc = PreferenceManager.GetDefaultSharedPreferences(_context).GetString(prefKey, null);
 | |
|             return Decrypt(enc);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public class BiometricEncryption : BiometricCrypt
 | |
|     {
 | |
| 
 | |
|         private KeyGenerator _keyGen;
 | |
| 
 | |
| 
 | |
|         public BiometricEncryption(BiometricModule biometric, string keyId) :
 | |
|             base(biometric, keyId)
 | |
|         {
 | |
|             _keyGen = biometric.KeyGenerator;
 | |
|             Kp2aLog.Log("FP: CreateKey ");
 | |
|             CreateKey();
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates a symmetric key in the Android Key Store which can only be used after the user 
 | |
|         /// has authenticated with biometry.
 | |
|         /// </summary>
 | |
|         private void CreateKey()
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 _keystore.Load(null);
 | |
|                 KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(GetAlias(_keyId),
 | |
|                         KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
 | |
|                     .SetBlockModes(KeyProperties.BlockModeCbc)
 | |
|                     // Require the user to authenticate with biometry to authorize every use
 | |
|                     // of the key
 | |
|                     .SetEncryptionPaddings(KeyProperties.EncryptionPaddingPkcs7)
 | |
|                     .SetUserAuthenticationRequired(true);
 | |
|                 
 | |
|                 if ((int)Build.VERSION.SdkInt >= 24)
 | |
|                     builder.SetInvalidatedByBiometricEnrollment(true);
 | |
| 
 | |
|                 _keyGen.Init(
 | |
|                     builder
 | |
|                     .Build());
 | |
|                 _keyGen.GenerateKey();
 | |
|             }
 | |
|             catch (NoSuchAlgorithmException e)
 | |
|             {
 | |
|                 throw new RuntimeException(e);
 | |
|             }
 | |
|             catch (InvalidAlgorithmParameterException e)
 | |
|             {
 | |
|                 throw new RuntimeException(e);
 | |
|             }
 | |
|             catch (CertificateException e)
 | |
|             {
 | |
|                 throw new RuntimeException(e);
 | |
|             }
 | |
|             catch (Java.IO.IOException e)
 | |
|             {
 | |
|                 throw new RuntimeException(e);
 | |
|             }
 | |
|             catch (System.Exception e)
 | |
|             {
 | |
|                 Kp2aLog.LogUnexpectedError(e);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public override bool Init()
 | |
|         {
 | |
|             Kp2aLog.Log("FP: Init for Enc ");
 | |
|             try
 | |
|             {
 | |
|                 _keystore.Load(null);
 | |
|                 var key = _keystore.GetKey(GetAlias(_keyId), null);
 | |
|                 _cipher.Init(CipherMode.EncryptMode, key);
 | |
| 
 | |
|                 _cryptoObject = new BiometricPrompt.CryptoObject(_cipher);
 | |
|                 return true;
 | |
|             }
 | |
|             catch (KeyPermanentlyInvalidatedException)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|             catch (KeyStoreException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher, e);
 | |
|             }
 | |
|             catch (CertificateException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher, e);
 | |
|             }
 | |
|             catch (UnrecoverableKeyException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher, e);
 | |
|             }
 | |
|             catch (Java.IO.IOException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher, e);
 | |
|             }
 | |
|             catch (NoSuchAlgorithmException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher, e);
 | |
|             }
 | |
|             catch (InvalidKeyException e)
 | |
|             {
 | |
|                 throw new RuntimeException(FailedToInitCipher, e);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public class BiometricAuthCallbackAdapter : BiometricPrompt.AuthenticationCallback
 | |
|     {
 | |
|         private readonly IBiometricAuthCallback _callback;
 | |
|         private readonly Context _context;
 | |
|         private bool _ignoreNextError;
 | |
| 
 | |
|         public BiometricAuthCallbackAdapter(IBiometricAuthCallback callback, Context context)
 | |
|         {
 | |
|             _callback = callback;
 | |
|             _context = context;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result)
 | |
|         {
 | |
|             new Handler(Looper.MainLooper).Post(() => _callback.OnBiometricAuthSucceeded());
 | |
|         }
 | |
| 
 | |
|         public override void OnAuthenticationError(int errorCode, ICharSequence errString)
 | |
|         {
 | |
|             if (_ignoreNextError)
 | |
|             {
 | |
|                 _ignoreNextError = false;
 | |
|                 return;
 | |
|             }
 | |
|             new Handler(Looper.MainLooper).Post(() => _callback.OnBiometricError(errString.ToString()));
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public override void OnAuthenticationFailed()
 | |
|         {
 | |
|             new Handler(Looper.MainLooper).Post(() => _callback.OnBiometricAttemptFailed(_context.Resources.GetString(Resource.String.fingerprint_not_recognized)));
 | |
|         }
 | |
| 
 | |
|         public void IgnoreNextError()
 | |
|         {
 | |
|             _ignoreNextError = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| } | 
