From 3e5c6889728922dbf69c0e1b8b4397dc91ec1df9 Mon Sep 17 00:00:00 2001 From: Steven Audette Date: Fri, 23 Jun 2017 20:46:50 -0600 Subject: [PATCH 01/24] Add Multi Window support for Samsung devices A single meta-data entry is enough to make KeepassDroid "Multi Window" aware on supported Samsung devices. Tested and confirmed working on Samsung Note Pro 12.2 running Android 5.1.1. --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 639d195c3..e136af562 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -106,5 +106,6 @@ android:theme="@style/NoTitleBar"> + \ No newline at end of file From 91f0b4d1ec0a49cffc343c41d8a53b20a1aad760 Mon Sep 17 00:00:00 2001 From: Credomo Date: Sat, 19 Aug 2017 00:13:24 +0200 Subject: [PATCH 02/24] Updated fr (French) translation Commit made via Stringlate --- app/src/androidTest/res/values-fr/strings.xml | 5 ++ app/src/main/res/values-fr/strings.xml | 84 ++++++++++--------- 2 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 app/src/androidTest/res/values-fr/strings.xml diff --git a/app/src/androidTest/res/values-fr/strings.xml b/app/src/androidTest/res/values-fr/strings.xml new file mode 100644 index 000000000..1768da0f9 --- /dev/null +++ b/app/src/androidTest/res/values-fr/strings.xml @@ -0,0 +1,5 @@ + + + Salut le Monde, Fantôme + KeePassDroid + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 800ecb19b..0c19c03a3 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,36 +1,36 @@ - - Signaler une anomalie\u00A0: - Site web\u00A0: + + Signaler une anomalie\\u00A0: + Site web\\u00A0: KeePassDroid est une implémentation sur Android du gestionnaire de mots de passe KeePass. - Twitter\u00A0: + Twitter\\u00A0: Accepter Ajouter une entrée Ajouter un groupe Ajouter un groupe + Ajouter une chaîne Algorithme - Algorithme\u00A0: + Algorithme\\u00A0: KeePassDroid Application timeout Temps avant le verrouillage de la base de données lorsque l\'application est inactive. @@ -40,29 +40,29 @@ Enregistrer des modifications dans les fichiers kdbx est EXPÉRIMENTAL. Faites des sauvegardes de votre base de données avant toute modification. Crochets File browsing requires the Open Intents File Manager, click below to install it. Due to some quirks in the file manager, browsing may not work correctly, the first time you browse. - Reconstruction de l\'index de recherche… + Reconstruction de l\'index de recherche… Annuler Presse-papier vidé Erreur de presse-papier - Certains appareils Android Samsung ont un bug dans l\'implémentation du presse-papier qui empêche la copie depuis des applications. Pour plus de détails, visitez\u00A0: + Certains appareils Android Samsung ont un bug dans l\'implémentation du presse-papier qui empêche la copie depuis des applications. Pour plus de détails, visitez\\u00A0: Le vidage du presse-papier a échoué Clipboard timeout Temps avant le vidage du presse-papier après copie du nom d\'utilisateur ou du mot de passe Copier le nom d\'utilisateur dans le presse-papier Copier le mot de passe dans le presse-papier - Création de la clé de base de données… - Groupe actuel\u00A0: - Groupe actuel\u00A0: Racine + Création de la clé de base de données… + Groupe actuel\\u00A0: + Groupe actuel\\u00A0: Racine Base de données - Déchiffrement du contenu de la base de données… + Déchiffrement du contenu de la base de données… Déchiffrement de l\'entrée Utiliser comme base de données par défaut Nombres - KeePassDroid Copyright 2009–2012 Brian Pellin n\'offre ABSOLUMENT AUCUNE GARANTIE; il s\'agit d\'un logiciel libre, vous pouvez le redistribuer sous les conditions de la licence GPL v2 ou ultérieure. - - Sélectionnez la base de données\u00A0: + KeePassDroid Copyright 2009–2012 Brian Pellin n\'offre ABSOLUMENT AUCUNE GARANTIE; il s\'agit d\'un logiciel libre, vous pouvez le redistribuer sous les conditions de la licence GPL v2 ou ultérieure. + \\u2026 + Sélectionnez la base de données\\u00A0: Dernier accès - Entrez un mot de passe et/ou un fichier de clé pour ouvrir la base de données\u00A0: + Entrez un mot de passe et/ou un fichier de clé pour ouvrir la base de données\\u00A0: Annuler Commentaires Confirmer mot de passe @@ -84,7 +84,7 @@ Impossible de déterminer les paramètres de la base de données. Échec lors de l\'ouverture du lien. Le nom de fichier est obligatoire. - Impossible de créer le fichier\u00A0: + Impossible de créer le fichier\\u00A0: Base de données invalide. Chemin invalide. Le nom est obligatoire. @@ -100,6 +100,7 @@ Nom du champ Valeur Fichier non trouvé. + Fichier non trouvé. Essayer de l\'ouvrir à nouveau à partir de votre fournisseur de contenu. Navigateur de fichiers Générer mot de passe Groupe @@ -124,9 +125,9 @@ Longueur Taille de la liste des groupes Taille de la police de caractères utilisée pour la liste des groupes - Ouverture de la base de données… + Ouverture de la base de données… Minuscule - + ***** Afficher le mot de passe Par défaut, masquer le mot de passe À propos @@ -152,14 +153,15 @@ Aucun élément. Aucun résultat pour cette recherche. Impossible d\'ouvrir cette URL. - Bases de données utilisées récemment\u00A0: + Bases de données utilisées récemment\\u00A0: Ignorer les sauvegardes Ignorer le groupe Sauvegardes des résultats de recherche (uniquement pour .kdb) Fichier de base de données KeePass Entrez le mot de passe de la base de données - Création d\'une nouvelle base de données… - Veuillez patienter… + Création d\'une nouvelle base de données… + Veuillez patienter… Protection + Lecture seule KeePassDroid n\'a pas la permission d\'écrire dans le répertoire de la base de données. Celle-ci sera ouverte en lecture seule. À partir d\'Android KitKat, certains appareils n\'autorisent plus les applications à écrire sur la carte SD. Historique de fichiers récents @@ -172,7 +174,7 @@ Niveau du chiffrement Un niveau de chiffrement supérieur assure une protection supplémentaire contre les attaques de force brute, mais peut considérablement ralentir l\'ouverture et l\'enregistrement. niveaux - Enregistrement de la base de données… + Enregistrement de la base de données… Espace Rechercher Afficher le mot de passe @@ -185,19 +187,23 @@ Souligné Version de la base de données non supportée. Majuscule + Utilisez l\'Environnement d\'Accès au Stockage d\'Android pour naviguer dans les fichiers (KitKat ou plus récent) + Environnement d\'Accès au Stockage + Alerte + Le format .kdb ne supporte que le jeu de caractère Latin1. Votre mot de passe doit contenir des caractères en dehors de ce jeu. Tous les caractères non-Latin1 sont convertis en un même caractère, ce qui diminue la sécurité de votre mot de passe. Le changement de votre mot de passe est recommandé. Votre carte SD est actuellement en lecture seule. Vous ne pourrez pas enregistrer les changements dans la base de données. Votre carte SD n\'est actuellement pas montée sur votre appareil. Vous ne pourrez pas charger ou créer votre base de données. - Version\u00A0: - + Version\\u00A0: + - 30 secondes - 1 minute - 5 minutes - Jamais + 30 secondes + 1 minute + 5 minutes + Jamais - Petit - Moyen - Grand + Petit + Moyen + Grand From 6d2358445e79057b7efa877021744acdff1dbc7b Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 29 Aug 2017 01:21:04 +0300 Subject: [PATCH 03/24] Updated Russian --- app/src/main/res/values-ru/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ce9f5a555..680607ac5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -185,6 +185,10 @@ _Подчёркивание_ Неподдерживаемая версия базы ЗАГЛАВНЫЕ + Storage Access Framework для обзора файлов (KK+) + Обзор через SAF + Внимание + Формат .kdb поддерживает только кодировку Latin1. Пароль может содержать символы вне этой кодировки. Все не-Latin1 символы будут преобразованы в одинаковый символ, что снизит надёжность пароля. Рекомендуется изменить пароль. Запись на карту памяти невозможна. Изменения не будут сохранены Карта памяти не подключена. Работа с базой невозможна Версия: From c2e97dbe2fabc4bba98f92f7916588841a5c96e7 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 29 Aug 2017 01:30:00 +0300 Subject: [PATCH 04/24] Update strings.xml --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 680607ac5..0eed4ebd7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -58,7 +58,7 @@ "KeePassDroid © 2009–2013 Разработчик Brian Pellin -Программа предоставляется БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ. Распространяется свободно по лицензии GPL v3 или новее" +Программа предоставляется БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ. Распространяется свободно по лицензии GPL v2 или новее" Путь к базе KeePass: Доступ: From 413f4d634fb5a815c60724435a40dcb78bbe8fa0 Mon Sep 17 00:00:00 2001 From: Hans Cappelle Date: Fri, 6 Oct 2017 19:38:14 +0200 Subject: [PATCH 05/24] update layout with fingerprint icon next to password input field added FingerPrintHelper class for fingerprint keystore handling update targetSdk for fingerprint support add fingerprint permission to manifest update PasswordActivity for fingerprint handling --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 1 + .../com/keepassdroid/PasswordActivity.java | 392 ++++++++++++++---- .../fingerprint/FingerPrintHelper.java | 316 ++++++++++++++ app/src/main/res/drawable-hdpi/ic_fp_40px.png | Bin 0 -> 7011 bytes .../main/res/drawable-xhdpi/ic_fp_40px.png | Bin 0 -> 10524 bytes .../main/res/drawable-xxhdpi/ic_fp_40px.png | Bin 0 -> 18565 bytes app/src/main/res/layout/password.xml | 52 ++- app/src/main/res/values/dimens.xml | 8 + app/src/main/res/values/strings.xml | 8 +- 10 files changed, 677 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/com/keepassdroid/fingerprint/FingerPrintHelper.java create mode 100644 app/src/main/res/drawable-hdpi/ic_fp_40px.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_fp_40px.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_fp_40px.png create mode 100644 app/src/main/res/values/dimens.xml diff --git a/app/build.gradle b/app/build.gradle index 7e66b65bc..b5afe4c87 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion = 22 + compileSdkVersion = 23 buildToolsVersion = "25.0.0" defaultConfig { applicationId = "com.android.keepass" minSdkVersion = 3 - targetSdkVersion = 12 + targetSdkVersion 23 versionCode = 154 versionName = "2.0.6.4" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 639d195c3..bfa80725b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ android:largeScreens="true" android:anyDensity="true" /> + = Build.VERSION_CODES.M) { + fingerPrintHelper = new FingerPrintHelper(this, this); + fingerprintView = findViewById(R.id.fingerprint); + confirmationView = (TextView) findViewById(R.id.fingerprint_label); + passwordView = (EditText) findViewById(R.id.password); + + // when text entered we can enable the logon/purchase button and if required update encryption/decryption mode + passwordView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged( + final CharSequence s, + final int start, + final int count, + final int after) { + + } + + @Override + public void onTextChanged( + final CharSequence s, + final int start, + final int before, + final int count) { + + } + + @Override + public void afterTextChanged(final Editable s) { + final boolean validInput = s.length() > 0; + // encrypt or decrypt mode based on how much input or not + confirmationView.setText(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint); + mode = validInput ? toggleMode(Cipher.ENCRYPT_MODE) : toggleMode(Cipher.DECRYPT_MODE); + + } + }); + + // callback for fingerprint findings + fingerPrintHelper.setAuthenticationCallback(new FingerprintManager.AuthenticationCallback() { + @Override + public void onAuthenticationError( + final int errorCode, + final CharSequence errString) { + + // this is triggered on stop/start listening done by helper to switch between modes so don't restart here + // errorCode = 5 + // errString = "Fingerprint operation canceled." + //onException(); + //confirmationView.setText(errString); + // true false fingerprint readings are handled otherwise with the toast messages, see below in code + } + + @Override + public void onAuthenticationHelp( + final int helpCode, + final CharSequence helpString) { + + onException(); + confirmationView.setText(helpString); + } + + @Override + public void onAuthenticationSucceeded(final FingerprintManager.AuthenticationResult result) { + + if (mode == Cipher.ENCRYPT_MODE) { + + final String password = passwordView.getText().toString(); + fingerPrintHelper.encryptData(password); + + } else if (mode == Cipher.DECRYPT_MODE) { + + // retrieve the encrypted value from preferences + final String encryptedValue = prefs.getString(getPreferenceKey(), null); + if (encryptedValue != null) { + fingerPrintHelper.decryptData(encryptedValue); + } + } + } + + @Override + public void onAuthenticationFailed() { + onException(); + } + }); + } + } + + private String getPreferenceKey() { + // TODO improve unique key per database generation, using complete path should be unique and seems to work + return PREF_KEY_PREFIX + mDbUri != null ? mDbUri.getPath() : "";//mDbUri.getLastPathSegment(); + } + + private int toggleMode(final int newMode) { + // check if mode is different so we can update fingerprint helper + if (mode != newMode) { + mode = newMode; + fingerPrintHelper.initForMode(mode); + return newMode; + } + // remains in current mode + return mode; + } + + @Override + protected void onPause() { + super.onPause(); + // stop listening when we go in background + if (fingerPrintHelper != null) { + fingerPrintHelper.stopListening(); + } + } + + private void checkAvailability() { + // fingerprint not supported (by API level or hardware) so keep option hidden + if (!fingerPrintHelper.isFingerprintSupported()) { + + fingerprintView.setVisibility(View.GONE); + // since this is a fingerprint example inform user, don't do this in your app + //confirmationView.setText(R.string.fingerprint_not_supported); + confirmationView.setVisibility(View.GONE); + } + // fingerprint is available but not configured show icon but in disabled state with some information + else if (!fingerPrintHelper.hasEnrolledFingerprints()) { + + fingerprintView.setVisibility(View.VISIBLE); + confirmationView.setVisibility(View.VISIBLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + fingerprintView.setAlpha(0.3f); + } + // This happens when no fingerprints are registered. Listening won't start + confirmationView.setText(R.string.configure_fingerprint); + } + // finally fingerprint available and configured so we can use it + else { + + fingerprintView.setVisibility(View.VISIBLE); + confirmationView.setVisibility(View.VISIBLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + fingerprintView.setAlpha(1f); + } + // all is set here so we can confirm to user and start listening for fingerprints + confirmationView.setText(R.string.scanning_fingerprint); + // listen for decryption by default + toggleMode(Cipher.DECRYPT_MODE); + } + } + + @Override + public void handleResult(final String value) { + if (mode == Cipher.ENCRYPT_MODE) { + + prefs.edit().putString(getPreferenceKey(), value).commit(); + // and remove visual input to reset UI + passwordView.setText(""); + confirmationView.setText(R.string.encrypted_value_stored); + + } else if (mode == Cipher.DECRYPT_MODE) { + + // on decrypt enter it for the purchase/login action + passwordView.setText(value); + // TODO also open DB right away + } + } + + @Override + public void onInvalidKeyException() { + Toast.makeText(this, R.string.fingerprint_invalid_key, Toast.LENGTH_SHORT).show(); + checkAvailability(); // restarts listening + } + + @Override + public void onException() { + Toast.makeText(this, R.string.fingerprint_error, Toast.LENGTH_SHORT).show(); + checkAvailability(); // restarts listening + } + private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener { @Override - public void onCheckedChanged(CompoundButton buttonView, + public void onCheckedChanged( + CompoundButton buttonView, boolean isChecked) { String newDefaultFileName; @@ -262,13 +466,16 @@ public class PasswordActivity extends LockingActivity { } } - private void loadDatabase(String pass, String keyfile) { + private void loadDatabase( + String pass, + String keyfile) { loadDatabase(pass, UriUtil.parseDefaultFile(keyfile)); } - private void loadDatabase(String pass, Uri keyfile) - { - if ( pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) { + private void loadDatabase( + String pass, + Uri keyfile) { + if (pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) { errorMessage(R.string.error_nopass); return; } @@ -290,9 +497,11 @@ public class PasswordActivity extends LockingActivity { return Util.getEditText(this, resId); } - private void setEditText(int resId, String str) { - TextView te = (TextView) findViewById(resId); - assert(te == null); + private void setEditText( + int resId, + String str) { + TextView te = (TextView) findViewById(resId); + assert (te == null); if (te != null) { te.setText(str); @@ -311,24 +520,27 @@ public class PasswordActivity extends LockingActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { - switch ( item.getItemId() ) { - case R.id.menu_about: - AboutDialog dialog = new AboutDialog(this); - dialog.show(); - return true; + switch (item.getItemId()) { + case R.id.menu_about: + AboutDialog dialog = new AboutDialog(this); + dialog.show(); + return true; - case R.id.menu_app_settings: - AppSettingsActivity.Launch(this); - return true; + case R.id.menu_app_settings: + AppSettingsActivity.Launch(this); + return true; } return super.onOptionsItemSelected(item); } private final class AfterLoad extends OnFinish { + private Database db; - public AfterLoad(Handler handler, Database db) { + public AfterLoad( + Handler handler, + Database db) { super(handler); this.db = db; @@ -336,17 +548,19 @@ public class PasswordActivity extends LockingActivity { @Override public void run() { - if ( db.passwordEncodingError) { + if (db.passwordEncodingError) { PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper(); dialog.show(PasswordActivity.this, new OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick( + DialogInterface dialog, + int which) { GroupActivity.Launch(PasswordActivity.this); } }); - } else if ( mSuccess ) { + } else if (mSuccess) { GroupActivity.Launch(PasswordActivity.this); } else { displayMessage(PasswordActivity.this); @@ -355,23 +569,24 @@ public class PasswordActivity extends LockingActivity { } private class InitTask extends AsyncTask { + String password = ""; boolean launch_immediately = false; @Override protected Integer doInBackground(Intent... args) { Intent i = args[0]; - String action = i.getAction();; - if ( action != null && action.equals(VIEW_INTENT) ) { + String action = i.getAction(); + ; + if (action != null && action.equals(VIEW_INTENT)) { Uri incoming = i.getData(); mDbUri = incoming; - mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE); + mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE); if (incoming == null) { return R.string.error_can_not_handle_uri; - } - else if (incoming.getScheme().equals("file")) { + } else if (incoming.getScheme().equals("file")) { String fileName = incoming.getPath(); if (fileName.length() == 0) { @@ -385,14 +600,14 @@ public class PasswordActivity extends LockingActivity { return R.string.FileNotFound; } - if(mKeyUri == null) - mKeyUri = getKeyFile(mDbUri); - } - else if (incoming.getScheme().equals("content")) { - if(mKeyUri == null) - mKeyUri = getKeyFile(mDbUri); - } - else { + if (mKeyUri == null) { + mKeyUri = getKeyFile(mDbUri); + } + } else if (incoming.getScheme().equals("content")) { + if (mKeyUri == null) { + mKeyUri = getKeyFile(mDbUri); + } + } else { return R.string.error_can_not_handle_uri; } password = i.getStringExtra(KEY_PASSWORD); @@ -404,7 +619,7 @@ public class PasswordActivity extends LockingActivity { password = i.getStringExtra(KEY_PASSWORD); launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false); - if ( mKeyUri == null || mKeyUri.toString().length() == 0) { + if (mKeyUri == null || mKeyUri.toString().length() == 0) { mKeyUri = getKeyFile(mDbUri); } } @@ -412,7 +627,7 @@ public class PasswordActivity extends LockingActivity { } public void onPostExecute(Integer result) { - if(result != null) { + if (result != null) { Toast.makeText(PasswordActivity.this, result, Toast.LENGTH_LONG).show(); finish(); return; @@ -427,11 +642,12 @@ public class PasswordActivity extends LockingActivity { // Show or hide password checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, + public void onCheckedChanged( + CompoundButton buttonView, boolean isChecked) { TextView password = (TextView) findViewById(R.id.password); - if ( isChecked ) { + if (isChecked) { password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } else { password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); @@ -457,8 +673,7 @@ public class PasswordActivity extends LockingActivity { i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); startActivityForResult(i, OPEN_DOC); - } - else { + } else { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); @@ -508,8 +723,9 @@ public class PasswordActivity extends LockingActivity { retrieveSettings(); - if (launch_immediately) + if (launch_immediately) { loadDatabase(password, mKeyUri); + } } } } diff --git a/app/src/main/java/com/keepassdroid/fingerprint/FingerPrintHelper.java b/app/src/main/java/com/keepassdroid/fingerprint/FingerPrintHelper.java new file mode 100644 index 000000000..295460878 --- /dev/null +++ b/app/src/main/java/com/keepassdroid/fingerprint/FingerPrintHelper.java @@ -0,0 +1,316 @@ +package com.keepassdroid.fingerprint; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.KeyguardManager; +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.CancellationSignal; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.util.Base64; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.KeyStore; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +public class FingerPrintHelper { + + private int MINIMAL_REQUIRED_SDK_VERSION = Build.VERSION_CODES.M; + + private static final String ALIAS_KEY = "example-key"; + private static final String IV_FILE = "iv-file"; + + private FingerprintManager fingerprintManager; + private Context context; + private KeyStore keyStore = null; + private KeyGenerator keyGenerator = null; + private Cipher cipher = null; + private KeyguardManager keyguardManager = null; + private FingerprintManager.CryptoObject cryptoObject = null; + + private boolean initOk = false; + private IvParameterSpec spec; + private FingerPrintCallback fingerPrintCallback; + private CancellationSignal cancellationSignal; + private FingerprintManager.AuthenticationCallback authenticationCallback; + + public void setAuthenticationCallback(final FingerprintManager.AuthenticationCallback authenticationCallback) { + this.authenticationCallback = authenticationCallback; + } + + @SuppressLint("NewApi") + public void startListening() { + // no need to start listening when not initialised + if (!isFingerprintInitialized()) { + if (fingerPrintCallback != null) { + fingerPrintCallback.onException(); + } + return; + } + // starts listening for fingerprints with the initialised crypto object + cancellationSignal = new CancellationSignal(); + fingerprintManager.authenticate( + cryptoObject, + cancellationSignal, + 0 /* flags */, + authenticationCallback, + null); + } + + @SuppressLint("NewApi") + public void stopListening() { + if (!isFingerprintInitialized()) { + return; + } + if (cancellationSignal != null) { + cancellationSignal.cancel(); + cancellationSignal = null; + } + } + + public void initForMode(final int mode) { + switch (mode) { + case Cipher.ENCRYPT_MODE: { + initEncryptData(); + break; + } + case Cipher.DECRYPT_MODE: { + initDecryptData(); + break; + } + } + } + + public interface FingerPrintCallback { + + void handleResult(String value); + + void onInvalidKeyException(); + + void onException(); + + } + + @TargetApi(Build.VERSION_CODES.M) + public FingerPrintHelper( + final Context context, + final FingerPrintCallback fingerPrintCallback) { + + if (!isFingerprintSupported()) { + // really not much to do when no fingerprint support found + setInitOk(false); + return; + } + this.context = context; + this.fingerprintManager = context.getSystemService(FingerprintManager.class); + this.keyguardManager = context.getSystemService(KeyguardManager.class); + this.fingerPrintCallback = fingerPrintCallback; + + if (hasEnrolledFingerprints()) { + try { + this.keyStore = KeyStore.getInstance("AndroidKeyStore"); + this.keyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_AES, + "AndroidKeyStore"); + this.cipher = Cipher.getInstance( + KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + this.cryptoObject = new FingerprintManager.CryptoObject(cipher); + setInitOk(true); + } catch (final Exception e) { + setInitOk(false); + fingerPrintCallback.onException(); + } + } + } + + public boolean isFingerprintInitialized() { + return hasEnrolledFingerprints() && initOk; + } + + @SuppressWarnings("NewApi") + public void initEncryptData() { + + if (!isFingerprintInitialized()) { + if (fingerPrintCallback != null) { + fingerPrintCallback.onException(); + } + return; + } + try { + createNewKeyIfNeeded(false); // no need to keep deleting existing keys + keyStore.load(null); + final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null); + cipher.init(Cipher.ENCRYPT_MODE, key); + + stopListening(); + startListening(); + + } catch (final KeyPermanentlyInvalidatedException invalidKeyException) { + fingerPrintCallback.onInvalidKeyException(); + } catch (final Exception e) { + fingerPrintCallback.onException(); + } + } + + @SuppressWarnings("NewApi") + public void encryptData(final String value) { + + if (!isFingerprintInitialized()) { + if (fingerPrintCallback != null) { + fingerPrintCallback.onException(); + } + return; + } + try { + // actual do encryption here + byte[] encrypted = cipher.doFinal(value.getBytes()); + final String encryptedValue = Base64.encodeToString(encrypted, 0 /* flags */); + + // create & store spec here since we need it to decrypt again later on (only done at this point to prevent failures on next attempts if + // we never encrypted anything new) + spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class); + final FileOutputStream fileOutputStream = context.openFileOutput(IV_FILE, Context.MODE_PRIVATE); + fileOutputStream.write(spec.getIV()); + fileOutputStream.close(); + + fingerPrintCallback.handleResult(encryptedValue); + + } catch (final Exception e) { + fingerPrintCallback.onException(); + } + + } + + @SuppressWarnings("NewApi") + public void initDecryptData() { + + if (!isFingerprintInitialized()) { + if (fingerPrintCallback != null) { + fingerPrintCallback.onException(); + } + return; + } + + try { + createNewKeyIfNeeded(false); + keyStore.load(null); + final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null); + + // restore spec + final File file = new File(context.getFilesDir() + "/" + IV_FILE); + final int fileSize = (int) file.length(); + final byte[] iv = new byte[fileSize]; + + final FileInputStream fileInputStream = context.openFileInput(IV_FILE); + fileInputStream.read(iv, 0, fileSize); + fileInputStream.close(); + + spec = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + + stopListening(); + startListening(); + + } catch (final KeyPermanentlyInvalidatedException invalidKeyException) { + fingerPrintCallback.onInvalidKeyException(); + } catch (final Exception e) { + fingerPrintCallback.onException(); + } + } + + @SuppressWarnings("NewApi") + public void decryptData(final String encryptedValue) { + + if (!isFingerprintInitialized()) { + if (fingerPrintCallback != null) { + fingerPrintCallback.onException(); + } + return; + } + + try { + // actual decryption here + final byte[] encrypted = Base64.decode(encryptedValue, 0); + byte[] decrypted = cipher.doFinal(encrypted); + final String decryptedString = new String(decrypted); + //final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */); + fingerPrintCallback.handleResult(decryptedString); + + } catch (final Exception e) { + fingerPrintCallback.onException(); + } + } + + @SuppressLint("NewApi") + private void createNewKeyIfNeeded(final boolean allowDeleteExisting) { + if (!isFingerprintInitialized()) { + return; + } + try { + keyStore.load(null); + if (allowDeleteExisting + && keyStore.containsAlias(ALIAS_KEY)) { + + keyStore.deleteEntry(ALIAS_KEY); + } + + // Create new key if needed + if (!keyStore.containsAlias(ALIAS_KEY)) { + // Set the alias of the entry in Android KeyStore where the key will appear + // and the constrains (purposes) in the constructor of the Builder + keyGenerator.init( + new KeyGenParameterSpec.Builder( + ALIAS_KEY, + KeyProperties.PURPOSE_ENCRYPT | + KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + // Require the user to authenticate with a fingerprint to authorize every use + // of the key + .setUserAuthenticationRequired(true) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .build()); + keyGenerator.generateKey(); + } + } catch (final Exception e) { + fingerPrintCallback.onException(); + } + } + + @SuppressLint("NewApi") + public boolean isHardwareDetected() { + return isFingerprintSupported() + && fingerprintManager != null + && fingerprintManager.isHardwareDetected(); + } + + @SuppressLint("NewApi") + public boolean hasEnrolledFingerprints() { + // fingerprint hardware supported and api level OK + return isHardwareDetected() + // fingerprints enrolled + && fingerprintManager != null + && fingerprintManager.hasEnrolledFingerprints() + // and lockscreen configured + && keyguardManager.isKeyguardSecure(); + } + + void setInitOk(final boolean initOk) { + this.initOk = initOk; + } + + public boolean isFingerprintSupported() { + return Build.VERSION.SDK_INT >= MINIMAL_REQUIRED_SDK_VERSION; + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_fp_40px.png b/app/src/main/res/drawable-hdpi/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..48ebd8ad737272d78d0ab9141c47c84f463fa3bf GIT binary patch literal 7011 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_fp_40px.png b/app/src/main/res/drawable-xhdpi/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c9590bbfbcf220b08879f2f9e560a4f285f4ec GIT binary patch literal 10524 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 ZgHjVyDhp4hf{p`X@O1TaS?83{1ORKvFslFn literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/password.xml b/app/src/main/res/layout/password.xml index 990945936..f88201756 100644 --- a/app/src/main/res/layout/password.xml +++ b/app/src/main/res/layout/password.xml @@ -18,11 +18,12 @@ along with KeePassDroid. If not, see . --> - + - - + + + + + - -