mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Refactoring of advanced unlock
This commit is contained in:
@@ -50,7 +50,7 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity
|
|||||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||||
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
|
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
@@ -114,7 +114,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
|
|
||||||
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
||||||
|
|
||||||
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
|
private var advancedUnlockManager: AdvancedUnlockManager? = null
|
||||||
private var mAllowAutoOpenBiometricPrompt: Boolean = true
|
private var mAllowAutoOpenBiometricPrompt: Boolean = true
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -210,7 +210,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) {
|
if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) {
|
||||||
// Stay with the same mode and init it
|
// Stay with the same mode and init it
|
||||||
advancedUnlockedManager?.initAdvancedUnlockMode()
|
advancedUnlockManager?.initAdvancedUnlockMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,9 +388,9 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
// Init Biometric elements
|
// Init Biometric elements
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (PreferencesUtil.isAdvancedUnlockEnable(this)) {
|
if (PreferencesUtil.isAdvancedUnlockEnable(this)) {
|
||||||
if (advancedUnlockedManager == null
|
if (advancedUnlockManager == null
|
||||||
&& databaseFileUri != null) {
|
&& databaseFileUri != null) {
|
||||||
advancedUnlockedManager = AdvancedUnlockedManager(this,
|
advancedUnlockManager = AdvancedUnlockManager(this,
|
||||||
databaseFileUri,
|
databaseFileUri,
|
||||||
advancedUnlockInfoView,
|
advancedUnlockInfoView,
|
||||||
checkboxPasswordView,
|
checkboxPasswordView,
|
||||||
@@ -415,16 +415,16 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
advancedUnlockedManager?.isBiometricPromptAutoOpenEnable =
|
advancedUnlockManager?.isBiometricPromptAutoOpenEnable =
|
||||||
mAllowAutoOpenBiometricPrompt && mProgressDatabaseTaskProvider?.isBinded() != true
|
mAllowAutoOpenBiometricPrompt && mProgressDatabaseTaskProvider?.isBinded() != true
|
||||||
advancedUnlockedManager?.checkBiometricAvailability()
|
advancedUnlockManager?.checkBiometricAvailability()
|
||||||
} else {
|
} else {
|
||||||
advancedUnlockInfoView?.visibility = View.GONE
|
advancedUnlockInfoView?.visibility = View.GONE
|
||||||
advancedUnlockedManager?.destroy()
|
advancedUnlockManager?.destroy()
|
||||||
advancedUnlockedManager = null
|
advancedUnlockManager = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (advancedUnlockedManager == null) {
|
if (advancedUnlockManager == null) {
|
||||||
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||||
}
|
}
|
||||||
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||||
@@ -480,8 +480,8 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
mProgressDatabaseTaskProvider?.unregisterProgressTask()
|
mProgressDatabaseTaskProvider?.unregisterProgressTask()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
advancedUnlockedManager?.destroy()
|
advancedUnlockManager?.destroy()
|
||||||
advancedUnlockedManager = null
|
advancedUnlockManager = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reinit locking activity UI variable
|
// Reinit locking activity UI variable
|
||||||
@@ -594,7 +594,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
|
|
||||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
// biometric menu
|
// biometric menu
|
||||||
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
|
advancedUnlockManager?.inflateOptionsMenu(inflater, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
@@ -709,7 +709,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
changeOpenFileReadIcon(item)
|
changeOpenFileReadIcon(item)
|
||||||
}
|
}
|
||||||
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
advancedUnlockedManager?.deleteEncryptedDatabaseKey()
|
advancedUnlockManager?.deleteEncryptedDatabaseKey()
|
||||||
}
|
}
|
||||||
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||||
}
|
}
|
||||||
@@ -725,6 +725,11 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
|
|
||||||
mAllowAutoOpenBiometricPrompt = false
|
mAllowAutoOpenBiometricPrompt = false
|
||||||
|
|
||||||
|
// To get device credential unlock result
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// TODO Advanced unlock response
|
||||||
|
}
|
||||||
|
|
||||||
// To get entry in result
|
// To get entry in result
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.kunzisoft.keepass.biometric
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
data class AdvancedUnlockCryptoPrompt(var cipher: Cipher,
|
||||||
|
@StringRes var promptTitleId: Int,
|
||||||
|
@StringRes var promptDescriptionId: Int? = null,
|
||||||
|
var isDeviceCredentialOperation: Boolean,
|
||||||
|
var isBiometricOperation: Boolean)
|
||||||
@@ -40,14 +40,14 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
|||||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
class AdvancedUnlockedManager(var context: FragmentActivity,
|
class AdvancedUnlockManager(var context: FragmentActivity,
|
||||||
var databaseFileUri: Uri,
|
var databaseFileUri: Uri,
|
||||||
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
||||||
private var checkboxPasswordView: CompoundButton?,
|
private var checkboxPasswordView: CompoundButton?,
|
||||||
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
||||||
var passwordView: TextView?,
|
var passwordView: TextView?,
|
||||||
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
||||||
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
||||||
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
|
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
|
||||||
|
|
||||||
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
|
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
|
||||||
@@ -253,18 +253,13 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
|
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
|
||||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
|
||||||
promptInfo: BiometricPrompt.PromptInfo) {
|
|
||||||
context.runOnUiThread {
|
context.runOnUiThread {
|
||||||
if (allowOpenBiometricPrompt) {
|
if (allowOpenBiometricPrompt) {
|
||||||
if (biometricPrompt != null) {
|
try {
|
||||||
if (cryptoObject != null) {
|
biometricUnlockDatabaseHelper?.openAdvancedUnlockPrompt(cryptoPrompt)
|
||||||
biometricPrompt.authenticate(promptInfo, cryptoObject)
|
} catch (e: Exception) {
|
||||||
} else {
|
Log.e(TAG, "Unable to open advanced unlock prompt", e)
|
||||||
setAdvancedUnlockedTitleView(R.string.crypto_object_not_initialized)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
|
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,10 +271,10 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
|
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
|
||||||
setAdvancedUnlockedMessageView("")
|
setAdvancedUnlockedMessageView("")
|
||||||
|
|
||||||
biometricUnlockDatabaseHelper?.initEncryptData { biometricPrompt, cryptoObject, promptInfo ->
|
biometricUnlockDatabaseHelper?.initEncryptData { cryptoPrompt ->
|
||||||
// Set listener to open the biometric dialog and save credential
|
// Set listener to open the biometric dialog and save credential
|
||||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||||
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
|
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,17 +287,17 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
if (biometricUnlockDatabaseHelper != null) {
|
if (biometricUnlockDatabaseHelper != null) {
|
||||||
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabase ->
|
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabase ->
|
||||||
cipherDatabase?.let {
|
cipherDatabase?.let {
|
||||||
biometricUnlockDatabaseHelper?.initDecryptData(it.specParameters) { biometricPrompt, cryptoObject, promptInfo ->
|
biometricUnlockDatabaseHelper?.initDecryptData(it.specParameters) { cryptoPrompt ->
|
||||||
|
|
||||||
// Set listener to open the biometric dialog and check credential
|
// Set listener to open the biometric dialog and check credential
|
||||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||||
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
|
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto open the biometric prompt
|
// Auto open the biometric prompt
|
||||||
if (isBiometricPromptAutoOpenEnable) {
|
if (isBiometricPromptAutoOpenEnable) {
|
||||||
isBiometricPromptAutoOpenEnable = false
|
isBiometricPromptAutoOpenEnable = false
|
||||||
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
|
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: deleteEncryptedDatabaseKey()
|
} ?: deleteEncryptedDatabaseKey()
|
||||||
@@ -417,6 +412,6 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val TAG = AdvancedUnlockedManager::class.java.name
|
private val TAG = AdvancedUnlockManager::class.java.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricManager.Authenticators.*
|
import androidx.biometric.BiometricManager.Authenticators.*
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
@@ -46,19 +47,18 @@ import javax.crypto.spec.IvParameterSpec
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
||||||
|
|
||||||
private var biometricPrompt: BiometricPrompt? = null
|
|
||||||
|
|
||||||
private var keyStore: KeyStore? = null
|
private var keyStore: KeyStore? = null
|
||||||
private var keyGenerator: KeyGenerator? = null
|
private var keyGenerator: KeyGenerator? = null
|
||||||
private var cipher: Cipher? = null
|
private var cipher: Cipher? = null
|
||||||
private var keyguardManager: KeyguardManager? = null
|
|
||||||
private var cryptoObject: BiometricPrompt.CryptoObject? = null
|
|
||||||
|
|
||||||
private var isKeyManagerInit = false
|
private var biometricPrompt: BiometricPrompt? = null
|
||||||
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
||||||
var biometricUnlockCallback: BiometricUnlockCallback? = null
|
var biometricUnlockCallback: BiometricUnlockCallback? = null
|
||||||
|
|
||||||
|
private var isKeyManagerInit = false
|
||||||
|
|
||||||
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context)
|
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context)
|
||||||
|
private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(context)
|
||||||
|
|
||||||
val isKeyManagerInitialized: Boolean
|
val isKeyManagerInitialized: Boolean
|
||||||
get() {
|
get() {
|
||||||
@@ -68,17 +68,31 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
return isKeyManagerInit
|
return isKeyManagerInit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since Android 30, device credential is also a biometric operation
|
||||||
|
private fun isDeviceCredentialOperation(): Boolean {
|
||||||
|
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||||
|
&& deviceCredentialUnlockEnable
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isDeviceCredentialBiometricOperation(): Boolean {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||||
|
&& deviceCredentialUnlockEnable
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBiometricOperation(): Boolean {
|
||||||
|
return biometricUnlockEnable || isDeviceCredentialBiometricOperation()
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (allowInitKeyStore(context)) {
|
if (isDeviceSecure(context)
|
||||||
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
|
&& (deviceCredentialUnlockEnable || biometricUnlockEnable)) {
|
||||||
try {
|
try {
|
||||||
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE)
|
this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE)
|
||||||
this.keyGenerator = KeyGenerator.getInstance(BIOMETRIC_KEY_ALGORITHM, BIOMETRIC_KEYSTORE)
|
this.keyGenerator = KeyGenerator.getInstance(ADVANCED_UNLOCK_KEY_ALGORITHM, ADVANCED_UNLOCK_KEYSTORE)
|
||||||
this.cipher = Cipher.getInstance(
|
this.cipher = Cipher.getInstance(
|
||||||
BIOMETRIC_KEY_ALGORITHM + "/"
|
ADVANCED_UNLOCK_KEY_ALGORITHM + "/"
|
||||||
+ BIOMETRIC_BLOCKS_MODES + "/"
|
+ ADVANCED_UNLOCK_BLOCKS_MODES + "/"
|
||||||
+ BIOMETRIC_ENCRYPTION_PADDING)
|
+ ADVANCED_UNLOCK_ENCRYPTION_PADDING)
|
||||||
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
|
|
||||||
isKeyManagerInit = (keyStore != null
|
isKeyManagerInit = (keyStore != null
|
||||||
&& keyGenerator != null
|
&& keyGenerator != null
|
||||||
&& cipher != null)
|
&& cipher != null)
|
||||||
@@ -103,12 +117,12 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
keyStore.load(null)
|
keyStore.load(null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!keyStore.containsAlias(BIOMETRIC_KEYSTORE_KEY)) {
|
if (!keyStore.containsAlias(ADVANCED_UNLOCK_KEYSTORE_KEY)) {
|
||||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||||
// and the constrains (purposes) in the constructor of the Builder
|
// and the constrains (purposes) in the constructor of the Builder
|
||||||
keyGenerator?.init(
|
keyGenerator?.init(
|
||||||
KeyGenParameterSpec.Builder(
|
KeyGenParameterSpec.Builder(
|
||||||
BIOMETRIC_KEYSTORE_KEY,
|
ADVANCED_UNLOCK_KEYSTORE_KEY,
|
||||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||||
@@ -116,8 +130,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
// of the key
|
// of the key
|
||||||
.setUserAuthenticationRequired(true)
|
.setUserAuthenticationRequired(true)
|
||||||
.apply {
|
.apply {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
if (isDeviceCredentialBiometricOperation()) {
|
||||||
&& deviceCredentialUnlockEnable) {
|
|
||||||
setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL)
|
setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +142,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
|
return keyStore.getKey(ADVANCED_UNLOCK_KEYSTORE_KEY, null) as SecretKey?
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
||||||
@@ -139,33 +152,24 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun initEncryptData(actionIfCypherInit
|
fun initEncryptData(actionIfCypherInit
|
||||||
: (biometricPrompt: BiometricPrompt?,
|
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
||||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
|
||||||
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
|
|
||||||
if (!isKeyManagerInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// TODO if (keyguardManager?.isDeviceSecure == true) {
|
// TODO if (keyguardManager?.isDeviceSecure == true) {
|
||||||
getSecretKey()?.let { secretKey ->
|
getSecretKey()?.let { secretKey ->
|
||||||
cipher?.init(Cipher.ENCRYPT_MODE, secretKey)
|
cipher?.let { cipher ->
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||||
|
|
||||||
initBiometricPrompt()
|
actionIfCypherInit.invoke(
|
||||||
|
AdvancedUnlockCryptoPrompt(
|
||||||
val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
cipher,
|
||||||
setTitle(context.getString(R.string.advanced_unlock_prompt_store_credential_title))
|
R.string.advanced_unlock_prompt_store_credential_title,
|
||||||
setDescription(context.getString(R.string.advanced_unlock_prompt_store_credential_message))
|
R.string.advanced_unlock_prompt_store_credential_message,
|
||||||
setConfirmationRequired(true)
|
isDeviceCredentialOperation(), isBiometricOperation())
|
||||||
if (deviceCredentialUnlockEnable) {
|
)
|
||||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
}
|
||||||
} else {
|
|
||||||
setNegativeButtonText(context.getString(android.R.string.cancel))
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
actionIfCypherInit.invoke(biometricPrompt,
|
|
||||||
cryptoObject,
|
|
||||||
promptInfoStoreCredential)
|
|
||||||
}
|
}
|
||||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||||
@@ -200,9 +204,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
||||||
: (biometricPrompt: BiometricPrompt?,
|
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
||||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
|
||||||
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
|
|
||||||
if (!isKeyManagerInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -213,24 +215,17 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
val spec = IvParameterSpec(iv)
|
val spec = IvParameterSpec(iv)
|
||||||
|
|
||||||
getSecretKey()?.let { secretKey ->
|
getSecretKey()?.let { secretKey ->
|
||||||
cipher?.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
cipher?.let { cipher ->
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
||||||
|
|
||||||
initBiometricPrompt()
|
actionIfCypherInit.invoke(
|
||||||
|
AdvancedUnlockCryptoPrompt(
|
||||||
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
cipher,
|
||||||
setTitle(context.getString(R.string.advanced_unlock_prompt_extract_credential_title))
|
R.string.advanced_unlock_prompt_extract_credential_title,
|
||||||
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
null,
|
||||||
setConfirmationRequired(false)
|
isDeviceCredentialOperation(), isBiometricOperation())
|
||||||
if (deviceCredentialUnlockEnable) {
|
)
|
||||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
}
|
||||||
} else {
|
|
||||||
setNegativeButtonText(context.getString(android.R.string.cancel))
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
actionIfCypherInit.invoke(biometricPrompt,
|
|
||||||
cryptoObject,
|
|
||||||
promptInfoExtractCredential)
|
|
||||||
}
|
}
|
||||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||||
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
|
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
|
||||||
@@ -267,20 +262,52 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
fun deleteKeystoreKey() {
|
fun deleteKeystoreKey() {
|
||||||
try {
|
try {
|
||||||
keyStore?.load(null)
|
keyStore?.load(null)
|
||||||
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
|
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun initBiometricPrompt() {
|
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
|
||||||
|
// Init advanced unlock prompt
|
||||||
if (biometricPrompt == null) {
|
if (biometricPrompt == null) {
|
||||||
authenticationCallback?.let {
|
authenticationCallback?.let { callback ->
|
||||||
biometricPrompt = BiometricPrompt(context, Executors.newSingleThreadExecutor(), it)
|
biometricPrompt = BiometricPrompt(context, Executors.newSingleThreadExecutor(), callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val promptTitle = context.getString(cryptoPrompt.promptTitleId)
|
||||||
|
val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId ->
|
||||||
|
context.getString(descriptionId)
|
||||||
|
} ?: ""
|
||||||
|
|
||||||
|
if (cryptoPrompt.isDeviceCredentialOperation) {
|
||||||
|
// TODO open intent keyguard for response
|
||||||
|
val keyGuardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
|
||||||
|
context.startActivityForResult(
|
||||||
|
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
|
||||||
|
REQUEST_DEVICE_CREDENTIAL)
|
||||||
|
}
|
||||||
|
else if (cryptoPrompt.isBiometricOperation) {
|
||||||
|
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||||
|
setTitle(promptTitle)
|
||||||
|
if (promptDescription.isNotEmpty())
|
||||||
|
setDescription(promptDescription)
|
||||||
|
setConfirmationRequired(false)
|
||||||
|
if (isDeviceCredentialBiometricOperation()) {
|
||||||
|
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
||||||
|
} else {
|
||||||
|
setNegativeButtonText(context.getString(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
biometricPrompt?.authenticate(
|
||||||
|
promptInfoExtractCredential,
|
||||||
|
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closeBiometricPrompt() {
|
fun closeBiometricPrompt() {
|
||||||
@@ -301,11 +328,13 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
|
|
||||||
private val TAG = BiometricUnlockDatabaseHelper::class.java.name
|
private val TAG = BiometricUnlockDatabaseHelper::class.java.name
|
||||||
|
|
||||||
private const val BIOMETRIC_KEYSTORE = "AndroidKeyStore"
|
private const val ADVANCED_UNLOCK_KEYSTORE = "AndroidKeyStore"
|
||||||
private const val BIOMETRIC_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
|
private const val ADVANCED_UNLOCK_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
|
||||||
private const val BIOMETRIC_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
private const val ADVANCED_UNLOCK_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
||||||
private const val BIOMETRIC_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
|
private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
|
||||||
private const val BIOMETRIC_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||||
|
|
||||||
|
private const val REQUEST_DEVICE_CREDENTIAL = 556
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
fun canAuthenticate(context: Context): Int {
|
fun canAuthenticate(context: Context): Int {
|
||||||
@@ -336,12 +365,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
fun allowInitKeyStore(context: Context): Boolean {
|
fun isDeviceSecure(context: Context): Boolean {
|
||||||
val biometricCanAuthenticate = canAuthenticate(context)
|
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
|
||||||
return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
keyguardManager?.isDeviceSecure ?: false
|
||||||
)
|
} else {
|
||||||
|
keyguardManager?.isKeyguardSecure ?: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
@@ -365,15 +396,26 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.R)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
fun deviceCredentialUnlockSupported(context: Context): Boolean {
|
fun deviceCredentialUnlockSupported(context: Context): Boolean {
|
||||||
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||||
)
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||||
|
)
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
isDeviceSecure
|
||||||
|
} else {
|
||||||
|
isKeyguardSecure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
BiometricUnlockDatabaseHelper.deviceCredentialUnlockSupported(activity)
|
BiometricUnlockDatabaseHelper.deviceCredentialUnlockSupported(activity)
|
||||||
} else false
|
} else false
|
||||||
deviceCredentialUnlockEnablePreference?.apply {
|
deviceCredentialUnlockEnablePreference?.apply {
|
||||||
@@ -266,7 +266,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
isChecked = false
|
isChecked = false
|
||||||
setOnPreferenceClickListener { preference ->
|
setOnPreferenceClickListener { preference ->
|
||||||
(preference as SwitchPreference).isChecked = false
|
(preference as SwitchPreference).isChecked = false
|
||||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.R)
|
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.LOLLIPOP)
|
||||||
.show(parentFragmentManager, "unavailableFeatureDialog")
|
.show(parentFragmentManager, "unavailableFeatureDialog")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -504,7 +504,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Pokuste se uložit sdílené info, když manuálné vybíráte položku</string>
|
<string name="keyboard_save_search_info_summary">Pokuste se uložit sdílené info, když manuálné vybíráte položku</string>
|
||||||
<string name="keyboard_save_search_info_title">Uložit sdílené info</string>
|
<string name="keyboard_save_search_info_title">Uložit sdílené info</string>
|
||||||
<string name="notification">Oznámení</string>
|
<string name="notification">Oznámení</string>
|
||||||
<string name="crypto_object_not_initialized">Krypto objekt nelze načíst.</string>
|
|
||||||
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
|
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
|
||||||
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
|
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
|
||||||
<string name="warning_empty_recycle_bin">Trvale odstranit všechny položky z koše\?</string>
|
<string name="warning_empty_recycle_bin">Trvale odstranit všechny položky z koše\?</string>
|
||||||
|
|||||||
@@ -491,7 +491,6 @@
|
|||||||
<string name="database_data_remove_unlinked_attachments_summary">Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post</string>
|
<string name="database_data_remove_unlinked_attachments_summary">Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post</string>
|
||||||
<string name="database_data_remove_unlinked_attachments_title">Fjern ikke-sammenkædede data</string>
|
<string name="database_data_remove_unlinked_attachments_title">Fjern ikke-sammenkædede data</string>
|
||||||
<string name="data">Data</string>
|
<string name="data">Data</string>
|
||||||
<string name="crypto_object_not_initialized">Kryptoobjektet kunne ikke hentes.</string>
|
|
||||||
<string name="biometric_security_update_required">Biometrisk sikkerhedsopdatering påkrævet.</string>
|
<string name="biometric_security_update_required">Biometrisk sikkerhedsopdatering påkrævet.</string>
|
||||||
<string name="warning_empty_keyfile_explanation">Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.</string>
|
<string name="warning_empty_keyfile_explanation">Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.</string>
|
||||||
<string name="warning_empty_keyfile">Det anbefales ikke at tilføje en tom nøglefil.</string>
|
<string name="warning_empty_keyfile">Det anbefales ikke at tilføje en tom nøglefil.</string>
|
||||||
|
|||||||
@@ -508,7 +508,6 @@
|
|||||||
<string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string>
|
<string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string>
|
||||||
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
|
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
|
||||||
<string name="notification">Benachrichtigung</string>
|
<string name="notification">Benachrichtigung</string>
|
||||||
<string name="crypto_object_not_initialized">Kryptoobjekt kann nicht abgerufen werden.</string>
|
|
||||||
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
|
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
|
||||||
<string name="configure_biometric">Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.</string>
|
<string name="configure_biometric">Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.</string>
|
||||||
<string name="registration_mode">Registrierungsmodus</string>
|
<string name="registration_mode">Registrierungsmodus</string>
|
||||||
|
|||||||
@@ -503,7 +503,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης</string>
|
<string name="keyboard_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης</string>
|
||||||
<string name="keyboard_save_search_info_title">Αποθήκευση κοινόχρηστων πληροφοριών</string>
|
<string name="keyboard_save_search_info_title">Αποθήκευση κοινόχρηστων πληροφοριών</string>
|
||||||
<string name="notification">Ειδοποίηση</string>
|
<string name="notification">Ειδοποίηση</string>
|
||||||
<string name="crypto_object_not_initialized">Δεν είναι δυνατή η ανάκτηση κρυπτογραφικού αντικειμένου.</string>
|
|
||||||
<string name="biometric_security_update_required">Απαιτείται ενημέρωση βιομετρικής ασφάλειας.</string>
|
<string name="biometric_security_update_required">Απαιτείται ενημέρωση βιομετρικής ασφάλειας.</string>
|
||||||
<string name="configure_biometric">Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.</string>
|
<string name="configure_biometric">Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.</string>
|
||||||
<string name="warning_empty_recycle_bin">Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;</string>
|
<string name="warning_empty_recycle_bin">Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;</string>
|
||||||
|
|||||||
@@ -513,7 +513,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Essayer d’enregistrer les informations partagées lors de la sélection manuelle d’une entrée</string>
|
<string name="keyboard_save_search_info_summary">Essayer d’enregistrer les informations partagées lors de la sélection manuelle d’une entrée</string>
|
||||||
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
|
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
|
||||||
<string name="notification">Notification</string>
|
<string name="notification">Notification</string>
|
||||||
<string name="crypto_object_not_initialized">Impossible de récupérer l\'objet crypto.</string>
|
|
||||||
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
|
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
|
||||||
<string name="warning_empty_recycle_bin">Supprimer définitivement tous les nœuds de la corbeille \?</string>
|
<string name="warning_empty_recycle_bin">Supprimer définitivement tous les nœuds de la corbeille \?</string>
|
||||||
<string name="registration_mode">Mode enregistrement</string>
|
<string name="registration_mode">Mode enregistrement</string>
|
||||||
|
|||||||
@@ -490,7 +490,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa</string>
|
<string name="keyboard_save_search_info_summary">Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa</string>
|
||||||
<string name="keyboard_save_search_info_title">Spremi dijeljene informacije</string>
|
<string name="keyboard_save_search_info_title">Spremi dijeljene informacije</string>
|
||||||
<string name="warning_empty_recycle_bin">Trajno izbrisati sve čvorove iz smeća\?</string>
|
<string name="warning_empty_recycle_bin">Trajno izbrisati sve čvorove iz smeća\?</string>
|
||||||
<string name="crypto_object_not_initialized">Nije moguće dohvatiti kripto objekt.</string>
|
|
||||||
<string name="biometric_security_update_required">Potrebno je aktualizirati biometrijsku zaštitu.</string>
|
<string name="biometric_security_update_required">Potrebno je aktualizirati biometrijsku zaštitu.</string>
|
||||||
<string name="configure_biometric">Ne postoji biometrijski ključ niti podaci za prijavu uređaja.</string>
|
<string name="configure_biometric">Ne postoji biometrijski ključ niti podaci za prijavu uređaja.</string>
|
||||||
<string name="registration_mode">Modus registracije</string>
|
<string name="registration_mode">Modus registracije</string>
|
||||||
|
|||||||
@@ -464,7 +464,6 @@
|
|||||||
<string name="database_data_remove_unlinked_attachments_summary">Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez</string>
|
<string name="database_data_remove_unlinked_attachments_summary">Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez</string>
|
||||||
<string name="database_data_remove_unlinked_attachments_title">Nem összekapcsolt adatok eltávolítása</string>
|
<string name="database_data_remove_unlinked_attachments_title">Nem összekapcsolt adatok eltávolítása</string>
|
||||||
<string name="data">Adatok</string>
|
<string name="data">Adatok</string>
|
||||||
<string name="crypto_object_not_initialized">A titkosítási objektum nem kérhető le.</string>
|
|
||||||
<string name="biometric_security_update_required">Biometrikus biztonsági frissítés szükséges.</string>
|
<string name="biometric_security_update_required">Biometrikus biztonsági frissítés szükséges.</string>
|
||||||
<string name="configure_biometric">Nincs biometrikus vagy eszközazonosító beállítva.</string>
|
<string name="configure_biometric">Nincs biometrikus vagy eszközazonosító beállítva.</string>
|
||||||
<string name="warning_empty_keyfile_explanation">A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.</string>
|
<string name="warning_empty_keyfile_explanation">A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.</string>
|
||||||
|
|||||||
@@ -506,7 +506,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Prova a salvare le informazioni condivise quando effettui una selezione di immissione manuale</string>
|
<string name="keyboard_save_search_info_summary">Prova a salvare le informazioni condivise quando effettui una selezione di immissione manuale</string>
|
||||||
<string name="keyboard_save_search_info_title">Salva le informazioni condivise</string>
|
<string name="keyboard_save_search_info_title">Salva le informazioni condivise</string>
|
||||||
<string name="notification">Notifica</string>
|
<string name="notification">Notifica</string>
|
||||||
<string name="crypto_object_not_initialized">Impossibile recuperare l\'oggetto crittografico.</string>
|
|
||||||
<string name="biometric_security_update_required">È necessario un aggiornamento della sicurezza biometrica.</string>
|
<string name="biometric_security_update_required">È necessario un aggiornamento della sicurezza biometrica.</string>
|
||||||
<string name="warning_empty_recycle_bin">Eliminare definitivamente tutti i nodi dal cestino\?</string>
|
<string name="warning_empty_recycle_bin">Eliminare definitivamente tutti i nodi dal cestino\?</string>
|
||||||
<string name="registration_mode">Modalità registrazione</string>
|
<string name="registration_mode">Modalità registrazione</string>
|
||||||
|
|||||||
@@ -273,7 +273,6 @@
|
|||||||
<string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string>
|
<string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string>
|
||||||
<string name="encrypted_value_stored">保存された暗号化済みパスワード</string>
|
<string name="encrypted_value_stored">保存された暗号化済みパスワード</string>
|
||||||
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
|
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
|
||||||
<string name="crypto_object_not_initialized">crypto オブジェクトを取得できません。</string>
|
|
||||||
<string name="database_history">履歴</string>
|
<string name="database_history">履歴</string>
|
||||||
<string name="menu_appearance_settings">デザイン</string>
|
<string name="menu_appearance_settings">デザイン</string>
|
||||||
<string name="biometric">生体認証</string>
|
<string name="biometric">生体認証</string>
|
||||||
|
|||||||
@@ -505,7 +505,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Probeer gedeelde informatie op te slaan bij een handmatige invoerselectie</string>
|
<string name="keyboard_save_search_info_summary">Probeer gedeelde informatie op te slaan bij een handmatige invoerselectie</string>
|
||||||
<string name="keyboard_save_search_info_title">Gedeelde info opslaan</string>
|
<string name="keyboard_save_search_info_title">Gedeelde info opslaan</string>
|
||||||
<string name="notification">Melding</string>
|
<string name="notification">Melding</string>
|
||||||
<string name="crypto_object_not_initialized">Kan crypto-object niet ophalen.</string>
|
|
||||||
<string name="biometric_security_update_required">Biometrische beveiligingsupdate vereist.</string>
|
<string name="biometric_security_update_required">Biometrische beveiligingsupdate vereist.</string>
|
||||||
<string name="configure_biometric">Geen biometrische gegevens of apparaatgegevens geregistreerd.</string>
|
<string name="configure_biometric">Geen biometrische gegevens of apparaatgegevens geregistreerd.</string>
|
||||||
<string name="warning_empty_recycle_bin">Alles definitief uit de prullenbak verwijderen\?</string>
|
<string name="warning_empty_recycle_bin">Alles definitief uit de prullenbak verwijderen\?</string>
|
||||||
|
|||||||
@@ -503,7 +503,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Spróbuj zapisać udostępnione informacje podczas ręcznego wybierania pozycji</string>
|
<string name="keyboard_save_search_info_summary">Spróbuj zapisać udostępnione informacje podczas ręcznego wybierania pozycji</string>
|
||||||
<string name="keyboard_save_search_info_title">Zapisz udostępnione informacje</string>
|
<string name="keyboard_save_search_info_title">Zapisz udostępnione informacje</string>
|
||||||
<string name="notification">Powiadomienia</string>
|
<string name="notification">Powiadomienia</string>
|
||||||
<string name="crypto_object_not_initialized">Nie można pobrać obiektu kryptograficznego.</string>
|
|
||||||
<string name="biometric_security_update_required">Wymagana aktualizacja zabezpieczeń biometrycznych.</string>
|
<string name="biometric_security_update_required">Wymagana aktualizacja zabezpieczeń biometrycznych.</string>
|
||||||
<string name="configure_biometric">Nie zarejestrowano żadnych danych biometrycznych ani danych urządzenia.</string>
|
<string name="configure_biometric">Nie zarejestrowano żadnych danych biometrycznych ani danych urządzenia.</string>
|
||||||
<string name="warning_empty_recycle_bin">Trwale usunąć wszystkie węzły z kosza\?</string>
|
<string name="warning_empty_recycle_bin">Trwale usunąć wszystkie węzły z kosza\?</string>
|
||||||
|
|||||||
@@ -494,7 +494,6 @@
|
|||||||
<string name="autofill_save_search_info_title">Сохранять данные поиска</string>
|
<string name="autofill_save_search_info_title">Сохранять данные поиска</string>
|
||||||
<string name="keyboard_save_search_info_summary">Сохранять общую информацию при ручном выборе записи</string>
|
<string name="keyboard_save_search_info_summary">Сохранять общую информацию при ручном выборе записи</string>
|
||||||
<string name="keyboard_save_search_info_title">Сохранять общие данные</string>
|
<string name="keyboard_save_search_info_title">Сохранять общие данные</string>
|
||||||
<string name="crypto_object_not_initialized">Невозможно получить доступ к зашифрованному объекту.</string>
|
|
||||||
<string name="keyboard_previous_lock_title">Блокировка базы</string>
|
<string name="keyboard_previous_lock_title">Блокировка базы</string>
|
||||||
<string name="keyboard_previous_lock_summary">Автоматически переключаться на предыдущую клавиатуру после блокировки базы</string>
|
<string name="keyboard_previous_lock_summary">Автоматически переключаться на предыдущую клавиатуру после блокировки базы</string>
|
||||||
<string name="show_uuid_summary">Показывать UUID, связанный с записью</string>
|
<string name="show_uuid_summary">Показывать UUID, связанный с записью</string>
|
||||||
|
|||||||
@@ -486,7 +486,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Elle girdi seçimi yaparken paylaşılan bilgileri kaydetmeyi dene</string>
|
<string name="keyboard_save_search_info_summary">Elle girdi seçimi yaparken paylaşılan bilgileri kaydetmeyi dene</string>
|
||||||
<string name="keyboard_save_search_info_title">Paylaşılan bilgileri kaydet</string>
|
<string name="keyboard_save_search_info_title">Paylaşılan bilgileri kaydet</string>
|
||||||
<string name="notification">Bildirim</string>
|
<string name="notification">Bildirim</string>
|
||||||
<string name="crypto_object_not_initialized">Şifreleme nesnesi alınamıyor.</string>
|
|
||||||
<string name="biometric_security_update_required">Biyometrik güvenlik güncellemesi gerekli.</string>
|
<string name="biometric_security_update_required">Biyometrik güvenlik güncellemesi gerekli.</string>
|
||||||
<string name="configure_biometric">Biyometrik bilgiler veya aygıt kimlik doğrulama bilgileri kaydedilmedi.</string>
|
<string name="configure_biometric">Biyometrik bilgiler veya aygıt kimlik doğrulama bilgileri kaydedilmedi.</string>
|
||||||
<string name="warning_empty_recycle_bin">Geri dönüşüm kutusundaki tüm düğümler kalıcı olarak silinsin mi\?</string>
|
<string name="warning_empty_recycle_bin">Geri dönüşüm kutusundaki tüm düğümler kalıcı olarak silinsin mi\?</string>
|
||||||
|
|||||||
@@ -503,7 +503,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">Намагатися зберегти спільні відомості під час вибору запису власноруч</string>
|
<string name="keyboard_save_search_info_summary">Намагатися зберегти спільні відомості під час вибору запису власноруч</string>
|
||||||
<string name="keyboard_save_search_info_title">Збереження спільних відомостей</string>
|
<string name="keyboard_save_search_info_title">Збереження спільних відомостей</string>
|
||||||
<string name="notification">Сповіщення</string>
|
<string name="notification">Сповіщення</string>
|
||||||
<string name="crypto_object_not_initialized">Не вдалося отримати крипто-об\'єкт.</string>
|
|
||||||
<string name="biometric_security_update_required">Необхідно оновити біометричний захист.</string>
|
<string name="biometric_security_update_required">Необхідно оновити біометричний захист.</string>
|
||||||
<string name="configure_biometric">Біометричних чи облікових даних пристрою не зареєстровано.</string>
|
<string name="configure_biometric">Біометричних чи облікових даних пристрою не зареєстровано.</string>
|
||||||
<string name="warning_empty_recycle_bin">Видалити всі вузли з кошика остаточно\?</string>
|
<string name="warning_empty_recycle_bin">Видалити всі вузли з кошика остаточно\?</string>
|
||||||
|
|||||||
@@ -503,7 +503,6 @@
|
|||||||
<string name="keyboard_save_search_info_summary">手动选择条目时,尝试保存共享信息</string>
|
<string name="keyboard_save_search_info_summary">手动选择条目时,尝试保存共享信息</string>
|
||||||
<string name="keyboard_save_search_info_title">保存分享的信息</string>
|
<string name="keyboard_save_search_info_title">保存分享的信息</string>
|
||||||
<string name="notification">通知</string>
|
<string name="notification">通知</string>
|
||||||
<string name="crypto_object_not_initialized">无法检索加密对象。</string>
|
|
||||||
<string name="biometric_security_update_required">需要生物识别安全更新。</string>
|
<string name="biometric_security_update_required">需要生物识别安全更新。</string>
|
||||||
<string name="configure_biometric">未登记生物识别或设备凭证。</string>
|
<string name="configure_biometric">未登记生物识别或设备凭证。</string>
|
||||||
<string name="warning_empty_recycle_bin">从回收站永久删除所有节点?</string>
|
<string name="warning_empty_recycle_bin">从回收站永久删除所有节点?</string>
|
||||||
|
|||||||
@@ -287,7 +287,6 @@
|
|||||||
<string name="advanced_unlock_scanning_error">Advanced unlock error: %1$s</string>
|
<string name="advanced_unlock_scanning_error">Advanced unlock error: %1$s</string>
|
||||||
<string name="no_credentials_stored">This database does not have stored credential yet.</string>
|
<string name="no_credentials_stored">This database does not have stored credential yet.</string>
|
||||||
<string name="advanced_unlock_prompt_not_initialized">Unable to initialize advanced unlock prompt.</string>
|
<string name="advanced_unlock_prompt_not_initialized">Unable to initialize advanced unlock prompt.</string>
|
||||||
<string name="crypto_object_not_initialized">Unable to retrieve crypto object.</string>
|
|
||||||
<string name="credential_before_click_advanced_unlock_button">Type in the password, and then click the \"Advanced unlock\" button.</string>
|
<string name="credential_before_click_advanced_unlock_button">Type in the password, and then click the \"Advanced unlock\" button.</string>
|
||||||
<string name="database_history">History</string>
|
<string name="database_history">History</string>
|
||||||
<string name="menu_appearance_settings">Appearance</string>
|
<string name="menu_appearance_settings">Appearance</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user