mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: cipher call #2105
This commit is contained in:
@@ -235,7 +235,7 @@ class MainCredentialActivity : DatabaseModeActivity() {
|
||||
mDeviceUnlockViewModel.uiState.collect { uiState ->
|
||||
// New value received
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
uiState.cipherCredentialRequired?.let { cipher ->
|
||||
uiState.credentialRequiredCipher?.let { cipher ->
|
||||
mDeviceUnlockViewModel.encryptCredential(
|
||||
credential = getCredentialForEncryption(),
|
||||
cipher = cipher
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
@@ -52,7 +53,6 @@ import com.kunzisoft.keepass.viewmodels.DeviceUnlockViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.Executors
|
||||
import javax.crypto.Cipher
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
class DeviceUnlockFragment: Fragment() {
|
||||
@@ -66,43 +66,19 @@ class DeviceUnlockFragment: Fragment() {
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
|
||||
private var mDeviceCredentialEncryptionResultLauncher = registerForActivityResult(
|
||||
private var mDeviceCredentialResultLauncher: ActivityResultLauncher<Intent>? = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
// TODO onEncryptionPromptSucceeded()
|
||||
mDeviceUnlockViewModel.onAuthenticationSucceeded(result)
|
||||
} else {
|
||||
setAuthenticationFailed()
|
||||
}
|
||||
}
|
||||
|
||||
private var mDeviceCredentialDecryptionResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
// TODO onDecryptionPromptSucceeded()
|
||||
} else {
|
||||
setAuthenticationFailed()
|
||||
}
|
||||
}
|
||||
|
||||
private var encryptionAuthenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
private var biometricAuthenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
onEncryptionPromptSucceeded(result.cryptoObject?.cipher)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
setAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
setAuthenticationError(errorCode, errString)
|
||||
}
|
||||
}
|
||||
|
||||
private var decryptionAuthenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
onDecryptionPromptSucceeded(result.cryptoObject?.cipher)
|
||||
mDeviceUnlockViewModel.onAuthenticationSucceeded(result)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
@@ -143,6 +119,13 @@ class DeviceUnlockFragment: Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Init device unlock prompt
|
||||
mBiometricPrompt = BiometricPrompt(
|
||||
this@DeviceUnlockFragment,
|
||||
Executors.newSingleThreadExecutor(),
|
||||
biometricAuthenticationCallback
|
||||
)
|
||||
|
||||
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
@@ -168,7 +151,7 @@ class DeviceUnlockFragment: Fragment() {
|
||||
mDeviceUnlockViewModel.checkUnlockAvailability()
|
||||
}
|
||||
|
||||
fun closeBiometricPrompt() {
|
||||
fun destroyBiometricPrompt() {
|
||||
mBiometricPrompt?.cancelAuthentication()
|
||||
mBiometricPrompt = null
|
||||
}
|
||||
@@ -199,18 +182,6 @@ class DeviceUnlockFragment: Fragment() {
|
||||
state: DeviceUnlockPromptMode
|
||||
) {
|
||||
cryptoPrompt?.let {
|
||||
// Init advanced unlock prompt
|
||||
mBiometricPrompt = BiometricPrompt(
|
||||
this@DeviceUnlockFragment,
|
||||
Executors.newSingleThreadExecutor(),
|
||||
when (cryptoPrompt.type) {
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION ->
|
||||
encryptionAuthenticationCallback
|
||||
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION ->
|
||||
decryptionAuthenticationCallback
|
||||
}
|
||||
)
|
||||
when (state) {
|
||||
DeviceUnlockPromptMode.IDLE -> {}
|
||||
DeviceUnlockPromptMode.SHOW -> {
|
||||
@@ -218,7 +189,7 @@ class DeviceUnlockFragment: Fragment() {
|
||||
mDeviceUnlockViewModel.promptShown()
|
||||
}
|
||||
DeviceUnlockPromptMode.CLOSE -> {
|
||||
closeBiometricPrompt()
|
||||
destroyBiometricPrompt()
|
||||
mDeviceUnlockViewModel.biometricPromptClosed()
|
||||
}
|
||||
}
|
||||
@@ -234,35 +205,27 @@ class DeviceUnlockFragment: Fragment() {
|
||||
} ?: ""
|
||||
|
||||
if (cryptoPrompt.isBiometricOperation) {
|
||||
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(promptTitle)
|
||||
if (promptDescription.isNotEmpty())
|
||||
setDescription(promptDescription)
|
||||
setConfirmationRequired(false)
|
||||
if (isDeviceCredentialBiometricOperation(context)) {
|
||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
||||
} else {
|
||||
setNegativeButtonText(getString(android.R.string.cancel))
|
||||
}
|
||||
}.build()
|
||||
mBiometricPrompt?.authenticate(
|
||||
promptInfoExtractCredential,
|
||||
BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(promptTitle)
|
||||
if (promptDescription.isNotEmpty())
|
||||
setDescription(promptDescription)
|
||||
setConfirmationRequired(false)
|
||||
if (isDeviceCredentialBiometricOperation(context)) {
|
||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
||||
} else {
|
||||
setNegativeButtonText(getString(android.R.string.cancel))
|
||||
}
|
||||
}.build(),
|
||||
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
|
||||
}
|
||||
else if (cryptoPrompt.isDeviceCredentialOperation) {
|
||||
} else if (cryptoPrompt.isDeviceCredentialOperation) {
|
||||
context?.let { context ->
|
||||
val keyGuardManager = ContextCompat.getSystemService(
|
||||
context,
|
||||
KeyguardManager::class.java
|
||||
)
|
||||
@Suppress("DEPRECATION")
|
||||
when (cryptoPrompt.type) {
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION ->
|
||||
mDeviceCredentialEncryptionResultLauncher
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION ->
|
||||
mDeviceCredentialDecryptionResultLauncher
|
||||
}.launch(
|
||||
keyGuardManager?.createConfirmDeviceCredentialIntent(
|
||||
mDeviceCredentialResultLauncher?.launch(
|
||||
ContextCompat.getSystemService(
|
||||
context,
|
||||
KeyguardManager::class.java
|
||||
)?.createConfirmDeviceCredentialIntent(
|
||||
promptTitle,
|
||||
promptDescription
|
||||
)
|
||||
@@ -271,7 +234,7 @@ class DeviceUnlockFragment: Fragment() {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to open prompt", e)
|
||||
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
|
||||
mDeviceUnlockViewModel.setException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,19 +353,8 @@ class DeviceUnlockFragment: Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDecryptionPromptSucceeded(cipher: Cipher?) {
|
||||
mDeviceUnlockViewModel.decryptCredential(cipher)
|
||||
mBiometricPrompt = null
|
||||
}
|
||||
|
||||
private fun onEncryptionPromptSucceeded(cipher: Cipher?) {
|
||||
mDeviceUnlockViewModel.retrieveCredentialForEncryption(cipher)
|
||||
mBiometricPrompt = null
|
||||
}
|
||||
|
||||
private fun setAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||
mBiometricPrompt = null
|
||||
when (errorCode) {
|
||||
BiometricPrompt.ERROR_CANCELED,
|
||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
|
||||
@@ -410,17 +362,14 @@ class DeviceUnlockFragment: Fragment() {
|
||||
// Ignore negative button
|
||||
}
|
||||
else ->
|
||||
mDeviceUnlockViewModel.setException(
|
||||
SecurityException(errString.toString())
|
||||
)
|
||||
mDeviceUnlockViewModel.setException(SecurityException(errString.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAuthenticationFailed() {
|
||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||
mBiometricPrompt = null
|
||||
mDeviceUnlockViewModel.setException(SecurityException(
|
||||
getString(R.string.advanced_unlock_not_recognized))
|
||||
mDeviceUnlockViewModel.setException(
|
||||
SecurityException(getString(R.string.advanced_unlock_not_recognized))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ package com.kunzisoft.keepass.viewmodels
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockCryptoPrompt
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockCryptoPromptType
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockManager
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockMode
|
||||
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
|
||||
@@ -140,7 +143,6 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
||||
|
||||
fun disconnect() {
|
||||
this.databaseUri = null
|
||||
closeBiometricPrompt()
|
||||
cipherDatabaseListener?.let {
|
||||
cipherDatabaseAction.unregisterDatabaseListener(it)
|
||||
}
|
||||
@@ -159,10 +161,38 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
||||
}
|
||||
}
|
||||
|
||||
fun retrieveCredentialForEncryption(cipher: Cipher?) {
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun onAuthenticationSucceeded(
|
||||
activityResult: ActivityResult
|
||||
) {
|
||||
uiState.value.cryptoPrompt?.let { prompt ->
|
||||
when (prompt.type) {
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION ->
|
||||
retrieveCredentialForEncryption( prompt.cipher)
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION ->
|
||||
decryptCredential( prompt.cipher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun onAuthenticationSucceeded(
|
||||
result: BiometricPrompt.AuthenticationResult
|
||||
) {
|
||||
uiState.value.cryptoPrompt?.type?.let { type ->
|
||||
when (type) {
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION ->
|
||||
retrieveCredentialForEncryption(result.cryptoObject?.cipher)
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION ->
|
||||
decryptCredential(result.cryptoObject?.cipher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun retrieveCredentialForEncryption(cipher: Cipher?) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherCredentialRequired = cipher
|
||||
credentialRequiredCipher = cipher
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -191,11 +221,13 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
setException(e)
|
||||
}
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherCredentialRequired = null
|
||||
)
|
||||
} finally {
|
||||
// Reinit credential storage request
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
credentialRequiredCipher = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +308,7 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
||||
if (allowAutoOpenBiometricPrompt
|
||||
&& PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(getApplication())
|
||||
) {
|
||||
if (uiState.value.cryptoPrompt != null)
|
||||
showPrompt()
|
||||
showPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +413,7 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cryptoPrompt = null,
|
||||
cryptoPromptState = DeviceUnlockPromptMode.SHOW
|
||||
cryptoPromptState = DeviceUnlockPromptMode.IDLE
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -409,7 +440,7 @@ enum class DeviceUnlockPromptMode {
|
||||
data class DeviceUnlockState(
|
||||
val deviceUnlockMode: DeviceUnlockMode = DeviceUnlockMode.BIOMETRIC_UNAVAILABLE,
|
||||
val allowAdvancedUnlockMenu: Boolean = false,
|
||||
val cipherCredentialRequired: Cipher? = null,
|
||||
val credentialRequiredCipher: Cipher? = null,
|
||||
val cipherEncryptDatabase: CipherEncryptDatabase? = null,
|
||||
val cipherDecryptDatabase: CipherDecryptDatabase? = null,
|
||||
val cryptoPrompt: DeviceUnlockCryptoPrompt? = null,
|
||||
|
||||
Reference in New Issue
Block a user