mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Add device credential unlock
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -416,7 +416,7 @@
|
||||
<string name="contains_duplicate_uuid_procedure">Résoudre le problème en générant de nouveaux UUID pour les doublons et continuer \?</string>
|
||||
<string name="database_opened">Base de données ouverte</string>
|
||||
<string name="clipboard_explanation_summary">Copier les champs d’une entrée à l’aide du presse-papier de votre appareil</string>
|
||||
<string name="advanced_unlock_explanation_summary">Utilise le déverrouillage avancé pour ouvrir plus facilement une base de données</string>
|
||||
<string name="advanced_unlock_explanation_summary">Utiliser le déverrouillage avancé pour ouvrir plus facilement une base de données</string>
|
||||
<string name="database_data_compression_title">Compression de données</string>
|
||||
<string name="database_data_compression_summary">La compression des données réduit la taille de la base de données</string>
|
||||
<string name="max_history_items_title">Nombre maximum</string>
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
<string name="biometric_auto_open_prompt_key" translatable="false">biometric_auto_open_prompt_key</string>
|
||||
<bool name="biometric_auto_open_prompt_default" translatable="false">false</bool>
|
||||
<string name="biometric_delete_all_key_key" translatable="false">biometric_delete_all_key_key</string>
|
||||
<string name="device_credential_unlock_enable_key" translatable="false">device_credential_unlock_enable_key</string>
|
||||
<bool name="device_credential_unlock_enable_default" translatable="false">false</bool>
|
||||
|
||||
<!-- Form Filling Settings -->
|
||||
<string name="settings_form_filling_key" translatable="false">settings_form_filling_key</string>
|
||||
|
||||
@@ -293,6 +293,7 @@
|
||||
<string name="database_history">History</string>
|
||||
<string name="menu_appearance_settings">Appearance</string>
|
||||
<string name="biometric">Biometric</string>
|
||||
<string name="device_credential">Device credential</string>
|
||||
<string name="general">General</string>
|
||||
<string name="autofill">Autofill</string>
|
||||
<string name="autofill_service_name">KeePassDX form autofilling</string>
|
||||
@@ -321,11 +322,13 @@
|
||||
<string name="advanced_unlock_explanation_summary">Use advanced unlocking to open a database more easily</string>
|
||||
<string name="biometric_unlock_enable_title">Biometric unlocking</string>
|
||||
<string name="biometric_unlock_enable_summary">Lets you scan your biometric to open the database</string>
|
||||
<string name="device_credential_unlock_enable_title">Device credential unlocking</string>
|
||||
<string name="device_credential_unlock_enable_summary">Lets you use your device credential to open the database</string>
|
||||
<string name="biometric_auto_open_prompt_title">Auto-open biometric prompt</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatically ask for biometric if the database is set up to use it</string>
|
||||
<string name="biometric_delete_all_key_title">Delete encryption keys</string>
|
||||
<string name="biometric_delete_all_key_summary">Delete all encryption keys related to biometric recognition</string>
|
||||
<string name="biometric_delete_all_key_warning">Delete all encryption keys related to biometric recognition?</string>
|
||||
<string name="biometric_delete_all_key_summary">Delete all encryption keys related to advanced unlock recognition</string>
|
||||
<string name="biometric_delete_all_key_warning">Delete all encryption keys related to advanced unlock recognition?</string>
|
||||
<string name="unavailable_feature_text">Could not start this feature.</string>
|
||||
<string name="unavailable_feature_version">The device is running Android %1$s, but needs %2$s or later.</string>
|
||||
<string name="unavailable_feature_hardware">Could not find the corresponding hardware.</string>
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:title="@string/biometric">
|
||||
<Preference
|
||||
android:key="@string/advanced_unlock_explanation_key"
|
||||
android:icon="@drawable/prefs_info_24dp"
|
||||
android:summary="@string/advanced_unlock_explanation_summary"/>
|
||||
<PreferenceCategory
|
||||
android:title="@string/biometric">
|
||||
<SwitchPreference
|
||||
android:key="@string/biometric_unlock_enable_key"
|
||||
android:title="@string/biometric_unlock_enable_title"
|
||||
@@ -35,10 +35,20 @@
|
||||
android:summary="@string/biometric_auto_open_prompt_summary"
|
||||
android:dependency="@string/biometric_unlock_enable_key"
|
||||
android:defaultValue="@bool/biometric_auto_open_prompt_default"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/device_credential">
|
||||
<SwitchPreference
|
||||
android:key="@string/device_credential_unlock_enable_key"
|
||||
android:title="@string/device_credential_unlock_enable_title"
|
||||
android:summary="@string/device_credential_unlock_enable_summary"
|
||||
android:defaultValue="@bool/device_credential_unlock_enable_default"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/general">
|
||||
<Preference
|
||||
android:key="@string/biometric_delete_all_key_key"
|
||||
android:title="@string/biometric_delete_all_key_title"
|
||||
android:summary="@string/biometric_delete_all_key_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user