mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Hardware key #2196
This commit is contained in:
@@ -178,19 +178,22 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.settings.AppearanceSettingsActivity" />
|
android:name="com.kunzisoft.keepass.settings.AppearanceSettingsActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.hardware.HardwareKeyActivity"
|
android:name="com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity"
|
||||||
android:theme="@style/Theme.Transparent"
|
android:theme="@style/Theme.Transparent"
|
||||||
android:exported="false" />
|
android:exported="false"
|
||||||
|
android:excludeFromRecents="true" />
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.credentialprovider.activity.AutofillLauncherActivity"
|
android:name="com.kunzisoft.keepass.credentialprovider.activity.AutofillLauncherActivity"
|
||||||
android:theme="@style/Theme.Transparent"
|
android:theme="@style/Theme.Transparent"
|
||||||
android:configChanges="keyboardHidden"
|
android:configChanges="keyboardHidden"
|
||||||
android:excludeFromRecents="true"/>
|
android:exported="false"
|
||||||
|
android:excludeFromRecents="true" />
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.credentialprovider.activity.EntrySelectionLauncherActivity"
|
android:name="com.kunzisoft.keepass.credentialprovider.activity.EntrySelectionLauncherActivity"
|
||||||
android:theme="@style/Theme.Transparent"
|
android:theme="@style/Theme.Transparent"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:excludeFromRecents="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@@ -209,8 +212,8 @@
|
|||||||
android:name="com.kunzisoft.keepass.credentialprovider.activity.PasskeyLauncherActivity"
|
android:name="com.kunzisoft.keepass.credentialprovider.activity.PasskeyLauncherActivity"
|
||||||
android:theme="@style/Theme.Transparent"
|
android:theme="@style/Theme.Transparent"
|
||||||
android:configChanges="keyboardHidden"
|
android:configChanges="keyboardHidden"
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
tools:targetApi="upside_down_cake" />
|
tools:targetApi="upside_down_cake" />
|
||||||
<service
|
<service
|
||||||
android:name="com.kunzisoft.keepass.services.DatabaseTaskNotificationService"
|
android:name="com.kunzisoft.keepass.services.DatabaseTaskNotificationService"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
|||||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||||
import com.kunzisoft.keepass.database.MainCredential
|
import com.kunzisoft.keepass.database.MainCredential
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKeyActivity
|
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
|
||||||
import com.kunzisoft.keepass.password.PasswordEntropy
|
import com.kunzisoft.keepass.password.PasswordEntropy
|
||||||
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
|
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
|
||||||
import com.kunzisoft.keepass.utils.UriUtil.openUrl
|
import com.kunzisoft.keepass.utils.UriUtil.openUrl
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
package com.kunzisoft.keepass.hardware
|
package com.kunzisoft.keepass.credentialprovider.activity
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.activity.result.ActivityResultCallback
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.HardwareKeyLauncherViewModel
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.HardwareKeyLauncherViewModel.Companion.addHardwareKey
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.HardwareKeyLauncherViewModel.Companion.addSeed
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.HardwareKeyLauncherViewModel.Companion.buildHardwareKeyChallenge
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.HardwareKeyLauncherViewModel.Companion.isYubikeyDriverAvailable
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||||
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.utils.AppUtil.openExternalApp
|
import com.kunzisoft.keepass.utils.AppUtil.openExternalApp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special activity to deal with hardware key drivers,
|
* Special activity to deal with hardware key drivers,
|
||||||
@@ -22,23 +30,12 @@ import com.kunzisoft.keepass.utils.AppUtil.openExternalApp
|
|||||||
*/
|
*/
|
||||||
class HardwareKeyActivity: DatabaseModeActivity(){
|
class HardwareKeyActivity: DatabaseModeActivity(){
|
||||||
|
|
||||||
// To manage hardware key challenge response
|
private val mHardwareKeyLauncherViewModel: HardwareKeyLauncherViewModel by viewModels()
|
||||||
private val resultCallback = ActivityResultCallback<ActivityResult> { result ->
|
|
||||||
if (result.resultCode == RESULT_OK) {
|
|
||||||
val challengeResponse: ByteArray? = result.data?.getByteArrayExtra(HARDWARE_KEY_RESPONSE_KEY)
|
|
||||||
Log.d(TAG, "Response form challenge")
|
|
||||||
mDatabaseViewModel.onChallengeResponded(challengeResponse)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Response from challenge error")
|
|
||||||
mDatabaseViewModel.onChallengeResponded(null)
|
|
||||||
}
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var activityResultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
|
private var activityResultLauncher: ActivityResultLauncher<Intent> =
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
resultCallback
|
mHardwareKeyLauncherViewModel.manageSelectionResult(it)
|
||||||
)
|
}
|
||||||
|
|
||||||
override fun applyCustomStyle(): Boolean {
|
override fun applyCustomStyle(): Boolean {
|
||||||
return false
|
return false
|
||||||
@@ -48,65 +45,60 @@ class HardwareKeyActivity: DatabaseModeActivity(){
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDatabaseRetrieved(database: ContextualDatabase) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
val hardwareKey = HardwareKey.getHardwareKeyFromString(
|
super.onCreate(savedInstanceState)
|
||||||
intent.getStringExtra(DATA_HARDWARE_KEY)
|
|
||||||
)
|
lifecycleScope.launch {
|
||||||
if (isHardwareKeyAvailable(this, hardwareKey, true) {
|
mHardwareKeyLauncherViewModel.uiState.collect { uiState ->
|
||||||
mDatabaseViewModel.onChallengeResponded(null)
|
when (uiState) {
|
||||||
}) {
|
is HardwareKeyLauncherViewModel.UIState.Loading -> {}
|
||||||
when (hardwareKey) {
|
is HardwareKeyLauncherViewModel.UIState.LaunchChallengeActivityForResponse -> {
|
||||||
/*
|
// Send to the driver
|
||||||
HardwareKey.FIDO2_SECRET -> {
|
activityResultLauncher.launch(
|
||||||
// TODO FIDO2 under development
|
buildHardwareKeyChallenge(uiState.challenge)
|
||||||
throw Exception("FIDO2 not implemented")
|
)
|
||||||
}
|
}
|
||||||
*/
|
is HardwareKeyLauncherViewModel.UIState.OnChallengeResponded -> {
|
||||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
mDatabaseViewModel.onChallengeResponded(uiState.response)
|
||||||
launchYubikeyChallengeForResponse(intent.getByteArrayExtra(DATA_SEED))
|
}
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchYubikeyChallengeForResponse(seed: ByteArray?) {
|
override fun onDatabaseRetrieved(database: ContextualDatabase) {
|
||||||
// Transform the seed before sending
|
super.onDatabaseRetrieved(database)
|
||||||
var challenge: ByteArray? = null
|
mHardwareKeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
||||||
if (seed != null) {
|
}
|
||||||
challenge = ByteArray(64)
|
|
||||||
seed.copyInto(challenge, 0, 0, 32)
|
override fun onDatabaseActionFinished(
|
||||||
challenge.fill(32, 32, 64)
|
database: ContextualDatabase,
|
||||||
}
|
actionTask: String,
|
||||||
// Send to the driver
|
result: ActionRunnable.Result
|
||||||
activityResultLauncher.launch(
|
) {
|
||||||
Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT).apply {
|
super.onDatabaseActionFinished(database, actionTask, result)
|
||||||
putExtra(HARDWARE_KEY_CHALLENGE_KEY, challenge)
|
when (actionTask) {
|
||||||
|
ACTION_DATABASE_LOAD_TASK -> {
|
||||||
|
finish()
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
Log.d(TAG, "Challenge sent")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = HardwareKeyActivity::class.java.simpleName
|
private val TAG = HardwareKeyActivity::class.java.simpleName
|
||||||
|
|
||||||
private const val DATA_HARDWARE_KEY = "DATA_HARDWARE_KEY"
|
|
||||||
private const val DATA_SEED = "DATA_SEED"
|
|
||||||
private const val YUBIKEY_CHALLENGE_RESPONSE_INTENT = "android.yubikey.intent.action.CHALLENGE_RESPONSE"
|
|
||||||
private const val HARDWARE_KEY_CHALLENGE_KEY = "challenge"
|
|
||||||
private const val HARDWARE_KEY_RESPONSE_KEY = "response"
|
|
||||||
|
|
||||||
fun launchHardwareKeyActivity(
|
fun launchHardwareKeyActivity(
|
||||||
context: Context,
|
context: Context,
|
||||||
hardwareKey: HardwareKey,
|
hardwareKey: HardwareKey,
|
||||||
seed: ByteArray?
|
seed: ByteArray?
|
||||||
) {
|
) {
|
||||||
context.startActivity(Intent(context, HardwareKeyActivity::class.java).apply {
|
context.startActivity(
|
||||||
flags = FLAG_ACTIVITY_NEW_TASK
|
Intent(
|
||||||
putExtra(DATA_HARDWARE_KEY, hardwareKey.value)
|
context,
|
||||||
putExtra(DATA_SEED, seed)
|
HardwareKeyActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
addHardwareKey(hardwareKey)
|
||||||
|
addSeed(seed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,15 +122,14 @@ class HardwareKeyActivity: DatabaseModeActivity(){
|
|||||||
*/
|
*/
|
||||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||||
// Check available intent
|
// Check available intent
|
||||||
val yubikeyDriverAvailable =
|
// TODO Dialog
|
||||||
Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT)
|
val yubikeyDriverAvailable = isYubikeyDriverAvailable(context)
|
||||||
.resolveActivity(context.packageManager) != null
|
if (showDialog && !yubikeyDriverAvailable && context is Activity) {
|
||||||
if (showDialog && !yubikeyDriverAvailable
|
|
||||||
&& context is Activity)
|
|
||||||
showHardwareKeyDriverNeeded(context, hardwareKey) {
|
showHardwareKeyDriverNeeded(context, hardwareKey) {
|
||||||
onDialogDismissed?.onDismiss(it)
|
onDialogDismissed?.onDismiss(it)
|
||||||
context.finish()
|
context.finish()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
yubikeyDriverAvailable
|
yubikeyDriverAvailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
|
|||||||
mSelectionResult = null
|
mSelectionResult = null
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun manageRegistrationResult(activityResult: ActivityResult)
|
open fun manageRegistrationResult(activityResult: ActivityResult) {}
|
||||||
|
|
||||||
open fun onExceptionOccurred(e: Throwable) {
|
open fun onExceptionOccurred(e: Throwable) {
|
||||||
showError(e)
|
showError(e)
|
||||||
@@ -93,7 +93,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
|
|||||||
specialMode: SpecialMode,
|
specialMode: SpecialMode,
|
||||||
database: ContextualDatabase?
|
database: ContextualDatabase?
|
||||||
) {
|
) {
|
||||||
if (database != null && database.loaded) {
|
if (database != null) {
|
||||||
onDatabaseRetrieved(database)
|
onDatabaseRetrieved(database)
|
||||||
}
|
}
|
||||||
if (isResultLauncherRegistered.not()) {
|
if (isResultLauncherRegistered.not()) {
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.kunzisoft.keepass.credentialprovider.viewmodel
|
||||||
|
|
||||||
|
import android.app.Activity.RESULT_OK
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity.Companion.isHardwareKeyAvailable
|
||||||
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
class HardwareKeyLauncherViewModel(application: Application): CredentialLauncherViewModel(application) {
|
||||||
|
|
||||||
|
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||||
|
val uiState: StateFlow<UIState> = mUiState
|
||||||
|
|
||||||
|
override suspend fun launchAction(
|
||||||
|
intent: Intent,
|
||||||
|
specialMode: SpecialMode,
|
||||||
|
database: ContextualDatabase?
|
||||||
|
) {
|
||||||
|
val hardwareKey = HardwareKey.Companion.getHardwareKeyFromString(
|
||||||
|
intent.getStringExtra(DATA_HARDWARE_KEY)
|
||||||
|
)
|
||||||
|
if (isHardwareKeyAvailable(getApplication(), hardwareKey, true) {
|
||||||
|
mUiState.value = UIState.OnChallengeResponded(null)
|
||||||
|
}) {
|
||||||
|
when (hardwareKey) {
|
||||||
|
/*
|
||||||
|
HardwareKey.FIDO2_SECRET -> {
|
||||||
|
// TODO FIDO2 under development
|
||||||
|
throw Exception("FIDO2 not implemented")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||||
|
launchYubikeyChallengeForResponse(intent.getByteArrayExtra(DATA_SEED))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
UIState.OnChallengeResponded(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchYubikeyChallengeForResponse(seed: ByteArray?) {
|
||||||
|
// Transform the seed before sending
|
||||||
|
var challenge: ByteArray? = null
|
||||||
|
if (seed != null) {
|
||||||
|
challenge = ByteArray(64)
|
||||||
|
seed.copyInto(challenge, 0, 0, 32)
|
||||||
|
challenge.fill(32, 32, 64)
|
||||||
|
}
|
||||||
|
mUiState.value = UIState.LaunchChallengeActivityForResponse(challenge)
|
||||||
|
Log.d(TAG, "Challenge sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun manageSelectionResult(
|
||||||
|
database: ContextualDatabase,
|
||||||
|
activityResult: ActivityResult
|
||||||
|
) {
|
||||||
|
super.manageSelectionResult(database, activityResult)
|
||||||
|
|
||||||
|
if (activityResult.resultCode == RESULT_OK) {
|
||||||
|
val challengeResponse: ByteArray? =
|
||||||
|
activityResult.data?.getByteArrayExtra(HARDWARE_KEY_RESPONSE_KEY)
|
||||||
|
Log.d(TAG, "Response form challenge")
|
||||||
|
mUiState.value = UIState.OnChallengeResponded(challengeResponse)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Response from challenge error")
|
||||||
|
mUiState.value = UIState.OnChallengeResponded(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class UIState {
|
||||||
|
object Loading : UIState()
|
||||||
|
data class LaunchChallengeActivityForResponse(
|
||||||
|
val challenge: ByteArray?,
|
||||||
|
): UIState() {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as LaunchChallengeActivityForResponse
|
||||||
|
|
||||||
|
return challenge.contentEquals(other.challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return challenge?.contentHashCode() ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data class OnChallengeResponded(
|
||||||
|
val response: ByteArray?
|
||||||
|
): UIState() {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as OnChallengeResponded
|
||||||
|
|
||||||
|
return response.contentEquals(other.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return response?.contentHashCode() ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = HardwareKeyLauncherViewModel::class.java.name
|
||||||
|
|
||||||
|
private const val DATA_HARDWARE_KEY = "DATA_HARDWARE_KEY"
|
||||||
|
private const val DATA_SEED = "DATA_SEED"
|
||||||
|
|
||||||
|
// Driver call
|
||||||
|
private const val YUBIKEY_CHALLENGE_RESPONSE_INTENT = "android.yubikey.intent.action.CHALLENGE_RESPONSE"
|
||||||
|
private const val HARDWARE_KEY_CHALLENGE_KEY = "challenge"
|
||||||
|
private const val HARDWARE_KEY_RESPONSE_KEY = "response"
|
||||||
|
|
||||||
|
fun isYubikeyDriverAvailable(context: Context): Boolean {
|
||||||
|
return Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT)
|
||||||
|
.resolveActivity(context.packageManager) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildHardwareKeyChallenge(challenge: ByteArray?): Intent {
|
||||||
|
return Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT).apply {
|
||||||
|
putExtra(HARDWARE_KEY_CHALLENGE_KEY, challenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Intent.addHardwareKey(hardwareKey: HardwareKey) {
|
||||||
|
putExtra(DATA_HARDWARE_KEY, hardwareKey.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Intent.addSeed(seed: ByteArray?) {
|
||||||
|
putExtra(DATA_SEED, seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ import com.kunzisoft.keepass.database.element.node.Node
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKeyActivity
|
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
|
||||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
|||||||
Reference in New Issue
Block a user