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