diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index 5c9498240..f53e8768c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -200,11 +200,11 @@ open class PasswordActivity : SpecialModeActivity() { onActionFinish = { actionTask, result -> when (actionTask) { ACTION_DATABASE_LOAD_TASK -> { - // Recheck biometric if error + // Recheck advanced unlock if error if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) { + if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) { // Stay with the same mode and init it - advancedUnlockedManager?.initBiometricMode() + advancedUnlockedManager?.initAdvancedUnlockMode() } } @@ -370,7 +370,7 @@ open class PasswordActivity : SpecialModeActivity() { } else { // Init Biometric elements if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isBiometricUnlockEnable(this)) { + if (PreferencesUtil.isAdvancedUnlockEnable(this)) { if (advancedUnlockedManager == null && databaseFileUri != null) { advancedUnlockedManager = AdvancedUnlockedManager(this, @@ -658,7 +658,7 @@ open class PasswordActivity : SpecialModeActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !readOnlyEducationPerformed) { val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(this) - PreferencesUtil.isBiometricUnlockEnable(applicationContext) + PreferencesUtil.isAdvancedUnlockEnable(applicationContext) && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) && advancedUnlockInfoView != null && advancedUnlockInfoView?.visibility == View.VISIBLE && advancedUnlockInfoView?.unlockIconImageView != null diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt index 796c144b0..30dd2019b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt @@ -90,7 +90,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(context) allowOpenBiometricPrompt = true - if (!PreferencesUtil.isBiometricUnlockEnable(context) + if (!PreferencesUtil.isAdvancedUnlockEnable(context) || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { toggleMode(Mode.BIOMETRIC_UNAVAILABLE) @@ -136,7 +136,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, private fun toggleMode(newBiometricMode: Mode) { if (newBiometricMode != biometricMode) { biometricMode = newBiometricMode - initBiometricMode() + initAdvancedUnlockMode() } } @@ -292,7 +292,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, } @Synchronized - fun initBiometricMode() { + fun initAdvancedUnlockMode() { mAllowAdvancedUnlockMenu = false when (biometricMode) { Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable() diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt index 7c12160d8..7752ff822 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt @@ -29,11 +29,11 @@ import android.util.Base64 import android.util.Log import androidx.annotation.RequiresApi import androidx.biometric.BiometricManager -import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG -import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK +import androidx.biometric.BiometricManager.Authenticators.* import androidx.biometric.BiometricPrompt import androidx.fragment.app.FragmentActivity import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.settings.PreferencesUtil import java.security.KeyStore import java.security.UnrecoverableKeyException import java.util.concurrent.Executors @@ -58,31 +58,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null var biometricUnlockCallback: BiometricUnlockCallback? = null - private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply { - setTitle(context.getString(R.string.biometric_prompt_store_credential_title)) - setDescription(context.getString(R.string.biometric_prompt_store_credential_message)) - setConfirmationRequired(true) - // TODO device credential #102 #152 - /* - if (keyguardManager?.isDeviceSecure == true) - setDeviceCredentialAllowed(true) - else - */ - setNegativeButtonText(context.getString(android.R.string.cancel)) - }.build() - - private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply { - setTitle(context.getString(R.string.biometric_prompt_extract_credential_title)) - //setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) - setConfirmationRequired(false) - // TODO device credential #102 #152 - /* - if (keyguardManager?.isDeviceSecure == true) - setDeviceCredentialAllowed(true) - else - */ - setNegativeButtonText(context.getString(android.R.string.cancel)) - }.build() + private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context) val isKeyManagerInitialized: Boolean get() { @@ -139,6 +115,12 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { // Require the user to authenticate with a fingerprint to authorize every use // of the key .setUserAuthenticationRequired(true) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + && deviceCredentialUnlockEnable) { + setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL) + } + } .build()) keyGenerator?.generateKey() } @@ -164,13 +146,27 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { return } try { + // TODO if (keyguardManager?.isDeviceSecure == true) { getSecretKey()?.let { secretKey -> cipher?.init(Cipher.ENCRYPT_MODE, secretKey) initBiometricPrompt() - actionIfCypherInit.invoke(biometricPrompt, cryptoObject, promptInfoStoreCredential) - } + val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply { + setTitle(context.getString(R.string.biometric_prompt_store_credential_title)) + setDescription(context.getString(R.string.biometric_prompt_store_credential_message)) + setConfirmationRequired(true) + if (deviceCredentialUnlockEnable) { + setAllowedAuthenticators(DEVICE_CREDENTIAL) + } else { + setNegativeButtonText(context.getString(android.R.string.cancel)) + } + }.build() + + actionIfCypherInit.invoke(biometricPrompt, + cryptoObject, + promptInfoStoreCredential) + } } catch (unrecoverableKeyException: UnrecoverableKeyException) { Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException) biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException) @@ -211,6 +207,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { return } try { + // TODO if (keyguardManager?.isDeviceSecure == true) { // important to restore spec here that was used for decryption val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP) val spec = IvParameterSpec(iv) @@ -219,9 +216,22 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { cipher?.init(Cipher.DECRYPT_MODE, secretKey, spec) initBiometricPrompt() - actionIfCypherInit.invoke(biometricPrompt, cryptoObject, promptInfoExtractCredential) - } + val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply { + setTitle(context.getString(R.string.biometric_prompt_extract_credential_title)) + //setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) + setConfirmationRequired(false) + if (deviceCredentialUnlockEnable) { + setAllowedAuthenticators(DEVICE_CREDENTIAL) + } else { + setNegativeButtonText(context.getString(android.R.string.cancel)) + } + }.build() + + actionIfCypherInit.invoke(biometricPrompt, + cryptoObject, + promptInfoExtractCredential) + } } catch (unrecoverableKeyException: UnrecoverableKeyException) { Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException) deleteEntryKey() @@ -299,11 +309,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { fun canAuthenticate(context: Context): Int { return try { - BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) + } else { + BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG) + } } catch (e: Exception) { Log.e(TAG, "Unable to authenticate with strong biometric.", e) try { - BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK or DEVICE_CREDENTIAL) + } else { + BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK) + } } catch (e: Exception) { Log.e(TAG, "Unable to authenticate with weak biometric.", e) BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt index a8a0e6a3b..1821dc485 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt @@ -225,6 +225,10 @@ object PreferencesUtil { context.resources.getBoolean(R.bool.enable_auto_save_database_default)) } + fun isAdvancedUnlockEnable(context: Context): Boolean { + return isBiometricUnlockEnable(context) || isDeviceCredentialUnlockEnable(context) + } + fun isBiometricUnlockEnable(context: Context): Boolean { val prefs = PreferenceManager.getDefaultSharedPreferences(context) return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key), @@ -237,6 +241,12 @@ object PreferencesUtil { context.resources.getBoolean(R.bool.biometric_auto_open_prompt_default)) } + fun isDeviceCredentialUnlockEnable(context: Context): Boolean { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getBoolean(context.getString(R.string.device_credential_unlock_enable_key), + context.resources.getBoolean(R.bool.device_credential_unlock_enable_default)) + } + fun getListSort(context: Context): SortNodeEnum { val prefs = PreferenceManager.getDefaultSharedPreferences(context) prefs.getString(context.getString(R.string.sort_node_key), diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index df0ebb96e..788139210 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -416,7 +416,7 @@ Résoudre le problème en générant de nouveaux UUID pour les doublons et continuer \? Base de données ouverte Copier les champs d’une entrée à l’aide du presse-papier de votre appareil - Utilise le déverrouillage avancé pour ouvrir plus facilement une base de données + Utiliser le déverrouillage avancé pour ouvrir plus facilement une base de données Compression de données La compression des données réduit la taille de la base de données Nombre maximum diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index f83e51ae1..bfa602c58 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -97,6 +97,8 @@ biometric_auto_open_prompt_key false biometric_delete_all_key_key + device_credential_unlock_enable_key + false settings_form_filling_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 477813785..1b34e09f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -293,6 +293,7 @@ History Appearance Biometric + Device credential General Autofill KeePassDX form autofilling @@ -321,11 +322,13 @@ Use advanced unlocking to open a database more easily Biometric unlocking Lets you scan your biometric to open the database + Device credential unlocking + Lets you use your device credential to open the database Auto-open biometric prompt Automatically ask for biometric if the database is set up to use it Delete encryption keys - Delete all encryption keys related to biometric recognition - Delete all encryption keys related to biometric recognition? + Delete all encryption keys related to advanced unlock recognition + Delete all encryption keys related to advanced unlock recognition? Could not start this feature. The device is running Android %1$s, but needs %2$s or later. Could not find the corresponding hardware. diff --git a/app/src/main/res/xml/preferences_advanced_unlock.xml b/app/src/main/res/xml/preferences_advanced_unlock.xml index db45ab418..618ff9b15 100644 --- a/app/src/main/res/xml/preferences_advanced_unlock.xml +++ b/app/src/main/res/xml/preferences_advanced_unlock.xml @@ -18,12 +18,12 @@ along with KeePassDX. If not, see . --> + - + + + + + - \ No newline at end of file