mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Better biometric exception implementation
This commit is contained in:
@@ -380,7 +380,7 @@ class DeviceUnlockManager(private var appContext: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deviceUnlockError(error: Exception, context: Context): String {
|
fun deviceUnlockError(error: Throwable, context: Context): String {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
&& (error is UnrecoverableKeyException
|
&& (error is UnrecoverableKeyException
|
||||||
|| error is KeyPermanentlyInvalidatedException)) {
|
|| error is KeyPermanentlyInvalidatedException)) {
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ import com.kunzisoft.keepass.model.CipherDecryptDatabase
|
|||||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.CredentialStorage
|
import com.kunzisoft.keepass.model.CredentialStorage
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +40,8 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
private var deviceUnlockManager: DeviceUnlockManager? = null
|
private var deviceUnlockManager: DeviceUnlockManager? = null
|
||||||
private var databaseUri: Uri? = null
|
private var databaseUri: Uri? = null
|
||||||
|
|
||||||
|
private var mCipherJob: Job? = null
|
||||||
|
|
||||||
private var deviceUnlockMode = DeviceUnlockMode.BIOMETRIC_UNAVAILABLE
|
private var deviceUnlockMode = DeviceUnlockMode.BIOMETRIC_UNAVAILABLE
|
||||||
var cryptoPrompt: DeviceUnlockCryptoPrompt? = null
|
var cryptoPrompt: DeviceUnlockCryptoPrompt? = null
|
||||||
private set
|
private set
|
||||||
@@ -95,6 +100,18 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
cipherDatabaseAction.registerDatabaseListener(cipherDatabaseListener)
|
cipherDatabaseAction.registerDatabaseListener(cipherDatabaseListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cancelAndLaunchCipherJob(
|
||||||
|
coroutineExceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||||
|
setException(e)
|
||||||
|
},
|
||||||
|
block: suspend () -> Unit
|
||||||
|
) {
|
||||||
|
mCipherJob?.cancel()
|
||||||
|
mCipherJob = viewModelScope.launch(coroutineExceptionHandler) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun checkConditionToStoreCredential(condition: Boolean) {
|
fun checkConditionToStoreCredential(condition: Boolean) {
|
||||||
isConditionToStoreCredentialVerified = condition
|
isConditionToStoreCredentialVerified = condition
|
||||||
checkUnlockAvailability()
|
checkUnlockAvailability()
|
||||||
@@ -153,12 +170,8 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
private fun changeMode(deviceUnlockMode: DeviceUnlockMode) {
|
private fun changeMode(deviceUnlockMode: DeviceUnlockMode) {
|
||||||
this.deviceUnlockMode = deviceUnlockMode
|
this.deviceUnlockMode = deviceUnlockMode
|
||||||
when (deviceUnlockMode) {
|
when (deviceUnlockMode) {
|
||||||
DeviceUnlockMode.STORE_CREDENTIAL -> {
|
DeviceUnlockMode.STORE_CREDENTIAL -> initEncryptData()
|
||||||
initEncryptData()
|
DeviceUnlockMode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||||
}
|
|
||||||
DeviceUnlockMode.EXTRACT_CREDENTIAL -> {
|
|
||||||
initDecryptData()
|
|
||||||
}
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
_uiState.update { currentState ->
|
_uiState.update { currentState ->
|
||||||
@@ -246,7 +259,7 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
credential: ByteArray,
|
credential: ByteArray,
|
||||||
cipher: Cipher?
|
cipher: Cipher?
|
||||||
) {
|
) {
|
||||||
try {
|
cancelAndLaunchCipherJob {
|
||||||
deviceUnlockManager?.encryptData(
|
deviceUnlockManager?.encryptData(
|
||||||
value = credential,
|
value = credential,
|
||||||
cipher = cipher,
|
cipher = cipher,
|
||||||
@@ -260,26 +273,23 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
this.specParameters = ivSpec
|
this.specParameters = ivSpec
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} ?: setException(UnknownDatabaseLocationException())
|
} ?: throw UnknownDatabaseLocationException()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
}
|
||||||
setException(e)
|
// Reinit credential storage request
|
||||||
} finally {
|
_uiState.update { currentState ->
|
||||||
// Reinit credential storage request
|
currentState.copy(
|
||||||
_uiState.update { currentState ->
|
credentialRequiredCipher = null
|
||||||
currentState.copy(
|
)
|
||||||
credentialRequiredCipher = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decryptCredential(cipher: Cipher?) {
|
fun decryptCredential(cipher: Cipher?) {
|
||||||
// retrieve the encrypted value from preferences
|
// retrieve the encrypted value from preferences
|
||||||
databaseUri?.let { databaseUri ->
|
cancelAndLaunchCipherJob {
|
||||||
cipherDatabase?.encryptedValue?.let { encryptedCredential ->
|
databaseUri?.let { databaseUri ->
|
||||||
try {
|
cipherDatabase?.encryptedValue?.let { encryptedCredential ->
|
||||||
deviceUnlockManager?.decryptData(
|
deviceUnlockManager?.decryptData(
|
||||||
encryptedValue = encryptedCredential,
|
encryptedValue = encryptedCredential,
|
||||||
cipher = cipher,
|
cipher = cipher,
|
||||||
@@ -295,12 +305,10 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
cipherDatabaseAction.resetCipherParameters(databaseUri)
|
cipherDatabaseAction.resetCipherParameters(databaseUri)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} ?: deleteEncryptedDatabaseKey()
|
||||||
setException(e)
|
} ?: run {
|
||||||
}
|
throw UnknownDatabaseLocationException()
|
||||||
} ?: deleteEncryptedDatabaseKey()
|
}
|
||||||
} ?: run {
|
|
||||||
setException(UnknownDatabaseLocationException())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +376,7 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setException(value: Exception?) {
|
fun setException(value: Throwable?) {
|
||||||
_uiState.update { currentState ->
|
_uiState.update { currentState ->
|
||||||
currentState.copy(
|
currentState.copy(
|
||||||
exception = value
|
exception = value
|
||||||
@@ -385,29 +393,25 @@ class DeviceUnlockViewModel(application: Application): AndroidViewModel(applicat
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initEncryptData() {
|
private fun initEncryptData() {
|
||||||
try {
|
cancelAndLaunchCipherJob {
|
||||||
deviceUnlockManager = DeviceUnlockManager(getApplication())
|
deviceUnlockManager = DeviceUnlockManager(getApplication())
|
||||||
deviceUnlockManager?.initEncryptData { cryptoPrompt ->
|
deviceUnlockManager?.initEncryptData { cryptoPrompt ->
|
||||||
onPromptRequested(cryptoPrompt)
|
onPromptRequested(cryptoPrompt)
|
||||||
} ?: setException(Exception("Device unlock manager not initialized"))
|
} ?: throw Exception("Device unlock manager not initialized")
|
||||||
} catch (e: Exception) {
|
|
||||||
setException(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initDecryptData() {
|
private fun initDecryptData() {
|
||||||
try {
|
cancelAndLaunchCipherJob {
|
||||||
cipherDatabase?.let { cipherDb ->
|
cipherDatabase?.let { cipherDb ->
|
||||||
deviceUnlockManager = DeviceUnlockManager(getApplication())
|
deviceUnlockManager = DeviceUnlockManager(getApplication())
|
||||||
deviceUnlockManager?.initDecryptData(cipherDb.specParameters) { cryptoPrompt ->
|
deviceUnlockManager?.initDecryptData(cipherDb.specParameters) { cryptoPrompt ->
|
||||||
onPromptRequested(
|
onPromptRequested(
|
||||||
cryptoPrompt,
|
cryptoPrompt,
|
||||||
autoOpen = isAutoOpenBiometricPromptAllowed
|
autoOpen = isAutoOpenBiometricPromptAllowed
|
||||||
)
|
)
|
||||||
} ?: setException(Exception("Device unlock manager not initialized"))
|
} ?: throw Exception("Device unlock manager not initialized")
|
||||||
} ?: setException(Exception("Cipher database not initialized"))
|
} ?: throw Exception("Cipher database not initialized")
|
||||||
} catch (e: Exception) {
|
|
||||||
setException(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,5 +479,5 @@ data class DeviceUnlockState(
|
|||||||
val cipherDecryptDatabase: CipherDecryptDatabase? = null,
|
val cipherDecryptDatabase: CipherDecryptDatabase? = null,
|
||||||
val cryptoPromptState: DeviceUnlockPromptMode = DeviceUnlockPromptMode.IDLE_CLOSE,
|
val cryptoPromptState: DeviceUnlockPromptMode = DeviceUnlockPromptMode.IDLE_CLOSE,
|
||||||
val autoOpenPrompt: Boolean = false,
|
val autoOpenPrompt: Boolean = false,
|
||||||
val exception: Exception? = null
|
val exception: Throwable? = null
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user