Merge branch 'develop' into feature/Passkeys

This commit is contained in:
J-Jamet
2025-07-24 20:12:05 +02:00
19 changed files with 449 additions and 244 deletions

View File

@@ -1,3 +1,9 @@
KeePassDX(4.1.3)
* Fix Autofill Registration #2089
* Fix Biometric errors #2081
* Fixed timestamp in copy file #1981 #1983
* Fix Template Email #1986
KeePassDX(4.1.2)
* Fix URL search #1940 #1946 #2003 #2040 #2044
* Fix Autofill popup #2054

View File

@@ -54,7 +54,7 @@ Optional visual styles are accessible after a contribution (and a congratulatory
|--------|--------|---------|
| [Google Play](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free) | ![Google Play Release](https://img.shields.io/endpoint?color=blue&logo=google-play&logoColor=green&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dcom.kunzisoft.keepass.free%26gl%3DUS%26hl%3Den%26l%3DGoogle%2520Play%26m%3D%24version) | Free + [Pro](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro) |
| [F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) | ![F-Droid Version](https://img.shields.io/f-droid/v/com.kunzisoft.keepass.libre?logo=F-Droid&label=F-Droid) | Libre |
| [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.free) | ![IzzyOnDroid Version](https://img.shields.io/endpoint?&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAADAFBMVEUA0////wAA0v8A0v8A0////wD//wAFz/QA0/8A0/8A0/8A0/8A0v///wAA0/8A0/8A0/8A0/8A0//8/gEA0/8A0/8B0/4A0/8A0/8A0/+j5QGAwwIA0//C9yEA0/8A0/8A0/8A0/8A0/8A0/+n4SAA0/8A0/8A0/+o6gCw3lKt7QCv5SC+422b3wC19AC36zAA0/+d1yMA0/8A0/+W2gEA0/+w8ACz8gCKzgG7+QC+9CFLfwkA0/8A0////wAA0/8A0/8A0/8A0/+f2xym3iuHxCGq5BoA1P+m2joI0vONyiCz3mLO7oYA0/8M1Piq3Ei78CbB8EPe8LLj9Ly751G77zWQ1AC96UYC0fi37CL//wAA0/8A0////wD//wCp3jcA0/+j3SGj2i/I72Sx4zHE8FLB8zak1kYeycDI6nRl3qEA0/7V7psA0v6WzTa95mGi2RvB5XkPy9zH5YJ3uwGV1yxVihRLiwdxtQ1ZkAf//wD//wD//wD//wD//wCn5gf//wD//wD//wD//wD//wAA0/+h4A3R6p8A0/+X1w565OD6/ARg237n9csz2vPz+gNt37V/vifO8HW68B/L6ZOCwxXY8KRQsWRzhExAtG/E612a1Rd/pTBpmR9qjysduKVhmxF9mTY51aUozK+CsDSA52T//wD//wAA0////wD//wBJ1JRRxFWjzlxDyXRc0pGT1wCG0CWB3VGUzSTh8h6c0TSr5CCJ5FFxvl6s4H3m8xML0/DA5CvK51EX1N+Y2gSt4Dag3ChE3fax2ki68yO57NF10FRZnUPl88eJxhuCxgCz5EOLwEGf1DFutmahzGW98x0W1PGk3R154MHE6bOn69qv3gy92oG90o+Hn07B7rhCmiyMwECv1nO+0pQfwrCo57xF2daXsVhKrEdenQAduaee1Bsjr42z5D9RoCXy+QNovXpy2Z5MtWDO/TiSukaF3UtE1K6j3B4YwLc5wXlzpyIK0u5zy3uJqg4pu5RTpkZmpVKyAP8A0wBHcExHcEyBUSeEAAABAHRSTlP///9F9wjAAxD7FCEGzBjd08QyEL39abMd6///8P/ZWAnipIv/cC6B//7////////L/1Dz/0D///////86/vYnquY3/v///5T//v///17///////////////84S3QNB/8L/////////////7r/////NP////9l/////wPD4yis/x7Ym2lWSP+em////0n////////v///////////////////7//7pdGN3Urr6/+v/6aT////+//H/o2P/1v+7r7jp4PM/3p4g////g///K///481LxO///v////9w////8v/////9/p3J///a+P9v/5KR/+n///+p/xf//8P//wAAe7FyaAAABCZJREFUSMdj+E8iYKBUgwIHnwQ3N7cEHxcH+///VayoAE0Dh41qR7aBnCIQ8MsJKHH9/99czYYMWlA0cIkJGjMgAKfq//9RNYzIgLcBWYOTiCgDMhDn+B9bh6LebiWyH6L5UZQzONoAHWSHoqEpDkkDsyKqelv1//9rG1HUN9YihZK9AKp6BkG+/6xNqA5ajhSsCkrIipmYGGRa//9vQXVQXSySBnkWJOUMfn5Myuz/G3hR1NdEIUUchwiy+bkTsg4dbW/fu6W/e1c3XMMy5JiOZkFxUFZo74mgKTqaKXu0+2HqVwkja3BH9kFu361JwcHTfPJD4mdfe8ULAdVRyGlJAcVFfg+CQOozZ4XrJ85+JgwBsVXIGriQw5Tp4ZScezd8JiWnBupru30qwJZa+ZAjmWlC8fUZM4qB6kPnLNSPLMWqQQ5ZQ5aOzs1HmamBaQHzFs6y+qAmJCTE8f9/QgKSBg4DJPWc6zVDQkIC09JkZSPD38kukpExFpT4z67uYI/QwCOOCCK/izvu5CWl6AcEWMnKWml7LWbKZfH9/99UkknQHhGsynDz+65eWXv3/JmJrq5eXienVlRUfH/z8VvCf45soKQIH1yDEQsszrp6gwq9C73T87xcXadKl5TkFev4A/2tygmSBqYXqAYJmK+ZuoJydDR1vP09DA0NOy2kpdML81+U/heCpH1JU3jig7lJ5nKOT4i/t6ZHkqGzs4lJmIVHfrj+JR4HqLQSD0yDkCNEpGNn5ix9D03/eJdElTZdKV2TpNOhkwt8YUlNUgimgV0dLMBvf1gz1MolPd5FRcVNSkpDQ8owJeBCDyIhrIDnOD5QcuIU+3/2QKSs9laQ+noNLS0zLWdtqyP7mBAFAw88TwsJgMuJYweBGjYngtWbmeuZOW+bvNQToUFOAlFqOBk4Ov3/L7Z60/aN0p1tUhpa5nqWlub7C3p2I9QzyAghlUvczOz/1fhzPT3XSIfpSmmYAdVbmm1gV0dSz8DSilpUQsqCddIWIA3meuZaJqdMJZEzl6gRqgZIWZAxUdoizERXN8yi5MltcZTChzMaRQM3JNUWHS8rL/+yaPGvMmvr5ywoGoxtkDWwQ+Pb89ycBeWfGSJeL/la+RS1eOPnRtbQKgMRjZg+t8x6PkP273nWQAoFOPAgaeAThKXAmXMrK39Kmr5fsuBlBqoXfJGLe3VbmHjG9Mczi9T//3h7vygXtcDlQtJg44iQiIjIBRbGPO7gghPJy0ZIxT2HOLIUgwxQzsgYrUR350HSIMaJLidhgKY+mw+pflBDrX8E7OGBjPCAPc76gQFSTqAIiYrb/8dRP4CyosJ/rmwU5XIxHMilt4QBJwsSkBMClxOQULBlkRRwEONmR2kJcDGjADX2/+xO8r5iqjExqmLyrWpcPFRta1BfAwCtyN3XpuJ4RgAAAABJRU5ErkJggg==&url=https://apt.izzysoft.de/fdroid/api/v1/shield/com.kunzisoft.keepass.free&label=IzzyOnDroid) | Free |
| [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.free) | ![IzzyOnDroid Version](https://img.shields.io/endpoint?&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAADAFBMVEUA0////wAA0v8A0v8A0////wD//wAFz/QA0/8A0/8A0/8A0/8A0v///wAA0/8A0/8A0/8A0/8A0//8/gEA0/8A0/8B0/4A0/8A0/8A0/+j5QGAwwIA0//C9yEA0/8A0/8A0/8A0/8A0/8A0/+n4SAA0/8A0/8A0/+o6gCw3lKt7QCv5SC+422b3wC19AC36zAA0/+d1yMA0/8A0/+W2gEA0/+w8ACz8gCKzgG7+QC+9CFLfwkA0/8A0////wAA0/8A0/8A0/8A0/+f2xym3iuHxCGq5BoA1P+m2joI0vONyiCz3mLO7oYA0/8M1Piq3Ei78CbB8EPe8LLj9Ly751G77zWQ1AC96UYC0fi37CL//wAA0/8A0////wD//wCp3jcA0/+j3SGj2i/I72Sx4zHE8FLB8zak1kYeycDI6nRl3qEA0/7V7psA0v6WzTa95mGi2RvB5XkPy9zH5YJ3uwGV1yxVihRLiwdxtQ1ZkAf//wD//wD//wD//wD//wCn5gf//wD//wD//wD//wD//wAA0/+h4A3R6p8A0/+X1w565OD6/ARg237n9csz2vPz+gNt37V/vifO8HW68B/L6ZOCwxXY8KRQsWRzhExAtG/E612a1Rd/pTBpmR9qjysduKVhmxF9mTY51aUozK+CsDSA52T//wD//wAA0////wD//wBJ1JRRxFWjzlxDyXRc0pGT1wCG0CWB3VGUzSTh8h6c0TSr5CCJ5FFxvl6s4H3m8xML0/DA5CvK51EX1N+Y2gSt4Dag3ChE3fax2ki68yO57NF10FRZnUPl88eJxhuCxgCz5EOLwEGf1DFutmahzGW98x0W1PGk3R154MHE6bOn69qv3gy92oG90o+Hn07B7rhCmiyMwECv1nO+0pQfwrCo57xF2daXsVhKrEdenQAduaee1Bsjr42z5D9RoCXy+QNovXpy2Z5MtWDO/TiSukaF3UtE1K6j3B4YwLc5wXlzpyIK0u5zy3uJqg4pu5RTpkZmpVKyAP8A0wBHcExHcEyBUSeEAAABAHRSTlP///9F9wjAAxD7FCEGzBjd08QyEL39abMd6///8P/ZWAnipIv/cC6B//7////////L/1Dz/0D///////86/vYnquY3/v///5T//v///17///////////////84S3QNB/8L/////////////7r/////NP////9l/////wPD4yis/x7Ym2lWSP+em////0n////////v///////////////////7//7pdGN3Urr6/+v/6aT////+//H/o2P/1v+7r7jp4PM/3p4g////g///K///481LxO///v////9w////8v/////9/p3J///a+P9v/5KR/+n///+p/xf//8P//wAAe7FyaAAABCZJREFUSMdj+E8iYKBUgwIHnwQ3N7cEHxcH+///VayoAE0Dh41qR7aBnCIQ8MsJKHH9/99czYYMWlA0cIkJGjMgAKfq//9RNYzIgLcBWYOTiCgDMhDn+B9bh6LebiWyH6L5UZQzONoAHWSHoqEpDkkDsyKqelv1//9rG1HUN9YihZK9AKp6BkG+/6xNqA5ajhSsCkrIipmYGGRa//9vQXVQXSySBnkWJOUMfn5Myuz/G3hR1NdEIUUchwiy+bkTsg4dbW/fu6W/e1c3XMMy5JiOZkFxUFZo74mgKTqaKXu0+2HqVwkja3BH9kFu361JwcHTfPJD4mdfe8ULAdVRyGlJAcVFfg+CQOozZ4XrJ85+JgwBsVXIGriQw5Tp4ZScezd8JiWnBupru30qwJZa+ZAjmWlC8fUZM4qB6kPnLNSPLMWqQQ5ZQ5aOzs1HmamBaQHzFs6y+qAmJCTE8f9/QgKSBg4DJPWc6zVDQkIC09JkZSPD38kukpExFpT4z67uYI/QwCOOCCK/izvu5CWl6AcEWMnKWml7LWbKZfH9/99UkknQHhGsynDz+65eWXv3/JmJrq5eXienVlRUfH/z8VvCf45soKQIH1yDEQsszrp6gwq9C73T87xcXadKl5TkFev4A/2tygmSBqYXqAYJmK+ZuoJydDR1vP09DA0NOy2kpdML81+U/heCpH1JU3jig7lJ5nKOT4i/t6ZHkqGzs4lJmIVHfrj+JR4HqLQSD0yDkCNEpGNn5ix9D03/eJdElTZdKV2TpNOhkwt8YUlNUgimgV0dLMBvf1gz1MolPd5FRcVNSkpDQ8owJeBCDyIhrIDnOD5QcuIU+3/2QKSs9laQ+noNLS0zLWdtqyP7mBAFAw88TwsJgMuJYweBGjYngtWbmeuZOW+bvNQToUFOAlFqOBk4Ov3/L7Z60/aN0p1tUhpa5nqWlub7C3p2I9QzyAghlUvczOz/1fhzPT3XSIfpSmmYAdVbmm1gV0dSz8DSilpUQsqCddIWIA3meuZaJqdMJZEzl6gRqgZIWZAxUdoizERXN8yi5MltcZTChzMaRQM3JNUWHS8rL/+yaPGvMmvr5ywoGoxtkDWwQ+Pb89ycBeWfGSJeL/la+RS1eOPnRtbQKgMRjZg+t8x6PkP273nWQAoFOPAgaeAThKXAmXMrK39Kmr5fsuBlBqoXfJGLe3VbmHjG9Mczi9T//3h7vygXtcDlQtJg44iQiIjIBRbGPO7gghPJy0ZIxT2HOLIUgwxQzsgYrUR350HSIMaJLidhgKY+mw+pflBDrX8E7OGBjPCAPc76gQFSTqAIiYrb/8dRP4CyosJ/rmwU5XIxHMilt4QBJwsSkBMClxOQULBlkRRwEONmR2kJcDGjADX2/+xO8r5iqjExqmLyrWpcPFRta1BfAwCtyN3XpuJ4RgAAAABJRU5ErkJggg==&url=https://apt.izzysoft.de/fdroid/api/v1/shield/com.kunzisoft.keepass.free&label=IzzyOnDroid) | Free & [Libre](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.libre) |
| [GitHub](https://github.com/Kunzisoft/KeePassDX/releases) / [Obtainium](https://github.com/ImranR98/Obtainium) | ![GitHub Release](https://img.shields.io/github/v/release/Kunzisoft/KeePassDX?include_prereleases&logo=GitHub&label=GitHub) | Free & Libre |
## Package authenticity from GitHub

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 19
targetSdkVersion 34
versionCode = 134
versionName = "4.1.2"
versionCode = 135
versionName = "4.1.3"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -119,7 +119,7 @@ import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.updateLockPaddingStart
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import com.kunzisoft.keepass.viewmodels.GroupViewModel
import org.joda.time.Instant
import org.joda.time.LocalDateTime
class GroupActivity : DatabaseLockActivity(),
@@ -344,7 +344,7 @@ class GroupActivity : DatabaseLockActivity(),
mExternalFileHelper?.createDocument(
getString(R.string.database_file_name_default) +
"_" +
Instant.now().toString() +
LocalDateTime.now().toString() +
mDatabase?.defaultFileExtension)
}
R.id.menu_lock_all -> {

View File

@@ -32,7 +32,6 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CompoundButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
@@ -43,6 +42,9 @@ import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.commit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
@@ -82,10 +84,11 @@ import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import kotlinx.coroutines.launch
import java.io.FileNotFoundException
class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
class MainCredentialActivity : DatabaseModeActivity() {
// Views
private var toolbar: Toolbar? = null
@@ -165,21 +168,13 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
}
// Listen password checkbox to init advanced unlock and confirmation button
mainCredentialView?.onPasswordChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onKeyFileChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onHardwareKeyChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onConditionToStoreCredentialChanged = { credentialStorage, verified ->
mAdvancedUnlockViewModel.checkUnlockAvailability(
conditionToStoreCredentialVerified = verified
)
// TODO Async by ViewModel
enableConfirmationButton()
}
// Observe if default database
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
@@ -227,6 +222,27 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey)
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mAdvancedUnlockViewModel.uiState.collect { uiState ->
// New value received
if (uiState.isCredentialRequired) {
mAdvancedUnlockViewModel.provideCredentialForEncryption(
getCredentialForEncryption()
)
}
uiState.cipherEncryptDatabase?.let { cipherEncryptDatabase ->
onCredentialEncrypted(cipherEncryptDatabase)
mAdvancedUnlockViewModel.consumeCredentialEncrypted()
}
uiState.cipherDecryptDatabase?.let { cipherDecryptDatabase ->
onCredentialDecrypted(cipherDecryptDatabase)
mAdvancedUnlockViewModel.consumeCredentialDecrypted()
}
}
}
}
}
override fun onResume() {
@@ -295,9 +311,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
mAdvancedUnlockViewModel.initAdvancedUnlockMode()
if (result.isSuccess) {
launchGroupActivityIfLoaded(database)
} else {
@@ -399,23 +412,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
}
}
override fun retrieveCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf()
}
override fun conditionToStoreCredential(): Boolean {
return mainCredentialView?.conditionToStoreCredential() == true
}
override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
// Load the database if password is registered with biometric
loadDatabase(mDatabaseFileUri,
mainCredentialView?.getMainCredential(),
cipherEncryptDatabase
)
}
private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
override fun passwordToStore(password: String?): ByteArray? {
return password?.toByteArray()
@@ -432,7 +428,20 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
}
}
override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
private fun getCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf()
}
private fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
// Load the database if password is registered with biometric
loadDatabase(mDatabaseFileUri,
mainCredentialView?.getMainCredential(),
cipherEncryptDatabase
)
}
private fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
// Load the database if password is retrieve from biometric
// Retrieve from biometric
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()

View File

@@ -20,14 +20,18 @@
package com.kunzisoft.keepass.biometric
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
@@ -35,7 +39,9 @@ import androidx.biometric.BiometricPrompt
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
@@ -52,8 +58,6 @@ import kotlinx.coroutines.launch
class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
private var mBuilderListener: BuilderListener? = null
private var mAdvancedUnlockEnabled = false
private var mAutoOpenPromptEnabled = false
@@ -84,6 +88,8 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
// Only keep connection when we request a device credential activity
private var keepConnection = false
private var isConditionToStoreCredentialVerified = false
private var mDeviceCredentialResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
@@ -115,37 +121,10 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBuilderListener = context as BuilderListener
}
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + BuilderListener::class.java.name)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) {
initAdvancedUnlockMode()
}
mAdvancedUnlockViewModel.onUnlockAvailabilityCheckRequested.observe(this) {
checkUnlockAvailability()
}
mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) {
onDatabaseLoaded(it)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -162,6 +141,32 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
super.onViewCreated(view, savedInstanceState)
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
mAdvancedUnlockViewModel.uiState.collect { uiState ->
// Database loaded
uiState.databaseFileLoaded?.let { databaseLoaded ->
onDatabaseLoaded(databaseLoaded)
mAdvancedUnlockViewModel.consumeDatabaseFileLoaded()
}
// New credential value received
uiState.credential?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockManager?.encryptData(uiState.credential)
}
mAdvancedUnlockViewModel.consumeCredentialForEncryption()
}
// Condition to store credential verified
isConditionToStoreCredentialVerified = uiState.isConditionToStoreCredentialVerified
// Check unlock availability
if (uiState.onUnlockAvailabilityCheckRequested) {
checkUnlockAvailability()
mAdvancedUnlockViewModel.consumeCheckUnlockAvailability()
}
}
}
}
}
override fun onResume() {
@@ -178,10 +183,9 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
// To get device credential unlock result, only if same database uri
if (databaseUri != null
&& mAdvancedUnlockEnabled) {
val deviceCredentialAuthSucceeded = mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded
deviceCredentialAuthSucceeded?.let {
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded?.let { authSucceeded ->
if (databaseUri == databaseFileUri) {
if (deviceCredentialAuthSucceeded == true) {
if (authSucceeded) {
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationSucceeded()
} else {
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationFailed()
@@ -250,7 +254,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (mBuilderListener?.conditionToStoreCredential() == true) {
if (isConditionToStoreCredentialVerified) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
@@ -261,8 +265,13 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
if (isConditionToStoreCredentialVerified) {
// if condition OK, key manager in error
Mode.KEY_MANAGER_UNAVAILABLE
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
}
})
}
}
@@ -523,9 +532,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
}
Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
advancedUnlockManager?.encryptData(credential)
}
mAdvancedUnlockViewModel.retrieveCredentialForEncryption()
}
Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences
@@ -545,7 +552,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialEncrypted(
mAdvancedUnlockViewModel.onCredentialEncrypted(
CipherEncryptDatabase().apply {
this.databaseUri = databaseUri
this.credentialStorage = credentialDatabaseStorage
@@ -559,7 +566,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
override fun handleDecryptedResult(decryptedValue: ByteArray) {
// Load database directly with password retrieve
databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialDecrypted(
mAdvancedUnlockViewModel.onCredentialDecrypted(
CipherDecryptDatabase().apply {
this.databaseUri = databaseUri
this.credentialStorage = credentialDatabaseStorage
@@ -630,13 +637,6 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
EXTRACT_CREDENTIAL
}
interface BuilderListener {
fun retrieveCredentialForEncryption(): ByteArray
fun conditionToStoreCredential(): Boolean
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
}
override fun onPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!keepConnection) {
@@ -645,13 +645,11 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
advancedUnlockManager = null
}
}
super.onPause()
}
override fun onDestroyView() {
mAdvancedUnlockInfoView = null
super.onDestroyView()
}
@@ -659,20 +657,11 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
disconnect()
advancedUnlockManager = null
mBuilderListener = null
}
super.onDestroy()
}
override fun onDetach() {
mBuilderListener = null
super.onDetach()
}
companion object {
private val TAG = AdvancedUnlockFragment::class.java.name
}
}

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.credentialprovider.activity
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@@ -32,10 +31,10 @@ import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
@@ -117,7 +116,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
}
else -> {
// Not an autofill call
setResult(Activity.RESULT_CANCELED)
setResult(RESULT_CANCELED)
finish()
}
}
@@ -128,17 +127,13 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
autofillComponent: AutofillComponent?,
searchInfo: SearchInfo) {
if (autofillComponent == null) {
setResult(Activity.RESULT_CANCELED)
setResult(RESULT_CANCELED)
finish()
} else if (!KeeAutofillService.autofillAllowedFor(
} else if (KeeAutofillService.autofillAllowedFor(
applicationId = searchInfo.applicationId,
webDomain = searchInfo.webDomain,
context = this
)) {
showBlockRestartMessage()
setResult(Activity.RESULT_CANCELED)
finish()
} else {
// If database is open
SearchHelper.checkAutoSearchInfo(
context = this,
@@ -170,6 +165,10 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
)
}
)
} else {
showBlockRestartMessage()
setResult(RESULT_CANCELED)
finish()
}
}
@@ -180,10 +179,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
applicationId = searchInfo.applicationId,
webDomain = searchInfo.webDomain,
context = this
)) {
showBlockRestartMessage()
setResult(Activity.RESULT_CANCELED)
} else {
)) {
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(
context = this,
@@ -227,6 +223,9 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
)
}
)
} else {
showBlockRestartMessage()
setResult(RESULT_CANCELED)
}
finish()
}

View File

@@ -53,9 +53,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
private var checkboxHardwareView: CompoundButton
private var hardwareKeySelectionView: HardwareKeySelectionView
var onPasswordChecked: (CompoundButton.OnCheckedChangeListener)? = null
var onKeyFileChecked: (CompoundButton.OnCheckedChangeListener)? = null
var onHardwareKeyChecked: (CompoundButton.OnCheckedChangeListener)? = null
var onConditionToStoreCredentialChanged: ((CredentialStorage, verified: Boolean) -> Unit)? = null
var onValidateListener: (() -> Unit)? = null
private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD
@@ -104,7 +102,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
}
checkboxPasswordView.setOnCheckedChangeListener { view, checked ->
onPasswordChecked?.onCheckedChanged(view, checked)
onConditionToStoreCredentialChanged?.invoke(
mCredentialStorage,
conditionToStoreCredential()
)
}
checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
if (checked) {
@@ -112,7 +113,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
checkboxKeyFileView.isChecked = false
}
}
onKeyFileChecked?.onCheckedChanged(view, checked)
onConditionToStoreCredentialChanged?.invoke(
mCredentialStorage,
conditionToStoreCredential()
)
}
checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
if (checked) {
@@ -120,7 +124,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
checkboxHardwareView.isChecked = false
}
}
onHardwareKeyChecked?.onCheckedChanged(view, checked)
onConditionToStoreCredentialChanged?.invoke(
mCredentialStorage,
conditionToStoreCredential()
)
}
hardwareKeySelectionView.selectionListener = { _ ->

View File

@@ -1,32 +1,152 @@
package com.kunzisoft.keepass.viewmodels
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.model.CipherDecryptDatabase
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
class AdvancedUnlockViewModel : ViewModel() {
var allowAutoOpenBiometricPrompt : Boolean = true
var deviceCredentialAuthSucceeded: Boolean? = null
val onInitAdvancedUnlockModeRequested : LiveData<Void?> get() = _onInitAdvancedUnlockModeRequested
private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent<Void?>()
private val _uiState = MutableStateFlow(DeviceUnlockState())
val uiState: StateFlow<DeviceUnlockState> = _uiState
val onUnlockAvailabilityCheckRequested : LiveData<Void?> get() = _onUnlockAvailabilityCheckRequested
private val _onUnlockAvailabilityCheckRequested = SingleLiveEvent<Void?>()
val onDatabaseFileLoaded : LiveData<Uri?> get() = _onDatabaseFileLoaded
private val _onDatabaseFileLoaded = SingleLiveEvent<Uri?>()
fun initAdvancedUnlockMode() {
_onInitAdvancedUnlockModeRequested.call()
fun checkUnlockAvailability(conditionToStoreCredentialVerified: Boolean? = null) {
_uiState.update { currentState ->
currentState.copy(
onUnlockAvailabilityCheckRequested = true,
isConditionToStoreCredentialVerified = conditionToStoreCredentialVerified
?: _uiState.value.isConditionToStoreCredentialVerified
)
}
}
fun checkUnlockAvailability() {
_onUnlockAvailabilityCheckRequested.call()
fun consumeCheckUnlockAvailability() {
_uiState.update { currentState ->
currentState.copy(
onUnlockAvailabilityCheckRequested = false
)
}
}
fun databaseFileLoaded(databaseUri: Uri?) {
_onDatabaseFileLoaded.value = databaseUri
_uiState.update { currentState ->
currentState.copy(
databaseFileLoaded = databaseUri
)
}
}
fun consumeDatabaseFileLoaded() {
_uiState.update { currentState ->
currentState.copy(
databaseFileLoaded = null
)
}
}
fun retrieveCredentialForEncryption() {
_uiState.update { currentState ->
currentState.copy(
isCredentialRequired = true,
credential = null
)
}
}
fun provideCredentialForEncryption(credential: ByteArray) {
_uiState.update { currentState ->
currentState.copy(
isCredentialRequired = false,
credential = credential
)
}
}
fun consumeCredentialForEncryption() {
_uiState.update { currentState ->
currentState.copy(
isCredentialRequired = false,
credential = null
)
}
}
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
_uiState.update { currentState ->
currentState.copy(
cipherEncryptDatabase = cipherEncryptDatabase
)
}
}
fun consumeCredentialEncrypted() {
_uiState.update { currentState ->
currentState.copy(
cipherEncryptDatabase = null
)
}
}
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
_uiState.update { currentState ->
currentState.copy(
cipherDecryptDatabase = cipherDecryptDatabase
)
}
}
fun consumeCredentialDecrypted() {
_uiState.update { currentState ->
currentState.copy(
cipherDecryptDatabase = null
)
}
}
}
data class DeviceUnlockState(
val initAdvancedUnlockMode: Boolean = false,
val databaseFileLoaded: Uri? = null,
val isCredentialRequired: Boolean = false,
val credential: ByteArray? = null,
val isConditionToStoreCredentialVerified: Boolean = false,
val onUnlockAvailabilityCheckRequested: Boolean = false,
val cipherEncryptDatabase: CipherEncryptDatabase? = null,
val cipherDecryptDatabase: CipherDecryptDatabase? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DeviceUnlockState
if (initAdvancedUnlockMode != other.initAdvancedUnlockMode) return false
if (isCredentialRequired != other.isCredentialRequired) return false
if (isConditionToStoreCredentialVerified != other.isConditionToStoreCredentialVerified) return false
if (onUnlockAvailabilityCheckRequested != other.onUnlockAvailabilityCheckRequested) return false
if (databaseFileLoaded != other.databaseFileLoaded) return false
if (!credential.contentEquals(other.credential)) return false
if (cipherEncryptDatabase != other.cipherEncryptDatabase) return false
if (cipherDecryptDatabase != other.cipherDecryptDatabase) return false
return true
}
override fun hashCode(): Int {
var result = initAdvancedUnlockMode.hashCode()
result = 31 * result + isCredentialRequired.hashCode()
result = 31 * result + isConditionToStoreCredentialVerified.hashCode()
result = 31 * result + onUnlockAvailabilityCheckRequested.hashCode()
result = 31 * result + (databaseFileLoaded?.hashCode() ?: 0)
result = 31 * result + (credential?.contentHashCode() ?: 0)
result = 31 * result + (cipherEncryptDatabase?.hashCode() ?: 0)
result = 31 * result + (cipherDecryptDatabase?.hashCode() ?: 0)
return result
}
}

View File

@@ -33,10 +33,10 @@
<string name="encryption">Encryption</string>
<string name="contact">Contact</string>
<string name="contribution">Contribution</string>
<string name="about_description">password</string>
<string name="encryption_algorithm">Encryption</string>
<string name="about_description">Android implementation of the KeePass password manager</string>
<string name="encryption_algorithm">Encryption algorithm</string>
<string name="app_timeout">Timeout</string>
<string name="app_timeout_summary">database</string>
<string name="app_timeout_summary">Idle time before locking the database</string>
<string name="application">App</string>
<string name="brackets">Brackets</string>
<string name="extended_ASCII">Extended ASCII</string>

View File

@@ -24,52 +24,115 @@ import java.util.*
class TemplateBuilder {
private val urlAttribute = TemplateAttribute(TemplateField.LABEL_URL, TemplateAttributeType.TEXT)
private val usernameAttribute = TemplateAttribute(TemplateField.LABEL_USERNAME, TemplateAttributeType.TEXT)
private val urlAttribute = TemplateAttribute(
label = TemplateField.LABEL_URL,
type = TemplateAttributeType.TEXT
)
private val usernameAttribute = TemplateAttribute(
label = TemplateField.LABEL_USERNAME,
type = TemplateAttributeType.TEXT
)
private val notesAttribute = TemplateAttribute(
TemplateField.LABEL_NOTES,
TemplateAttributeType.TEXT,
false,
TemplateAttributeOption().apply {
label = TemplateField.LABEL_NOTES,
type = TemplateAttributeType.TEXT,
protected = false,
options = TemplateAttributeOption().apply {
setNumberLinesToMany()
})
private val holderAttribute = TemplateAttribute(TemplateField.LABEL_HOLDER, TemplateAttributeType.TEXT)
private val numberAttribute = TemplateAttribute(TemplateField.LABEL_NUMBER, TemplateAttributeType.TEXT)
private val cvvAttribute = TemplateAttribute(TemplateField.LABEL_CVV, TemplateAttributeType.TEXT, true)
private val pinAttribute = TemplateAttribute(TemplateField.LABEL_PIN, TemplateAttributeType.TEXT, true)
private val nameAttribute = TemplateAttribute(TemplateField.LABEL_NAME, TemplateAttributeType.TEXT)
private val placeOfIssueAttribute = TemplateAttribute(TemplateField.LABEL_PLACE_OF_ISSUE, TemplateAttributeType.TEXT)
}
)
private val holderAttribute = TemplateAttribute(
label = TemplateField.LABEL_HOLDER,
type = TemplateAttributeType.TEXT
)
private val numberAttribute = TemplateAttribute(
label = TemplateField.LABEL_NUMBER,
type = TemplateAttributeType.TEXT
)
private val cvvAttribute = TemplateAttribute(
label = TemplateField.LABEL_CVV,
type = TemplateAttributeType.TEXT,
protected = true
)
private val pinAttribute = TemplateAttribute(
label = TemplateField.LABEL_PIN,
type = TemplateAttributeType.TEXT,
protected = true
)
private val nameAttribute = TemplateAttribute(
label = TemplateField.LABEL_NAME,
type = TemplateAttributeType.TEXT
)
private val placeOfIssueAttribute = TemplateAttribute(
label = TemplateField.LABEL_PLACE_OF_ISSUE,
type = TemplateAttributeType.TEXT
)
private val dateOfIssueAttribute = TemplateAttribute(
TemplateField.LABEL_DATE_OF_ISSUE,
TemplateAttributeType.DATETIME,
false,
TemplateAttributeOption().apply {
label = TemplateField.LABEL_DATE_OF_ISSUE,
type = TemplateAttributeType.DATETIME,
protected = false,
options = TemplateAttributeOption().apply {
setDateFormatToDate()
})
}
)
private val expirationDateAttribute = TemplateAttribute(
TemplateField.LABEL_EXPIRATION,
TemplateAttributeType.DATETIME,
false,
TemplateAttributeOption().apply {
label = TemplateField.LABEL_EXPIRATION,
type = TemplateAttributeType.DATETIME,
protected = false,
options = TemplateAttributeOption().apply {
setDateFormatToDate()
})
private val emailAddressAttribute = TemplateAttribute(TemplateField.LABEL_EMAIL_ADDRESS, TemplateAttributeType.TEXT)
private val passwordAttribute = TemplateAttribute(TemplateField.LABEL_PASSWORD, TemplateAttributeType.TEXT, true)
private val ssidAttribute = TemplateAttribute(TemplateField.LABEL_SSID, TemplateAttributeType.TEXT)
}
)
private val emailAddressAttribute = TemplateAttribute(
label = TemplateField.LABEL_USERNAME,
type = TemplateAttributeType.TEXT,
options = TemplateAttributeOption().apply {
alias = TemplateField.LABEL_EMAIL_ADDRESS
}
)
private val passwordAttribute = TemplateAttribute(
label = TemplateField.LABEL_PASSWORD,
type = TemplateAttributeType.TEXT,
protected = true
)
private val ssidAttribute = TemplateAttribute(
label = TemplateField.LABEL_SSID,
type = TemplateAttributeType.TEXT
)
private val wirelessTypeAttribute = TemplateAttribute(
TemplateField.LABEL_TYPE,
TemplateAttributeType.LIST,
false,
TemplateAttributeOption().apply{
label = TemplateField.LABEL_TYPE,
type = TemplateAttributeType.LIST,
protected = false,
options = TemplateAttributeOption().apply{
setListItems("WPA3", "WPA2", "WPA", "WEP")
default = "WPA2"
})
private val tokenAttribute = TemplateAttribute(TemplateField.LABEL_TOKEN, TemplateAttributeType.TEXT)
private val publicKeyAttribute = TemplateAttribute(TemplateField.LABEL_PUBLIC_KEY, TemplateAttributeType.TEXT)
private val privateKeyAttribute = TemplateAttribute(TemplateField.LABEL_PRIVATE_KEY, TemplateAttributeType.TEXT, true)
private val seedAttribute = TemplateAttribute(TemplateField.LABEL_SEED, TemplateAttributeType.TEXT, true)
private val bicAttribute = TemplateAttribute(TemplateField.LABEL_BIC, TemplateAttributeType.TEXT)
private val ibanAttribute = TemplateAttribute(TemplateField.LABEL_IBAN, TemplateAttributeType.TEXT)
}
)
private val tokenAttribute = TemplateAttribute(
label = TemplateField.LABEL_TOKEN,
type = TemplateAttributeType.TEXT
)
private val publicKeyAttribute = TemplateAttribute(
label = TemplateField.LABEL_PUBLIC_KEY,
type = TemplateAttributeType.TEXT
)
private val privateKeyAttribute = TemplateAttribute(
label = TemplateField.LABEL_PRIVATE_KEY,
type = TemplateAttributeType.TEXT,
protected = true
)
private val seedAttribute = TemplateAttribute(
label = TemplateField.LABEL_SEED,
type = TemplateAttributeType.TEXT,
protected = true
)
private val bicAttribute = TemplateAttribute(
label = TemplateField.LABEL_BIC,
type = TemplateAttributeType.TEXT
)
private val ibanAttribute = TemplateAttribute(
label = TemplateField.LABEL_IBAN,
type = TemplateAttributeType.TEXT
)
val email: Template
get() {

View File

@@ -1,19 +1,20 @@
KeePassDX ist ein <b>Passwort-Safe und -Manager</b>, der es ermöglicht, verschlüsselte <b>Daten in einer einzigen Datei</b> im offenen KeePass-Format zu bearbeiten und <b>die Formulare auf sichere Weise auszufüllen</b>, <b>erfordert keine Internetverbindung</b> und integriert Android-Designstandards. Die App ist <b>Open Source, ohne Werbung</b>.
<b>Funktionalitäten</b>
- Erstellung von Datenbanken / Einträgen und Gruppen.
- Unterstützung von .kdb- und .kdbx-Dateien (Version 1 bis 4) mit AES - Twofish - ChaCha20 - Argon2 Algorithmus.
- Kompatibel mit den meisten alternativen Programmen (KeePass, KeePassXC, KeeWeb, ...).
- Ermöglicht das schnelle Kopieren von Feldern und das Öffnen von URIs /URLs.
- Biometrische Erkennung für eine schnelle Entsperrung (Fingerabdrücke / Entsperrung durch Gesicht / ...).
- Verwaltung von Einmalpasswörtern (One-Time Password HOTP / TOTP) für die Zwei-Faktor-Authentifizierung (2FA).
- Material Design mit Themen.
- Automatisches Ausfüllen von Feldern.
- Tastatur zum Ausfüllen von Feldern.
- Dynamische Schablonen.
- Historie jedes Eintrags.
- Präzise Verwaltung der Parameter.
- In nativen Sprachen geschriebener Code (Kotlin / Java / JNI / C).
- Erstellung von Datenbanken / Einträgen und Gruppen.
- Unterstützung von .kdb- und .kdbx-Dateien (Version 1 bis 4) mit AES - Twofish - ChaCha20 - Argon2 Algorithmus.
- Kompatibel mit den meisten alternativen Programmen (KeePass, KeePassXC, KeeWeb, ...).
- Ermöglicht das schnelle Kopieren von Feldern und das Öffnen von URIs /URLs.
- Biometrische Erkennung für eine schnelle Entsperrung (Fingerabdrücke / Entsperrung durch Gesicht / ...).
- Verwaltung von Einmalpasswörtern (One-Time Password HOTP / TOTP) für die Zwei-Faktor-Authentifizierung (2FA).
- Material Design mit Themen.
- Automatisches Ausfüllen von Feldern.
- Tastatur zum Ausfüllen von Feldern.
- Dynamische Schablonen.
- Historie jedes Eintrags.
- Präzise Verwaltung der Parameter.
- In nativen Sprachen geschriebener Code (Kotlin / Java / JNI / C).
Sie können spenden oder die Pro-Version kaufen, um einen besseren Service und eine schnelle Entwicklung der von Ihnen gewünschten Funktionen zu erhalten: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>

View File

@@ -3,6 +3,6 @@
* Fix Group notes #2053
* Fix Dialog background #2005 #2004 (Thx @codokie)
* Fix OTP configuration #2042 #2065 (Thx @Dev-ClayP)
* Fix small UI elements #1987 #2007 (Thx @ymcx)
* Fix small UI elements #1987 #2007 (Thx @ymcx)
* RTL layout support #2021 (Thx @codokie)
* App Metadata to translation #1823

View File

@@ -0,0 +1,4 @@
* Fix Autofill Registration #2089
* Fix Biometric errors #2081
* Fixed timestamp in copy file #1981 #1983
* Fix Template Email #1986

View File

@@ -1,19 +1,20 @@
KeePassDX is a <b>password safe and manager</b> allows editing <b>encrypted data in a single file</b> in the open KeePass format and <b>fill in the forms in a secure way</b>, requires <b>no Internet connection</b> and integrates Android design standards. The app is <b>open source, with no advertising</b>.
<b>Features</b>
- Create database files / entries and groups.
- Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
- Compatible with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
- Allows opening and copying URI / URL fields quickly.
- Biometric recognition for fast unlocking (fingerprint / face unlock / …).
- One-time password management (HOTP / TOTP) for two-factor authentication (2FA).
- Material design with themes.
- Auto-Fill and integration.
- Field filling keyboard.
- Dynamic templates.
- History of each entry.
- Precise management of settings.
- Code written in native languages (Kotlin / Java / JNI / C).
- Create database files / entries and groups.
- Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
- Compatible with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
- Allows opening and copying URI / URL fields quickly.
- Biometric recognition for fast unlocking (fingerprint / face unlock / …).
- One-time password management (HOTP / TOTP) for two-factor authentication (2FA).
- Material design with themes.
- Auto-Fill and integration.
- Field filling keyboard.
- Dynamic templates.
- History of each entry.
- Precise management of settings.
- Code written in native languages (Kotlin / Java / JNI / C).
You can donate or buy the pro version for better service and a quick development of features you want: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>

View File

@@ -0,0 +1,4 @@
* Correction de l'enregistrement pour le remplissage automatique #2089
* Correction des erreurs biométriques #2081
* Correction du timestamp dans le fichier de copie #1981 #1983
* Correction des gabaris Email #1986

View File

@@ -2,19 +2,19 @@ KeePassDX est un <b>coffre-fort et un gestionnaire de mots de passe</b> qui perm
<b>Fonctionnalités</b>
- Création de bases de données / entrées et groupes.
- Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2.
- Compatible avec la majorité des programmes alternatifs (KeePass, KeePassXC, KeeWeb, …)
- Permet la copie rapide de champs et l'ouverture d'URI /URL.
- Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / …).
- Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA).
- Material design avec thèmes.
- Remplissage automatique de champs.
- Clavier de remplissage de champs.
- Gabarits dynamiques.
- Historique de chaque entrée.
- Gestion précise des paramètres.
- Code écrit en langages natifs (Kotlin / Java / JNI / C).
- Création de bases de données / entrées et groupes.
- Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2.
- Compatible avec la majorité des programmes alternatifs (KeePass, KeePassXC, KeeWeb, …)
- Permet la copie rapide de champs et l'ouverture d'URI /URL.
- Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / …).
- Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA).
- Material design avec thèmes.
- Remplissage automatique de champs.
- Clavier de remplissage de champs.
- Gabarits dynamiques.
- Historique de chaque entrée.
- Gestion précise des paramètres.
- Code écrit en langages natifs (Kotlin / Java / JNI / C).
Vous pouvez faire un don ou acheter la version pro pour un meilleur service et un développement rapide des fonctionnalités que vous souhaitez : <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>

View File

@@ -1,19 +1,20 @@
KeePassDX は <b>オープンソース</b>かつ<b>広告なし</b>です。<b>複数の形式に対応する KeePass パスワード マネージャー</b>。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。<b>インターネットに接続する必要はありません。</b>
<b>機能</b>
- データベースファイル / エントリー・グループの作成
- .kdb、.kdbx ファイルバージョン1から4に対応。AES、Twofish、ChaCha20、Argon2 アルゴリズムが使用可能
- 主流の代替ソフトウェアKeePass、KeePassXC, KeeWeb など)との互換性あり
- URI / URL フィールドは開くのもコピーするのもすばやく行えます
- 生体認証を使った高速ロック解除 (指紋認証 / 顔認証 / …)
- 2 要素認証2FAのためのワンタイムパスワード管理HOTP / TOTP
- マテリアルデザインに準拠した複数のテーマ
- 自動入力機能の統合
- フィールド入力用のキーボード
- ダイナミックテンプレート
- エントリーごとの履歴
- 設定の細かな管理
- コードはネイティブ言語Kotlin / Java / JNI / Cで書かれています
- データベースファイル / エントリー・グループの作成
- .kdb、.kdbx ファイルバージョン1から4に対応。AES、Twofish、ChaCha20、Argon2 アルゴリズムが使用可能
- 主流の代替ソフトウェアKeePass、KeePassXC, KeeWeb など)との互換性あり
- URI / URL フィールドは開くのもコピーするのもすばやく行えます
- 生体認証を使った高速ロック解除 (指紋認証 / 顔認証 / …
- 2 要素認証2FAのためのワンタイムパスワード管理HOTP / TOTP
- マテリアルデザインに準拠した複数のテーマ
- 自動入力機能の統合
- フィールド入力用のキーボード
- ダイナミックテンプレート
- エントリーごとの履歴
- 設定の細かな管理
- コードはネイティブ言語Kotlin / Java / JNI / Cで書かれています
寄付または pro バージョンの購入はサービスの改善と必要な機能の迅速な開発につながります: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>

View File

@@ -1,19 +1,20 @@
KeePassDX имеет <b>открытый исходный код</b> и <b>без рекламы</b>. <b>Многоформатный менеджер паролей KeePass</b> это приложение которое позволяет безопасно сохранять и использовать пароли, ключи и цифровые идентификаторы за счет интеграции стандартов дизайна Android и <b>не требует подключения к Интернету</b>.
<b>Возможности</b>
- Создание файлов/записей и групп.
— Поддержка файлов .kdb и .kdbx (версии от 1 до 4) с алгоритмом AES — Twofish — ChaCha20 — Argon2.
- Совместим с большинством альтернативных программ (KeePass, KeePassXC, KeeWeb, …).
- Позволяет быстро открывать и копировать поля URI / URL.
- Биометрическое распознавание для быстрой разблокировки (отпечаток пальца /разблокировка по лицу / …).
- Управление одноразовыми паролями (HOTP / TOTP) для двухфакторной аутентификации (2FA).
- Материальный дизайн с темами.
- Автозаполнение и интеграция.
- Клавиатура для заполнения полей.
- Динамические шаблоны.
- История каждой записи.
- Точное управление настройками.
— Код написан на родных языках для платформы (Kotlin / Java / JNI / C).
- Создание файлов/записей и групп.
- Поддержка файлов .kdb и .kdbx (версии от 1 до 4) с алгоритмом AES — Twofish — ChaCha20 — Argon2.
- Совместим с большинством альтернативных программ (KeePass, KeePassXC, KeeWeb, …).
- Позволяет быстро открывать и копировать поля URI / URL.
- Биометрическое распознавание для быстрой разблокировки (отпечаток пальца /разблокировка по лицу / …).
- Управление одноразовыми паролями (HOTP / TOTP) для двухфакторной аутентификации (2FA).
- Материальный дизайн с темами.
- Автозаполнение и интеграция.
- Клавиатура для заполнения полей.
- Динамические шаблоны.
- История каждой записи.
- Точное управление настройками.
- Код написан на родных языках для платформы (Kotlin / Java / JNI / C).
Вы можете пожертвовать или купить профессиональную версию чтобы улучшить обслуживание и ускорить разработку нужных вам возможностей: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">KeePass Pro</a>