mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Added a dialog to verify the password #2283
This commit is contained in:
@@ -84,6 +84,7 @@ import com.kunzisoft.keepass.view.changeTitleColor
|
||||
import com.kunzisoft.keepass.view.hideByFading
|
||||
import com.kunzisoft.keepass.view.setTransparentNavigationBar
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.view.showError
|
||||
import com.kunzisoft.keepass.viewmodels.EntryViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -214,7 +215,7 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
|
||||
mEntryViewModel.loadEntry(mDatabase, mainEntryId, historyPosition)
|
||||
}
|
||||
} catch (e: ClassCastException) {
|
||||
} catch (_: ClassCastException) {
|
||||
Log.e(TAG, "Unable to retrieve the entry key")
|
||||
}
|
||||
|
||||
@@ -328,6 +329,7 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
when (uIState) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
coordinatorLayout?.showError(uIState.error)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
|
||||
@@ -122,6 +122,7 @@ import com.kunzisoft.keepass.view.applyWindowInsets
|
||||
import com.kunzisoft.keepass.view.hideByFading
|
||||
import com.kunzisoft.keepass.view.setTransparentNavigationBar
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.view.showError
|
||||
import com.kunzisoft.keepass.view.toastError
|
||||
import com.kunzisoft.keepass.view.updateLockPaddingStart
|
||||
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
|
||||
@@ -581,6 +582,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
when (uIState) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
coordinatorLayout?.showError(uIState.error)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
@@ -711,14 +713,6 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
super.onDatabaseActionFinished(database, actionTask, result)
|
||||
|
||||
var entry: Entry? = null
|
||||
try {
|
||||
entry = result.data?.getNewEntry(database)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve entry action for selection", e)
|
||||
}
|
||||
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||
if (result.isSuccess) {
|
||||
@@ -731,6 +725,12 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
// Search not used
|
||||
},
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo ->
|
||||
var entry: Entry? = null
|
||||
try {
|
||||
entry = result.data?.getNewEntry(database)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve entry action for selection", e)
|
||||
}
|
||||
when (typeMode) {
|
||||
TypeMode.DEFAULT -> {}
|
||||
TypeMode.MAGIKEYBOARD -> entry?.let {
|
||||
@@ -749,14 +749,12 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
}
|
||||
)
|
||||
}
|
||||
coordinatorError?.showActionErrorIfNeeded(result)
|
||||
// Reload the group
|
||||
loadGroup()
|
||||
finishNodeAction()
|
||||
}
|
||||
}
|
||||
|
||||
coordinatorError?.showActionErrorIfNeeded(result)
|
||||
|
||||
// Reload the group
|
||||
loadGroup()
|
||||
finishNodeAction()
|
||||
}
|
||||
|
||||
private fun manageIntent(intent: Intent?) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.UriUtil.openUrl
|
||||
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
|
||||
|
||||
|
||||
class CheckDatabaseCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private val userVerificationViewModel: UserVerificationViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val inflater = activity.layoutInflater
|
||||
val rootView = inflater.inflate(R.layout.fragment_check_database_credential, null)
|
||||
builder.setView(rootView)
|
||||
.setPositiveButton(R.string.check) { _, _ ->
|
||||
userVerificationViewModel.checkMainCredential(
|
||||
rootView
|
||||
.findViewById<TextView>(R.id.setup_check_password_edit_text)
|
||||
.text.toString(),
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
userVerificationViewModel.onUserVerificationFailed()
|
||||
dismiss()
|
||||
}
|
||||
rootView.findViewById<View>(R.id.user_verification_information)?.setOnClickListener {
|
||||
activity.openUrl(R.string.user_verification_explanation_url)
|
||||
}
|
||||
return builder.create()
|
||||
}
|
||||
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun getInstance(): CheckDatabaseCredentialDialogFragment {
|
||||
val fragment = CheckDatabaseCredentialDialogFragment()
|
||||
val args = Bundle()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,14 +181,6 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
fun checkMainCredential(mainCredential: MainCredential) {
|
||||
mDatabase?.let { database ->
|
||||
database.fileUri?.let { databaseUri ->
|
||||
mDatabaseViewModel.checkMainCredential(databaseUri, mainCredential)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveDatabase() {
|
||||
mDatabaseViewModel.saveDatabase(save = true)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ package com.kunzisoft.keepass.credentialprovider
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
@@ -13,6 +11,7 @@ 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.CheckDatabaseCredentialDialogFragment
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.utils.getEnumExtra
|
||||
@@ -99,9 +98,7 @@ class UserVerificationHelper {
|
||||
if (isAuthenticatorsAllowed()) {
|
||||
showUserVerificationDeviceCredential(userVerificationViewModel, dataToVerify)
|
||||
} else {
|
||||
showUserVerificationMessage {
|
||||
userVerificationViewModel.onUserVerificationFailed()
|
||||
}
|
||||
showUserVerificationDatabaseCredential(userVerificationViewModel, dataToVerify)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,33 +142,22 @@ class UserVerificationHelper {
|
||||
}
|
||||
}).authenticate(
|
||||
BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.user_verification_required))
|
||||
.setTitle(getString(R.string.user_verification_required_title))
|
||||
.setSubtitle(getString(R.string.user_verification_required_description))
|
||||
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
||||
.setConfirmationRequired(false)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
fun FragmentActivity.showUserVerificationMessage(
|
||||
onActionPerformed: () -> Unit
|
||||
fun FragmentActivity.showUserVerificationDatabaseCredential(
|
||||
userVerificationViewModel: UserVerificationViewModel,
|
||||
dataToVerify: UserVerificationData
|
||||
) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.set_up_user_verification_passkeys_title))
|
||||
.setMessage(getString(R.string.set_up_user_verification_passkeys_description))
|
||||
.setPositiveButton(resources.getString(R.string.set_up_user_verification)
|
||||
) { _, _ ->
|
||||
startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
onActionPerformed()
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.cancel)
|
||||
) { _, _ ->
|
||||
onActionPerformed()
|
||||
}
|
||||
.setOnDismissListener {
|
||||
onActionPerformed()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
userVerificationViewModel.dataToVerify = dataToVerify
|
||||
CheckDatabaseCredentialDialogFragment
|
||||
.getInstance()
|
||||
.show(this.supportFragmentManager, "checkDatabaseCredentialDialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewMod
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.model.AppOrigin
|
||||
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.settings.PreferencesUtil.isPasskeyUserVerificationPreferred
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
@@ -189,6 +188,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
userVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
toastError(uiState.error)
|
||||
passkeyLauncherViewModel.cancelResult()
|
||||
userVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
@@ -229,17 +229,6 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
// TODO When auto save is enabled, WARNING filter by the calling activity
|
||||
// passkeyLauncherViewModel.autoSelectPasskey(result, database)
|
||||
}
|
||||
ACTION_DATABASE_CHECK_CREDENTIAL_TASK -> {
|
||||
if (result.isSuccess) {
|
||||
userVerificationViewModel.onUserVerificationSucceeded(
|
||||
UserVerificationData(database)
|
||||
)
|
||||
} else {
|
||||
userVerificationViewModel.onUserVerificationFailed(
|
||||
UserVerificationData(database)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
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_CHECK_CREDENTIAL_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_GROUP_TASK
|
||||
@@ -239,7 +238,7 @@ class DatabaseTaskProvider(
|
||||
|
||||
try {
|
||||
context.unregisterReceiver(databaseTaskBroadcastReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// If receiver not register, do nothing
|
||||
}
|
||||
}
|
||||
@@ -326,16 +325,6 @@ class DatabaseTaskProvider(
|
||||
}, 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
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.MainCredential
|
||||
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.LoadDatabaseRunnable
|
||||
import com.kunzisoft.keepass.database.action.MergeDatabaseRunnable
|
||||
@@ -349,7 +348,6 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(intent, database)
|
||||
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(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_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent, database)
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent, database)
|
||||
@@ -944,37 +942,6 @@ 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) {
|
||||
// Erase the biometric
|
||||
CipherDatabaseAction.getInstance(this)
|
||||
@@ -1363,7 +1330,6 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
const val ACTION_DATABASE_MERGE_TASK = "ACTION_DATABASE_MERGE_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_CHECK_CREDENTIAL_TASK = "ACTION_DATABASE_CHECK_CREDENTIAL_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_CREATE_ENTRY_TASK = "ACTION_DATABASE_CREATE_ENTRY_TASK"
|
||||
|
||||
@@ -238,15 +238,13 @@ fun View.updateLockPaddingStart() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.toastError(e: Throwable) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
if (e is LocalizedException)
|
||||
e.getLocalizedMessage(resources)
|
||||
else
|
||||
e.localizedMessage,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
fun Context.toastError(e: Throwable?) {
|
||||
val message = if (e is LocalizedException)
|
||||
e.getLocalizedMessage(resources)
|
||||
else e?.localizedMessage
|
||||
message?.let {
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.showActionErrorIfNeeded(result: ActionRunnable.Result) {
|
||||
@@ -259,6 +257,15 @@ fun Context.showActionErrorIfNeeded(result: ActionRunnable.Result) {
|
||||
}
|
||||
}
|
||||
|
||||
fun CoordinatorLayout.showError(error: Throwable?) {
|
||||
val message = if (error is LocalizedException) {
|
||||
error.getLocalizedMessage(resources) ?: error.message
|
||||
} else error?.message
|
||||
message?.let {
|
||||
Snackbar.make(this, message, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
|
||||
fun CoordinatorLayout.showActionErrorIfNeeded(result: ActionRunnable.Result) {
|
||||
if (!result.isSuccess) {
|
||||
result.exception?.getLocalizedMessage(resources)?.let { errorMessage ->
|
||||
|
||||
@@ -133,13 +133,6 @@ class DatabaseViewModel(application: Application): AndroidViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkMainCredential(
|
||||
databaseUri: Uri,
|
||||
mainCredential: MainCredential
|
||||
) {
|
||||
mDatabaseTaskProvider.startDatabaseCheckCredential(databaseUri, mainCredential)
|
||||
}
|
||||
|
||||
fun saveDatabase(save: Boolean, saveToUri: Uri? = null) {
|
||||
mDatabaseTaskProvider.startDatabaseSave(save, saveToUri)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
|
||||
import com.kunzisoft.keepass.database.element.MasterCredential.CREATOR.getCheckKey
|
||||
import com.kunzisoft.keepass.database.exception.InvalidCredentialsDatabaseException
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -13,12 +15,28 @@ class UserVerificationViewModel: ViewModel() {
|
||||
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = mUiState
|
||||
|
||||
var dataToVerify: UserVerificationData = UserVerificationData()
|
||||
|
||||
fun checkMainCredential(checkString: String) {
|
||||
// Check the password part
|
||||
if (dataToVerify.database?.checkKey(getCheckKey(checkString)) == true)
|
||||
onUserVerificationSucceeded(dataToVerify)
|
||||
else {
|
||||
onUserVerificationFailed(dataToVerify, InvalidCredentialsDatabaseException())
|
||||
}
|
||||
dataToVerify = UserVerificationData()
|
||||
}
|
||||
|
||||
fun onUserVerificationSucceeded(dataToVerify: UserVerificationData) {
|
||||
mUiState.value = UIState.OnUserVerificationSucceeded(dataToVerify)
|
||||
}
|
||||
|
||||
fun onUserVerificationFailed(dataToVerify: UserVerificationData = UserVerificationData()) {
|
||||
mUiState.value = UIState.OnUserVerificationCanceled(dataToVerify)
|
||||
fun onUserVerificationFailed(
|
||||
dataToVerify: UserVerificationData = UserVerificationData(),
|
||||
error: Throwable? = null
|
||||
) {
|
||||
this.dataToVerify = dataToVerify
|
||||
mUiState.value = UIState.OnUserVerificationCanceled(dataToVerify, error)
|
||||
}
|
||||
|
||||
fun onUserVerificationReceived() {
|
||||
@@ -28,7 +46,10 @@ class UserVerificationViewModel: ViewModel() {
|
||||
sealed class UIState {
|
||||
object Loading: UIState()
|
||||
data class OnUserVerificationSucceeded(val dataToVerify: UserVerificationData): UIState()
|
||||
data class OnUserVerificationCanceled(val dataToVerify: UserVerificationData): UIState()
|
||||
data class OnUserVerificationCanceled(
|
||||
val dataToVerify: UserVerificationData,
|
||||
val error: Throwable?
|
||||
): UIState()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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/>.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:padding="@dimen/default_margin"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
tools:targetApi="o">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/user_verification_information"
|
||||
android:text="@string/user_verification_required_title"
|
||||
style="@style/KeepassDXStyle.Title"/>
|
||||
<ImageView
|
||||
android:id="@+id/user_verification_information"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:src="@drawable/ic_info_white_24dp"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"
|
||||
android:contentDescription="@string/content_description_user_verification_information"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/user_verification_required_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginRight="@dimen/card_view_margin_horizontal"
|
||||
android:text="@string/user_verification_database_credential"
|
||||
android:textColor="?attr/colorSecondary"/>
|
||||
|
||||
<!-- Password -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/setup_check_password_input_layout"
|
||||
android:layout_margin="@dimen/card_view_margin_horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/colorSecondary">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/setup_check_password_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:focusedByDefault="true"
|
||||
android:maxLength="4"
|
||||
android:inputType="textPassword"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:hint="@string/first_chars" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -45,6 +45,7 @@
|
||||
<string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string>
|
||||
<string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string>
|
||||
<string name="passkeys_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Passkeys</string>
|
||||
<string name="user_verification_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Passkeys#UserVerification</string>
|
||||
<string name="autofill_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Autofill</string>
|
||||
<string name="file_manager_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/File-Manager-and-Sync</string>
|
||||
<string name="html_rose">--,--`--,{@</string>
|
||||
|
||||
@@ -775,8 +775,10 @@
|
||||
<string name="passkey_backup_state">Passkey Backup State</string>
|
||||
<string name="error_passkey_result">Unable to return the passkey</string>
|
||||
<string name="error_passkey_credential_id">No passkey found with relying party %1$s and credentialIds %2$s</string>
|
||||
<string name="user_verification_required">User verification required</string>
|
||||
<string name="set_up_user_verification">Set up user verification</string>
|
||||
<string name="set_up_user_verification_passkeys_title">Set up user verification to use passkeys</string>
|
||||
<string name="set_up_user_verification_passkeys_description">To use passkeys, make sure you have a device screen lock set up</string>
|
||||
<string name="content_description_user_verification_information">User verification info</string>
|
||||
<string name="user_verification_required_title">User Verification</string>
|
||||
<string name="user_verification_required_description">User verification is required to use and edit passkeys</string>
|
||||
<string name="user_verification_database_credential">Enter the first four characters of your database password</string>
|
||||
<string name="first_chars">First chars</string>
|
||||
<string name="check">Check</string>
|
||||
</resources>
|
||||
@@ -288,8 +288,7 @@
|
||||
|
||||
<!-- Dialog -->
|
||||
<style name="KeepassDXStyle.Light.Dialog" parent="ThemeOverlay.Material3.Dialog.Alert">
|
||||
<item name="android:windowBackground">?attr/dialogBackgroundColor</item>
|
||||
<item name="background">?attr/dialogBackgroundColor</item>
|
||||
<item name="android:colorBackground">?attr/dialogBackgroundColor</item>
|
||||
<item name="android:windowSoftInputMode">adjustResize</item>
|
||||
<item name="dialogCornerRadius">@dimen/dialog_radius</item>
|
||||
<item name="buttonBarNegativeButtonStyle">@style/KeepassDXStyle.Light.Dialog.NegativeButtonStyle</item>
|
||||
@@ -303,8 +302,7 @@
|
||||
</style>
|
||||
|
||||
<style name="KeepassDXStyle.Night.Dialog" parent="ThemeOverlay.Material3.Dialog.Alert">
|
||||
<item name="android:windowBackground">?attr/dialogBackgroundColor</item>
|
||||
<item name="background">?attr/dialogBackgroundColor</item>
|
||||
<item name="android:colorBackground">?attr/dialogBackgroundColor</item>
|
||||
<item name="android:windowSoftInputMode">adjustResize</item>
|
||||
<item name="dialogCornerRadius">@dimen/dialog_radius</item>
|
||||
<item name="buttonBarNegativeButtonStyle">@style/KeepassDXStyle.Night.Dialog.NegativeButtonStyle</item>
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.kunzisoft.keepass.database.exception.DatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseInputException
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
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.SignatureDatabaseException
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||
@@ -391,6 +390,9 @@ open class Database {
|
||||
val transformSeed: ByteArray?
|
||||
get() = mDatabaseKDB?.transformSeed ?: mDatabaseKDBX?.transformSeed
|
||||
|
||||
private val checkKey: ByteArray
|
||||
get() = mDatabaseKDB?.checkKey ?: mDatabaseKDBX?.checkKey ?: ByteArray(32)
|
||||
|
||||
var rootGroup: Group?
|
||||
get() {
|
||||
mDatabaseKDB?.rootGroup?.let {
|
||||
@@ -619,51 +621,11 @@ 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)
|
||||
}
|
||||
/**
|
||||
* Check if the key is valid
|
||||
*/
|
||||
fun checkKey(key: ByteArray): Boolean {
|
||||
return checkKey.contentEquals(key)
|
||||
}
|
||||
|
||||
fun isMergeDataAllowed(): Boolean {
|
||||
@@ -1268,7 +1230,7 @@ open class Database {
|
||||
}
|
||||
|
||||
fun undoRecycle(entry: Entry, parent: Group) {
|
||||
recycleBin?.let { it ->
|
||||
recycleBin?.let {
|
||||
removeEntryFrom(entry, it)
|
||||
}
|
||||
addEntryTo(entry, parent)
|
||||
|
||||
@@ -64,6 +64,10 @@ data class MasterCredential(
|
||||
return 0
|
||||
}
|
||||
|
||||
fun getCheckKey(): ByteArray {
|
||||
return getCheckKey(password)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@@ -95,6 +99,15 @@ data class MasterCredential(
|
||||
|
||||
private val TAG = MasterCredential::class.java.simpleName
|
||||
|
||||
fun getCheckKey(password: String?): ByteArray {
|
||||
return retrievePasswordKey(
|
||||
try {
|
||||
password?.substring(0, 3) ?: ""
|
||||
} catch (_: Exception) { "" },
|
||||
Charsets.UTF_8
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrievePasswordKey(
|
||||
key: String,
|
||||
@@ -102,7 +115,7 @@ data class MasterCredential(
|
||||
): ByteArray {
|
||||
val bKey: ByteArray = try {
|
||||
key.toByteArray(encoding)
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
key.toByteArray()
|
||||
}
|
||||
return HashManager.hashSha256(bKey)
|
||||
@@ -128,7 +141,7 @@ data class MasterCredential(
|
||||
32 -> return keyFileData
|
||||
64 -> try {
|
||||
return Hex.decodeHex(String(keyFileData).toCharArray())
|
||||
} catch (ignoredException: Exception) {
|
||||
} catch (_: Exception) {
|
||||
// Key is not base 64, treat it as binary data
|
||||
}
|
||||
}
|
||||
@@ -151,7 +164,7 @@ data class MasterCredential(
|
||||
// Disable certain unsecure XML-Parsing DocumentBuilderFactory features
|
||||
try {
|
||||
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
|
||||
} catch (e : ParserConfigurationException) {
|
||||
} catch (_ : ParserConfigurationException) {
|
||||
Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
|
||||
}
|
||||
|
||||
@@ -187,7 +200,7 @@ data class MasterCredential(
|
||||
xmlKeyFileVersion = versionText.toFloat()
|
||||
Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
|
||||
Log.e(TAG, "XML Keyfile version cannot be read : $versionText", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,7 +248,7 @@ data class MasterCredential(
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -36,7 +36,7 @@ import com.kunzisoft.keepass.database.exception.EmptyKeyDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.HardwareKeyDatabaseException
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
|
||||
@@ -158,6 +158,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
} else {
|
||||
this.masterKey = passwordBytes ?: keyFileBytes ?: byteArrayOf(0)
|
||||
}
|
||||
|
||||
// Build check key
|
||||
this.checkKey = masterCredential.getCheckKey()
|
||||
}
|
||||
|
||||
override fun createGroup(): GroupKDB {
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
@@ -28,7 +27,12 @@ import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.CompositeKey
|
||||
import com.kunzisoft.keepass.database.element.CustomData
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||
import com.kunzisoft.keepass.database.element.MasterCredential
|
||||
import com.kunzisoft.keepass.database.element.Tags
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
@@ -36,7 +40,11 @@ import com.kunzisoft.keepass.database.element.entry.FieldReferencesEngine
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.*
|
||||
import com.kunzisoft.keepass.database.element.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
||||
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
||||
import com.kunzisoft.keepass.database.element.template.Template
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateEngineCompatible
|
||||
@@ -51,7 +59,8 @@ import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import java.util.Arrays
|
||||
import java.util.UUID
|
||||
import javax.crypto.Mac
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -253,6 +262,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
keyFileBytes,
|
||||
hardwareKeyBytes
|
||||
)
|
||||
|
||||
// Build check key
|
||||
this.checkKey = masterCredential.getCheckKey()
|
||||
}
|
||||
|
||||
@Throws(DatabaseOutputException::class)
|
||||
@@ -635,7 +647,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
messageDigest = MessageDigest.getInstance("SHA-512")
|
||||
cmpKey[64] = 1
|
||||
hmacKey = messageDigest.digest(cmpKey)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
} catch (_: NoSuchAlgorithmException) {
|
||||
throw IOException("No SHA-512 implementation")
|
||||
} finally {
|
||||
Arrays.fill(cmpKey, 0.toByte())
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import java.io.InputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
abstract class DatabaseVersioned<
|
||||
GroupId,
|
||||
@@ -58,6 +58,8 @@ abstract class DatabaseVersioned<
|
||||
protected set
|
||||
var transformSeed: ByteArray? = null
|
||||
|
||||
var checkKey = ByteArray(32)
|
||||
|
||||
abstract val version: String
|
||||
abstract val defaultFileExtension: String
|
||||
|
||||
@@ -89,10 +91,6 @@ abstract class DatabaseVersioned<
|
||||
return getGroupIndexes().filter { it != rootGroup }
|
||||
}
|
||||
|
||||
protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
|
||||
return null
|
||||
}
|
||||
|
||||
open fun isValidCredential(password: String?, containsKeyFile: Boolean): Boolean {
|
||||
if (password == null && !containsKeyFile)
|
||||
return false
|
||||
@@ -105,14 +103,14 @@ abstract class DatabaseVersioned<
|
||||
val bKey: ByteArray
|
||||
try {
|
||||
bKey = password.toByteArray(encoding)
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
return false
|
||||
}
|
||||
|
||||
val reEncoded: String
|
||||
try {
|
||||
reEncoded = String(bKey, encoding)
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
return false
|
||||
}
|
||||
return password == reEncoded
|
||||
@@ -121,6 +119,7 @@ abstract class DatabaseVersioned<
|
||||
fun copyMasterKeyFrom(databaseVersioned: DatabaseVersioned<GroupId, EntryId, Group, Entry>) {
|
||||
this.masterKey = databaseVersioned.masterKey
|
||||
this.transformSeed = databaseVersioned.transformSeed
|
||||
this.checkKey = databaseVersioned.checkKey
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user