Add device credential unlock

This commit is contained in:
J-Jamet
2020-11-23 19:14:44 +01:00
parent 6c5be88432
commit 75f245c7dc
8 changed files with 92 additions and 49 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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),

View File

@@ -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 dune entrée à laide 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">
<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">
<Preference
android:key="@string/advanced_unlock_explanation_key"
android:icon="@drawable/prefs_info_24dp"
android:summary="@string/advanced_unlock_explanation_summary"/>
<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>