Fix device credential condition and keep connexion if ActivityForResult requested

This commit is contained in:
J-Jamet
2020-12-13 22:33:58 +01:00
parent ec33ca8173
commit 388cf6a91b
2 changed files with 87 additions and 70 deletions

View File

@@ -72,6 +72,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private var mAllowAdvancedUnlockMenu = false
private var mAddBiometricMenuInProgress = false
// Only keep connection when we request a device credential activity
private var keepConnection = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -113,6 +116,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// To wait resume
activityResult = ActivityResult(requestCode, resultCode, data)
keepConnection = false
super.onActivityResult(requestCode, resultCode, data)
}
@@ -121,6 +125,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
super.onResume()
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext())
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext())
keepConnection = false
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -148,7 +153,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
// To get device credential unlock result, only if same database uri
activityResult?.let {
if (databaseUri != null) {
advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode, it.data)
advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode)
}
} ?: run {
if (databaseUri != null && mAdvancedUnlockEnabled) {
@@ -167,62 +172,71 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
*/
fun checkUnlockAvailability() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
allowOpenBiometricPrompt = true
if (PreferencesUtil.isDeviceCredentialUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(requireContext())) {
selectMode()
} else {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
}
} else if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
}
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
allowOpenBiometricPrompt = true
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
advancedUnlockManager = AdvancedUnlockManager { requireActivity() }
// callback for fingerprint findings
advancedUnlockManager?.advancedUnlockCallback = this
}
// Recheck to change the mode
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} else {
if (mBuilderListener?.conditionToStoreCredential() == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode(if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
} ?: throw IODatabaseException()
}
selectMode()
}
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun selectMode() {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
advancedUnlockManager = AdvancedUnlockManager { requireActivity() }
// callback for fingerprint findings
advancedUnlockManager?.advancedUnlockCallback = this
}
// Recheck to change the mode
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (mBuilderListener?.conditionToStoreCredential() == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode(if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
} ?: throw IODatabaseException()
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun toggleMode(newBiometricMode: Mode) {
if (newBiometricMode != biometricMode) {
@@ -287,9 +301,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
requireActivity().runOnUiThread {
if (allowOpenBiometricPrompt) {
if (cryptoPrompt.isDeviceCredentialOperation)
keepConnection = true
try {
advancedUnlockManager
?.openAdvancedUnlockPrompt(cryptoPrompt)
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt)
} catch (e: Exception) {
Log.e(TAG, "Unable to open advanced unlock prompt", e)
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
@@ -348,13 +363,12 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
}
invalidateBiometricMenu()
}
}
@@ -402,7 +416,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
cipherDatabaseListener?.let {
cipherDatabaseAction.unregisterDatabaseListener(it)
}
cipherDatabaseListener = null
mAdvancedUnlockInfoView?.visibility = View.GONE
}
@@ -441,7 +454,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
}
Mode.BIOMETRIC_NOT_CONFIGURED -> {
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> {
}
Mode.KEY_MANAGER_UNAVAILABLE -> {
}
@@ -520,7 +533,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_NOT_CONFIGURED,
DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED,
KEY_MANAGER_UNAVAILABLE,
WAIT_CREDENTIAL,
STORE_CREDENTIAL,
@@ -534,6 +547,15 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
}
override fun onPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!keepConnection)
disconnect()
}
super.onPause()
}
override fun onDestroyView() {
mAdvancedUnlockInfoView = null
@@ -543,6 +565,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
disconnect()
advancedUnlockManager = null
mBuilderListener = null
}
@@ -551,7 +574,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onDetach() {
mBuilderListener = null
biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
biometricMode = Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED
super.onDetach()
}

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.biometric
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
@@ -142,8 +141,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
// of the key, don't use it for device credential because it's the user authentication
.apply {
if (isBiometricOperation()) {
setUserAuthenticationRequired(true)
}
}
.build())
keyGenerator?.generateKey()
}
@@ -293,7 +296,6 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
} ?: ""
if (cryptoPrompt.isDeviceCredentialOperation) {
// TODO open intent keyguard for response
val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
retrieveContext().startActivityForResult(
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
@@ -319,7 +321,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
@Synchronized
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
fun onActivityResult(requestCode: Int, resultCode: Int) {
if (requestCode == REQUEST_DEVICE_CREDENTIAL) {
if (resultCode == Activity.RESULT_OK) {
advancedUnlockCallback?.onAuthenticationSucceeded()
@@ -387,14 +389,10 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@RequiresApi(api = Build.VERSION_CODES.M)
fun isDeviceSecure(context: Context): Boolean {
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
keyguardManager?.isDeviceSecure ?: false
} else {
keyguardManager?.isKeyguardSecure ?: false
}
return keyguardManager?.isDeviceSecure ?: false
}
@RequiresApi(api = Build.VERSION_CODES.M)
@@ -418,7 +416,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
)
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@RequiresApi(api = Build.VERSION_CODES.M)
fun deviceCredentialUnlockSupported(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
@@ -428,13 +426,9 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
isDeviceSecure
} else {
isKeyguardSecure
}
return isDeviceSecure
}
}
return false