mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Add main credential check method
This commit is contained in:
@@ -157,7 +157,6 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
|
|
||||||
private val mGroupViewModel: GroupViewModel by viewModels()
|
private val mGroupViewModel: GroupViewModel by viewModels()
|
||||||
private val mGroupEditViewModel: GroupEditViewModel by viewModels()
|
private val mGroupEditViewModel: GroupEditViewModel by viewModels()
|
||||||
|
|
||||||
private val mMainCredentialViewModel: MainCredentialViewModel by viewModels()
|
private val mMainCredentialViewModel: MainCredentialViewModel by viewModels()
|
||||||
|
|
||||||
private val mGroupActivityEducation = GroupActivityEducation(this)
|
private val mGroupActivityEducation = GroupActivityEducation(this)
|
||||||
@@ -557,7 +556,7 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
mMainCredentialViewModel.uiState.collect { uiState ->
|
mMainCredentialViewModel.uiState.collect { uiState ->
|
||||||
when (uiState) {
|
when (uiState) {
|
||||||
is MainCredentialViewModel.UIState.Loading -> {}
|
is MainCredentialViewModel.UIState.Loading -> {}
|
||||||
is MainCredentialViewModel.UIState.OnMainCredentialValidated -> {
|
is MainCredentialViewModel.UIState.OnMainCredentialEntered -> {
|
||||||
mergeDatabaseFrom(uiState.databaseUri, uiState.mainCredential)
|
mergeDatabaseFrom(uiState.databaseUri, uiState.mainCredential)
|
||||||
}
|
}
|
||||||
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
|
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
|
||||||
|
|||||||
@@ -181,6 +181,14 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkMainCredential(mainCredential: MainCredential) {
|
||||||
|
mDatabase?.let { database ->
|
||||||
|
database.fileUri?.let { databaseUri ->
|
||||||
|
mDatabaseViewModel.checkMainCredential(databaseUri, mainCredential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun saveDatabase() {
|
fun saveDatabase() {
|
||||||
mDatabaseViewModel.saveDatabase(save = true)
|
mDatabaseViewModel.saveDatabase(save = true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package com.kunzisoft.keepass.credentialprovider
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.biometric.BiometricManager
|
|
||||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
|
||||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
||||||
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
|
||||||
import androidx.biometric.BiometricPrompt
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.activities.dialogs.MainCredentialDialogFragment
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
|
||||||
import com.kunzisoft.keepass.utils.getEnumExtra
|
|
||||||
import com.kunzisoft.keepass.utils.putEnumExtra
|
|
||||||
import com.kunzisoft.keepass.view.toastError
|
|
||||||
|
|
||||||
class UserVerification {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
|
|
||||||
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allowed authenticators for the User Verification
|
|
||||||
*/
|
|
||||||
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the device supports the biometric prompt for User Verification
|
|
||||||
*/
|
|
||||||
fun Context.isAuthenticatorsAllowed(): Boolean {
|
|
||||||
return BiometricManager.from(this)
|
|
||||||
.canAuthenticate(ALLOWED_AUTHENTICATORS) == BIOMETRIC_SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the User Verification to the intent
|
|
||||||
*/
|
|
||||||
fun Intent.addUserVerification(
|
|
||||||
userVerification: UserVerificationRequirement,
|
|
||||||
userVerifiedWithAuth: Boolean
|
|
||||||
) {
|
|
||||||
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
|
|
||||||
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define if the User is verified with authentification from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.getUserVerifiedWithAuth(): Boolean {
|
|
||||||
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the User Verification from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.removeUserVerification() {
|
|
||||||
removeExtra(EXTRA_USER_VERIFICATION)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the User verified with auth from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.removeUserVerifiedWithAuth() {
|
|
||||||
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the User Verification from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.getUserVerificationCondition(): Boolean {
|
|
||||||
return (getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
|
|
||||||
?: UserVerificationRequirement.PREFERRED) == UserVerificationRequirement.REQUIRED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask the user for verification
|
|
||||||
* Ask for the biometric if defined on the device
|
|
||||||
* Ask for the database credential otherwise
|
|
||||||
*/
|
|
||||||
fun FragmentActivity.askUserVerification(
|
|
||||||
database: ContextualDatabase,
|
|
||||||
onVerificationSucceeded: () -> Unit,
|
|
||||||
onVerificationFailed: () -> Unit
|
|
||||||
) {
|
|
||||||
if (this.intent.getUserVerificationCondition()) {
|
|
||||||
if (isAuthenticatorsAllowed()) {
|
|
||||||
BiometricPrompt(
|
|
||||||
this, ContextCompat.getMainExecutor(this),
|
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationError(
|
|
||||||
errorCode: Int,
|
|
||||||
errString: CharSequence
|
|
||||||
) {
|
|
||||||
super.onAuthenticationError(errorCode, errString)
|
|
||||||
when (errorCode) {
|
|
||||||
BiometricPrompt.ERROR_CANCELED,
|
|
||||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
|
|
||||||
BiometricPrompt.ERROR_USER_CANCELED -> {
|
|
||||||
// No operation
|
|
||||||
Log.i("UserVerification", "$errString")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
toastError(SecurityException("Authentication error: $errString"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onVerificationFailed()
|
|
||||||
}
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: BiometricPrompt.AuthenticationResult
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
onVerificationSucceeded()
|
|
||||||
}
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed()
|
|
||||||
toastError(SecurityException(getString(R.string.device_unlock_not_recognized)))
|
|
||||||
onVerificationFailed()
|
|
||||||
}
|
|
||||||
}).authenticate(
|
|
||||||
BiometricPrompt.PromptInfo.Builder()
|
|
||||||
.setTitle(getString(R.string.user_verification_required))
|
|
||||||
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
|
||||||
.setConfirmationRequired(false)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
MainCredentialDialogFragment.getInstance(database.fileUri)
|
|
||||||
.show(
|
|
||||||
supportFragmentManager,
|
|
||||||
MainCredentialDialogFragment.TAG_ASK_MAIN_CREDENTIAL
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package com.kunzisoft.keepass.credentialprovider
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.MainCredentialDialogFragment
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.MainCredentialDialogFragment.Companion.TAG_ASK_MAIN_CREDENTIAL
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
||||||
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
|
import com.kunzisoft.keepass.utils.getEnumExtra
|
||||||
|
import com.kunzisoft.keepass.utils.putEnumExtra
|
||||||
|
import com.kunzisoft.keepass.view.toastError
|
||||||
|
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
|
||||||
|
|
||||||
|
class UserVerificationHelper {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
|
||||||
|
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed authenticators for the User Verification
|
||||||
|
*/
|
||||||
|
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the device supports the biometric prompt for User Verification
|
||||||
|
*/
|
||||||
|
fun Context.isAuthenticatorsAllowed(): Boolean {
|
||||||
|
return BiometricManager.from(this)
|
||||||
|
.canAuthenticate(ALLOWED_AUTHENTICATORS) == BIOMETRIC_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the User Verification to the intent
|
||||||
|
*/
|
||||||
|
fun Intent.addUserVerification(
|
||||||
|
userVerification: UserVerificationRequirement,
|
||||||
|
userVerifiedWithAuth: Boolean
|
||||||
|
) {
|
||||||
|
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
|
||||||
|
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define if the User is verified with authentification from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.getUserVerifiedWithAuth(): Boolean {
|
||||||
|
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the User Verification from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.removeUserVerification() {
|
||||||
|
removeExtra(EXTRA_USER_VERIFICATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the User verified with auth from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.removeUserVerifiedWithAuth() {
|
||||||
|
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the User Verification from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.getUserVerificationCondition(): Boolean {
|
||||||
|
return (getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
|
||||||
|
?: UserVerificationRequirement.PREFERRED) == UserVerificationRequirement.REQUIRED
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the user for verification
|
||||||
|
* Ask for the biometric if defined on the device
|
||||||
|
* Ask for the database credential otherwise
|
||||||
|
*/
|
||||||
|
fun FragmentActivity.askUserVerification(
|
||||||
|
database: ContextualDatabase?,
|
||||||
|
userVerificationViewModel: UserVerificationViewModel
|
||||||
|
) {
|
||||||
|
if (this.intent.getUserVerificationCondition()) {
|
||||||
|
// Important to check the nullable database here
|
||||||
|
database?.let {
|
||||||
|
if (isAuthenticatorsAllowed()) {
|
||||||
|
BiometricPrompt(
|
||||||
|
this, ContextCompat.getMainExecutor(this),
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationError(
|
||||||
|
errorCode: Int,
|
||||||
|
errString: CharSequence
|
||||||
|
) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
when (errorCode) {
|
||||||
|
BiometricPrompt.ERROR_CANCELED,
|
||||||
|
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
|
||||||
|
BiometricPrompt.ERROR_USER_CANCELED -> {
|
||||||
|
// No operation
|
||||||
|
Log.i("UserVerification", "$errString")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
toastError(SecurityException("Authentication error: $errString"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userVerificationViewModel.onUserVerificationFailed(database)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(
|
||||||
|
result: BiometricPrompt.AuthenticationResult
|
||||||
|
) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
userVerificationViewModel.onUserVerificationSucceeded(database)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
toastError(SecurityException(getString(R.string.device_unlock_not_recognized)))
|
||||||
|
userVerificationViewModel.onUserVerificationFailed(database)
|
||||||
|
}
|
||||||
|
}).authenticate(
|
||||||
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(getString(R.string.user_verification_required))
|
||||||
|
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
||||||
|
.setConfirmationRequired(false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// TODO Check fragment
|
||||||
|
var mainCredentialDialogFragment = supportFragmentManager
|
||||||
|
.findFragmentByTag(TAG_ASK_MAIN_CREDENTIAL) as? MainCredentialDialogFragment?
|
||||||
|
if (mainCredentialDialogFragment == null) {
|
||||||
|
mainCredentialDialogFragment = MainCredentialDialogFragment
|
||||||
|
.getInstance(database.fileUri)
|
||||||
|
mainCredentialDialogFragment.show(
|
||||||
|
supportFragmentManager,
|
||||||
|
TAG_ASK_MAIN_CREDENTIAL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userVerificationViewModel.onUserVerificationSucceeded(database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,9 +43,9 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
|
|||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
||||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.UserVerification.Companion.addUserVerification
|
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.addUserVerification
|
||||||
import com.kunzisoft.keepass.credentialprovider.UserVerification.Companion.askUserVerification
|
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.askUserVerification
|
||||||
import com.kunzisoft.keepass.credentialprovider.UserVerification.Companion.getUserVerifiedWithAuth
|
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.getUserVerifiedWithAuth
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
||||||
@@ -55,11 +55,13 @@ import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewMod
|
|||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
import com.kunzisoft.keepass.model.AppOrigin
|
import com.kunzisoft.keepass.model.AppOrigin
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CHECK_CREDENTIAL_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
|
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
|
||||||
import com.kunzisoft.keepass.view.toastError
|
import com.kunzisoft.keepass.view.toastError
|
||||||
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
|
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
|
||||||
|
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -68,6 +70,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
|
|
||||||
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
|
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
|
||||||
private val mainCredentialViewModel: MainCredentialViewModel by viewModels()
|
private val mainCredentialViewModel: MainCredentialViewModel by viewModels()
|
||||||
|
private val userVerificationViewModel: UserVerificationViewModel by viewModels()
|
||||||
|
|
||||||
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||||
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
@@ -172,11 +175,28 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
mainCredentialViewModel.uiState.collect { uiState ->
|
mainCredentialViewModel.uiState.collect { uiState ->
|
||||||
when (uiState) {
|
when (uiState) {
|
||||||
is MainCredentialViewModel.UIState.Loading -> {}
|
is MainCredentialViewModel.UIState.Loading -> {}
|
||||||
is MainCredentialViewModel.UIState.OnMainCredentialValidated -> {
|
is MainCredentialViewModel.UIState.OnMainCredentialEntered -> {
|
||||||
// TODO Pass through UserVerification View Model
|
checkMainCredential(uiState.mainCredential)
|
||||||
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
|
|
||||||
}
|
}
|
||||||
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
|
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
|
||||||
|
userVerificationViewModel.onUserVerificationFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
userVerificationViewModel.uiState.collect { uiState ->
|
||||||
|
when (uiState) {
|
||||||
|
is UserVerificationViewModel.UIState.Loading -> {}
|
||||||
|
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||||
|
passkeyLauncherViewModel.launchActionIfNeeded(
|
||||||
|
userVerified = true,
|
||||||
|
intent = intent,
|
||||||
|
specialMode = mSpecialMode,
|
||||||
|
database = uiState.database
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||||
passkeyLauncherViewModel.cancelResult()
|
passkeyLauncherViewModel.cancelResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,21 +206,11 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
|
|
||||||
override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) {
|
override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) {
|
||||||
super.onUnknownDatabaseRetrieved(database)
|
super.onUnknownDatabaseRetrieved(database)
|
||||||
|
|
||||||
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
|
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
|
||||||
database?.let {
|
askUserVerification(
|
||||||
askUserVerification(
|
database = database,
|
||||||
database = it,
|
userVerificationViewModel = userVerificationViewModel
|
||||||
onVerificationSucceeded = {
|
)
|
||||||
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
|
|
||||||
},
|
|
||||||
onVerificationFailed = {
|
|
||||||
passkeyLauncherViewModel.cancelResult()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
passkeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDatabaseActionFinished(
|
override fun onDatabaseActionFinished(
|
||||||
@@ -214,6 +224,13 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
// TODO When auto save is enabled, WARNING filter by the calling activity
|
// TODO When auto save is enabled, WARNING filter by the calling activity
|
||||||
// passkeyLauncherViewModel.autoSelectPasskey(result, database)
|
// passkeyLauncherViewModel.autoSelectPasskey(result, database)
|
||||||
}
|
}
|
||||||
|
ACTION_DATABASE_CHECK_CREDENTIAL_TASK -> {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
userVerificationViewModel.onUserVerificationSucceeded(database)
|
||||||
|
} else {
|
||||||
|
userVerificationViewModel.onUserVerificationFailed(database)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNod
|
|||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.UserVerification.Companion.getUserVerificationCondition
|
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.getUserVerificationCondition
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
|
||||||
@@ -152,13 +152,14 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchAction(
|
fun launchActionIfNeeded(
|
||||||
userVerified: Boolean,
|
userVerified: Boolean,
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
specialMode: SpecialMode,
|
specialMode: SpecialMode,
|
||||||
|
database: ContextualDatabase?
|
||||||
) {
|
) {
|
||||||
this.mUserVerified = userVerified
|
this.mUserVerified = userVerified
|
||||||
super.launchActionIfNeeded(intent, specialMode, mDatabase)
|
super.launchActionIfNeeded(intent, specialMode, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun launchActionIfNeeded(
|
override fun launchActionIfNeeded(
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
|||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_CHALLENGE_RESPONDED
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_CHALLENGE_RESPONDED
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK
|
||||||
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CHECK_CREDENTIAL_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
||||||
@@ -319,13 +320,22 @@ class DatabaseTaskProvider(
|
|||||||
databaseUri: Uri,
|
databaseUri: Uri,
|
||||||
mainCredential: MainCredential
|
mainCredential: MainCredential
|
||||||
) {
|
) {
|
||||||
|
|
||||||
start(Bundle().apply {
|
start(Bundle().apply {
|
||||||
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||||
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
|
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
|
||||||
}, ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK)
|
}, ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startDatabaseCheckCredential(
|
||||||
|
databaseUri: Uri,
|
||||||
|
mainCredential: MainCredential
|
||||||
|
) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
|
||||||
|
}, ACTION_DATABASE_CHECK_CREDENTIAL_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
----
|
----
|
||||||
Nodes Actions
|
Nodes Actions
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
|
import com.kunzisoft.keepass.database.MainCredential
|
||||||
|
import com.kunzisoft.keepass.database.exception.DatabaseInputException
|
||||||
|
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
|
||||||
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
|
import com.kunzisoft.keepass.utils.getUriInputStream
|
||||||
|
|
||||||
|
class CheckCredentialDatabaseRunnable(
|
||||||
|
private val context: Context,
|
||||||
|
private val mDatabase: ContextualDatabase,
|
||||||
|
private val mDatabaseUri: Uri,
|
||||||
|
private val mMainCredential: MainCredential,
|
||||||
|
private val mChallengeResponseRetriever: (hardwareKey: HardwareKey, seed: ByteArray?) -> ByteArray,
|
||||||
|
private val progressTaskUpdater: ProgressTaskUpdater?
|
||||||
|
) : ActionRunnable() {
|
||||||
|
|
||||||
|
var afterCheckCredential : ((Result) -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onStartRun() {}
|
||||||
|
|
||||||
|
override fun onActionRun() {
|
||||||
|
try {
|
||||||
|
val contentResolver = context.contentResolver
|
||||||
|
mDatabase.fileUri = mDatabaseUri
|
||||||
|
mDatabase.checkMasterKey(
|
||||||
|
databaseStream = contentResolver.getUriInputStream(mDatabaseUri)
|
||||||
|
?: throw UnknownDatabaseLocationException(),
|
||||||
|
masterCredential = mMainCredential.toMasterCredential(contentResolver),
|
||||||
|
challengeResponseRetriever = mChallengeResponseRetriever,
|
||||||
|
progressTaskUpdater = progressTaskUpdater
|
||||||
|
)
|
||||||
|
} catch (e: DatabaseInputException) {
|
||||||
|
setError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinishRun() {
|
||||||
|
afterCheckCredential?.invoke(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,9 +33,11 @@ import com.kunzisoft.keepass.R
|
|||||||
import com.kunzisoft.keepass.activities.GroupActivity
|
import com.kunzisoft.keepass.activities.GroupActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
import com.kunzisoft.keepass.database.MainCredential
|
import com.kunzisoft.keepass.database.MainCredential
|
||||||
import com.kunzisoft.keepass.database.ProgressMessage
|
import com.kunzisoft.keepass.database.ProgressMessage
|
||||||
|
import com.kunzisoft.keepass.database.action.CheckCredentialDatabaseRunnable
|
||||||
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
|
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
|
||||||
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
|
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
|
||||||
import com.kunzisoft.keepass.database.action.MergeDatabaseRunnable
|
import com.kunzisoft.keepass.database.action.MergeDatabaseRunnable
|
||||||
@@ -61,7 +63,6 @@ import com.kunzisoft.keepass.database.element.node.Node
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
|
|
||||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
@@ -348,6 +349,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(intent, database)
|
ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(intent, database)
|
||||||
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
|
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
|
||||||
ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK -> buildDatabaseAssignCredentialActionTask(intent, database)
|
ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK -> buildDatabaseAssignCredentialActionTask(intent, database)
|
||||||
|
ACTION_DATABASE_CHECK_CREDENTIAL_TASK -> buildDatabaseCheckCredentialActionTask(intent, database)
|
||||||
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
|
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
|
||||||
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent, database)
|
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent, database)
|
||||||
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent, database)
|
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent, database)
|
||||||
@@ -917,7 +919,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
|
|
||||||
private fun buildDatabaseAssignCredentialActionTask(
|
private fun buildDatabaseAssignCredentialActionTask(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
database: ContextualDatabase,
|
database: ContextualDatabase
|
||||||
): ActionRunnable? {
|
): ActionRunnable? {
|
||||||
return if (intent.hasExtra(DATABASE_URI_KEY)
|
return if (intent.hasExtra(DATABASE_URI_KEY)
|
||||||
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
||||||
@@ -942,6 +944,37 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildDatabaseCheckCredentialActionTask(
|
||||||
|
intent: Intent,
|
||||||
|
database: ContextualDatabase
|
||||||
|
): ActionRunnable? {
|
||||||
|
return if (intent.hasExtra(DATABASE_URI_KEY)
|
||||||
|
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
||||||
|
) {
|
||||||
|
val databaseUri: Uri = intent.getParcelableExtraCompat(DATABASE_URI_KEY) ?: return null
|
||||||
|
val mainCredential: MainCredential =
|
||||||
|
intent.getParcelableExtraCompat(MAIN_CREDENTIAL_KEY) ?: MainCredential()
|
||||||
|
CheckCredentialDatabaseRunnable(
|
||||||
|
context = this,
|
||||||
|
mDatabase = database,
|
||||||
|
mDatabaseUri = databaseUri,
|
||||||
|
mMainCredential = mainCredential,
|
||||||
|
mChallengeResponseRetriever = { hardwareKey, seed ->
|
||||||
|
retrieveResponseFromChallenge(hardwareKey, seed)
|
||||||
|
},
|
||||||
|
progressTaskUpdater = this
|
||||||
|
).apply {
|
||||||
|
afterCheckCredential = {
|
||||||
|
result.data = Bundle().apply {
|
||||||
|
putParcelable(DATABASE_URI_KEY, databaseUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun eraseCredentials(databaseUri: Uri) {
|
private fun eraseCredentials(databaseUri: Uri) {
|
||||||
// Erase the biometric
|
// Erase the biometric
|
||||||
CipherDatabaseAction.getInstance(this)
|
CipherDatabaseAction.getInstance(this)
|
||||||
@@ -1330,6 +1363,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
const val ACTION_DATABASE_MERGE_TASK = "ACTION_DATABASE_MERGE_TASK"
|
const val ACTION_DATABASE_MERGE_TASK = "ACTION_DATABASE_MERGE_TASK"
|
||||||
const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
|
const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
|
||||||
const val ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK = "ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK"
|
const val ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK = "ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK"
|
||||||
|
const val ACTION_DATABASE_CHECK_CREDENTIAL_TASK = "ACTION_DATABASE_CHECK_CREDENTIAL_TASK"
|
||||||
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
|
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
|
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
|
||||||
const val ACTION_DATABASE_CREATE_ENTRY_TASK = "ACTION_DATABASE_CREATE_ENTRY_TASK"
|
const val ACTION_DATABASE_CREATE_ENTRY_TASK = "ACTION_DATABASE_CREATE_ENTRY_TASK"
|
||||||
|
|||||||
@@ -133,6 +133,13 @@ class DatabaseViewModel(application: Application): AndroidViewModel(application)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkMainCredential(
|
||||||
|
databaseUri: Uri,
|
||||||
|
mainCredential: MainCredential
|
||||||
|
) {
|
||||||
|
mDatabaseTaskProvider.startDatabaseCheckCredential(databaseUri, mainCredential)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveDatabase(save: Boolean, saveToUri: Uri? = null) {
|
fun saveDatabase(save: Boolean, saveToUri: Uri? = null) {
|
||||||
mDatabaseTaskProvider.startDatabaseSave(save, saveToUri)
|
mDatabaseTaskProvider.startDatabaseSave(save, saveToUri)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class MainCredentialViewModel: ViewModel() {
|
|||||||
databaseUri: Uri,
|
databaseUri: Uri,
|
||||||
mainCredential: MainCredential
|
mainCredential: MainCredential
|
||||||
) {
|
) {
|
||||||
mUiState.value = UIState.OnMainCredentialValidated(databaseUri, mainCredential)
|
mUiState.value = UIState.OnMainCredentialEntered(databaseUri, mainCredential)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelMainCredential(
|
fun cancelMainCredential(
|
||||||
@@ -30,7 +30,7 @@ class MainCredentialViewModel: ViewModel() {
|
|||||||
|
|
||||||
sealed class UIState {
|
sealed class UIState {
|
||||||
object Loading: UIState()
|
object Loading: UIState()
|
||||||
data class OnMainCredentialValidated(
|
data class OnMainCredentialEntered(
|
||||||
val databaseUri: Uri,
|
val databaseUri: Uri,
|
||||||
val mainCredential: MainCredential
|
val mainCredential: MainCredential
|
||||||
): UIState()
|
): UIState()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.kunzisoft.keepass.viewmodels
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for the User Verification
|
||||||
|
*/
|
||||||
|
class UserVerificationViewModel: ViewModel() {
|
||||||
|
|
||||||
|
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||||
|
val uiState: StateFlow<UIState> = mUiState
|
||||||
|
|
||||||
|
fun onUserVerificationSucceeded(database: ContextualDatabase?) {
|
||||||
|
mUiState.value = UIState.OnUserVerificationSucceeded(database)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUserVerificationFailed(database: ContextualDatabase? = null) {
|
||||||
|
mUiState.value = UIState.OnUserVerificationCanceled(database)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class UIState {
|
||||||
|
object Loading: UIState()
|
||||||
|
data class OnUserVerificationSucceeded(
|
||||||
|
val database: ContextualDatabase?
|
||||||
|
): UIState()
|
||||||
|
data class OnUserVerificationCanceled(
|
||||||
|
val database: ContextualDatabase?
|
||||||
|
): UIState()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.exception.DatabaseException
|
|||||||
import com.kunzisoft.keepass.database.exception.DatabaseInputException
|
import com.kunzisoft.keepass.database.exception.DatabaseInputException
|
||||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||||
|
import com.kunzisoft.keepass.database.exception.InvalidCredentialsDatabaseException
|
||||||
import com.kunzisoft.keepass.database.exception.MergeDatabaseKDBException
|
import com.kunzisoft.keepass.database.exception.MergeDatabaseKDBException
|
||||||
import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
|
import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||||
@@ -618,6 +619,53 @@ open class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkMasterKey(
|
||||||
|
databaseStream: InputStream,
|
||||||
|
masterCredential: MasterCredential,
|
||||||
|
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
|
||||||
|
progressTaskUpdater: ProgressTaskUpdater?
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
var masterKey = byteArrayOf()
|
||||||
|
// Read database stream for the first time
|
||||||
|
readDatabaseStream(databaseStream,
|
||||||
|
{ databaseInputStream ->
|
||||||
|
val databaseKDB = DatabaseKDB()
|
||||||
|
DatabaseInputKDB(databaseKDB)
|
||||||
|
.openDatabase(databaseInputStream,
|
||||||
|
progressTaskUpdater
|
||||||
|
) {
|
||||||
|
databaseKDB.deriveMasterKey(
|
||||||
|
masterCredential
|
||||||
|
)
|
||||||
|
}
|
||||||
|
masterKey = databaseKDB.masterKey
|
||||||
|
},
|
||||||
|
{ databaseInputStream ->
|
||||||
|
val databaseKDBX = DatabaseKDBX()
|
||||||
|
DatabaseInputKDBX(databaseKDBX).apply {
|
||||||
|
openDatabase(databaseInputStream,
|
||||||
|
progressTaskUpdater) {
|
||||||
|
databaseKDBX.deriveMasterKey(
|
||||||
|
masterCredential,
|
||||||
|
challengeResponseRetriever
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
masterKey = databaseKDBX.masterKey
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!this.masterKey.contentEquals(masterKey)) {
|
||||||
|
throw InvalidCredentialsDatabaseException()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to check the main credential")
|
||||||
|
if (e is DatabaseInputException)
|
||||||
|
throw e
|
||||||
|
throw DatabaseInputException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isMergeDataAllowed(): Boolean {
|
fun isMergeDataAllowed(): Boolean {
|
||||||
return mDatabaseKDBX != null
|
return mDatabaseKDBX != null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,11 @@ import javax.xml.XMLConstants
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
import javax.xml.parsers.ParserConfigurationException
|
import javax.xml.parsers.ParserConfigurationException
|
||||||
|
|
||||||
data class MasterCredential(var password: String? = null,
|
data class MasterCredential(
|
||||||
var keyFileData: ByteArray? = null,
|
var password: String? = null,
|
||||||
var hardwareKey: HardwareKey? = null): Parcelable {
|
var keyFileData: ByteArray? = null,
|
||||||
|
var hardwareKey: HardwareKey? = null
|
||||||
|
): Parcelable {
|
||||||
|
|
||||||
constructor(parcel: Parcel) : this() {
|
constructor(parcel: Parcel) : this() {
|
||||||
password = parcel.readString()
|
password = parcel.readString()
|
||||||
@@ -94,8 +96,9 @@ data class MasterCredential(var password: String? = null,
|
|||||||
private val TAG = MasterCredential::class.java.simpleName
|
private val TAG = MasterCredential::class.java.simpleName
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun retrievePasswordKey(key: String,
|
fun retrievePasswordKey(
|
||||||
encoding: Charset
|
key: String,
|
||||||
|
encoding: Charset
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
val bKey: ByteArray = try {
|
val bKey: ByteArray = try {
|
||||||
key.toByteArray(encoding)
|
key.toByteArray(encoding)
|
||||||
|
|||||||
Reference in New Issue
Block a user