mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Change main credential validation
This commit is contained in:
@@ -36,6 +36,7 @@ import com.kunzisoft.keepass.R
|
|||||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
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.hardware.HardwareKey
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
|
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||||
import com.kunzisoft.keepass.model.MainCredential
|
import com.kunzisoft.keepass.model.MainCredential
|
||||||
import com.kunzisoft.keepass.password.PasswordEntropy
|
import com.kunzisoft.keepass.password.PasswordEntropy
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
@@ -53,7 +54,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
private lateinit var rootView: View
|
private lateinit var rootView: View
|
||||||
|
|
||||||
private lateinit var passwordCheckBox: CompoundButton
|
private lateinit var passwordCheckBox: CompoundButton
|
||||||
private lateinit var passKeyView: PassKeyView
|
private lateinit var passwordView: PassKeyView
|
||||||
private lateinit var passwordRepeatTextInputLayout: TextInputLayout
|
private lateinit var passwordRepeatTextInputLayout: TextInputLayout
|
||||||
private lateinit var passwordRepeatView: TextView
|
private lateinit var passwordRepeatView: TextView
|
||||||
|
|
||||||
@@ -139,7 +140,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
passwordCheckBox = rootView.findViewById(R.id.password_checkbox)
|
passwordCheckBox = rootView.findViewById(R.id.password_checkbox)
|
||||||
passKeyView = rootView.findViewById(R.id.password_view)
|
passwordView = rootView.findViewById(R.id.password_view)
|
||||||
passwordRepeatTextInputLayout = rootView.findViewById(R.id.password_repeat_input_layout)
|
passwordRepeatTextInputLayout = rootView.findViewById(R.id.password_repeat_input_layout)
|
||||||
passwordRepeatView = rootView.findViewById(R.id.password_confirmation)
|
passwordRepeatView = rootView.findViewById(R.id.password_confirmation)
|
||||||
passwordRepeatView.applyFontVisibility()
|
passwordRepeatView.applyFontVisibility()
|
||||||
@@ -165,8 +166,15 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
}
|
}
|
||||||
keyFileSelectionView.setOpenDocumentClickListener(mExternalFileHelper)
|
keyFileSelectionView.setOpenDocumentClickListener(mExternalFileHelper)
|
||||||
|
|
||||||
hardwareKeySelectionView.selectionListener = { _ ->
|
hardwareKeySelectionView.selectionListener = { hardwareKey ->
|
||||||
hardwareKeyCheckBox.isChecked = true
|
hardwareKeyCheckBox.isChecked = true
|
||||||
|
hardwareKeySelectionView.error =
|
||||||
|
if (!HardwareKeyResponseHelper.isHardwareKeyAvailable(requireActivity(), hardwareKey)) {
|
||||||
|
// show hardware driver dialog if required
|
||||||
|
getString(R.string.warning_hardware_key_required)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
@@ -194,18 +202,41 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun approveMainCredential() {
|
private fun approveMainCredential() {
|
||||||
var error = verifyPassword() || verifyKeyFile() || verifyHardwareKey()
|
val errorPassword = verifyPassword()
|
||||||
if (!passwordCheckBox.isChecked
|
val errorKeyFile = verifyKeyFile()
|
||||||
&& !keyFileCheckBox.isChecked
|
val errorHardwareKey = verifyHardwareKey()
|
||||||
&& !hardwareKeyCheckBox.isChecked
|
// Check all to fill error
|
||||||
|
var error = errorPassword || errorKeyFile || errorHardwareKey
|
||||||
|
val hardwareKey = hardwareKeySelectionView.hardwareKey
|
||||||
|
if (!error
|
||||||
|
&& (!passwordCheckBox.isChecked)
|
||||||
|
&& (!keyFileCheckBox.isChecked)
|
||||||
|
&& (!hardwareKeyCheckBox.isChecked)
|
||||||
) {
|
) {
|
||||||
error = true
|
error = true
|
||||||
if (mAllowNoMasterKey)
|
if (mAllowNoMasterKey) {
|
||||||
|
// show no key dialog if required
|
||||||
showNoKeyConfirmationDialog()
|
showNoKeyConfirmationDialog()
|
||||||
else {
|
} else {
|
||||||
passwordRepeatTextInputLayout.error =
|
passwordRepeatTextInputLayout.error =
|
||||||
getString(R.string.error_disallow_no_credentials)
|
getString(R.string.error_disallow_no_credentials)
|
||||||
}
|
}
|
||||||
|
} else if (!error
|
||||||
|
&& mMasterPassword.isNullOrEmpty()
|
||||||
|
&& !keyFileCheckBox.isChecked
|
||||||
|
&& !hardwareKeyCheckBox.isChecked
|
||||||
|
) {
|
||||||
|
// show empty password dialog if required
|
||||||
|
error = true
|
||||||
|
showEmptyPasswordConfirmationDialog()
|
||||||
|
} else if (!error
|
||||||
|
&& hardwareKey != null
|
||||||
|
&& !HardwareKeyResponseHelper.isHardwareKeyAvailable(
|
||||||
|
requireActivity(), hardwareKey, false)
|
||||||
|
) {
|
||||||
|
// show hardware driver dialog if required
|
||||||
|
error = true
|
||||||
|
hardwareKeySelectionView.error = getString(R.string.warning_hardware_key_required)
|
||||||
}
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
||||||
@@ -213,30 +244,11 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun retrieveMainCredential(): MainCredential {
|
|
||||||
val masterPassword = if (passwordCheckBox.isChecked) mMasterPassword else null
|
|
||||||
val keyFileUri = if (keyFileCheckBox.isChecked) mKeyFileUri else null
|
|
||||||
val hardwareKey = if (hardwareKeyCheckBox.isChecked) mHardwareKey else null
|
|
||||||
return MainCredential(masterPassword, keyFileUri, hardwareKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
// To check checkboxes if a text is present
|
|
||||||
passKeyView.addTextChangedListener(passwordTextWatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
|
|
||||||
passKeyView.removeTextChangedListener(passwordTextWatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun verifyPassword(): Boolean {
|
private fun verifyPassword(): Boolean {
|
||||||
var error = false
|
var error = false
|
||||||
|
passwordRepeatTextInputLayout.error = null
|
||||||
if (passwordCheckBox.isChecked) {
|
if (passwordCheckBox.isChecked) {
|
||||||
mMasterPassword = passKeyView.passwordString
|
mMasterPassword = passwordView.passwordString
|
||||||
val confPassword = passwordRepeatView.text.toString()
|
val confPassword = passwordRepeatView.text.toString()
|
||||||
|
|
||||||
// Verify that passwords match
|
// Verify that passwords match
|
||||||
@@ -245,21 +257,13 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
// Passwords do not match
|
// Passwords do not match
|
||||||
passwordRepeatTextInputLayout.error = getString(R.string.error_pass_match)
|
passwordRepeatTextInputLayout.error = getString(R.string.error_pass_match)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mMasterPassword.isNullOrEmpty())
|
|
||||||
&& (!keyFileCheckBox.isChecked
|
|
||||||
|| keyFileSelectionView.uri == null)
|
|
||||||
&& (!hardwareKeyCheckBox.isChecked
|
|
||||||
|| hardwareKeySelectionView.hardwareKey == null)) {
|
|
||||||
error = true
|
|
||||||
showEmptyPasswordConfirmationDialog()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyKeyFile(): Boolean {
|
private fun verifyKeyFile(): Boolean {
|
||||||
var error = false
|
var error = false
|
||||||
|
keyFileSelectionView.error = null
|
||||||
if (keyFileCheckBox.isChecked) {
|
if (keyFileCheckBox.isChecked) {
|
||||||
keyFileSelectionView.uri?.let { uri ->
|
keyFileSelectionView.uri?.let { uri ->
|
||||||
mKeyFileUri = uri
|
mKeyFileUri = uri
|
||||||
@@ -273,25 +277,45 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
|
|
||||||
private fun verifyHardwareKey(): Boolean {
|
private fun verifyHardwareKey(): Boolean {
|
||||||
var error = false
|
var error = false
|
||||||
|
hardwareKeySelectionView.error = null
|
||||||
if (hardwareKeyCheckBox.isChecked) {
|
if (hardwareKeyCheckBox.isChecked) {
|
||||||
hardwareKeySelectionView.hardwareKey.let { hardwareKey ->
|
hardwareKeySelectionView.hardwareKey?.let { hardwareKey ->
|
||||||
mHardwareKey = hardwareKey
|
mHardwareKey = hardwareKey
|
||||||
|
} ?: run {
|
||||||
|
error = true
|
||||||
|
hardwareKeySelectionView.error = getString(R.string.error_no_hardware_key)
|
||||||
}
|
}
|
||||||
// TODO verify drivers
|
|
||||||
// error = true
|
|
||||||
}
|
}
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun retrieveMainCredential(): MainCredential {
|
||||||
|
val masterPassword = if (passwordCheckBox.isChecked) mMasterPassword else null
|
||||||
|
val keyFileUri = if (keyFileCheckBox.isChecked) mKeyFileUri else null
|
||||||
|
val hardwareKey = if (hardwareKeyCheckBox.isChecked) mHardwareKey else null
|
||||||
|
return MainCredential(masterPassword, keyFileUri, hardwareKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
// To check checkboxes if a text is present
|
||||||
|
passwordView.addTextChangedListener(passwordTextWatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
passwordView.removeTextChangedListener(passwordTextWatcher)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showEmptyPasswordConfirmationDialog() {
|
private fun showEmptyPasswordConfirmationDialog() {
|
||||||
activity?.let {
|
activity?.let {
|
||||||
val builder = AlertDialog.Builder(it)
|
val builder = AlertDialog.Builder(it)
|
||||||
builder.setMessage(R.string.warning_empty_password)
|
builder.setMessage(R.string.warning_empty_password)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (!verifyKeyFile()) {
|
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
||||||
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
this@SetMainCredentialDialogFragment.dismiss()
|
||||||
this@SetMainCredentialDialogFragment.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
mEmptyPasswordConfirmationDialog = builder.create()
|
mEmptyPasswordConfirmationDialog = builder.create()
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
|||||||
import com.kunzisoft.keepass.database.element.node.Node
|
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.database.exception.InvalidCredentialsDatabaseException
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
@@ -84,7 +85,7 @@ import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
|
|||||||
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
|
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
|
||||||
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
||||||
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,8 +135,12 @@ class DatabaseTaskProvider {
|
|||||||
respondToChallengeIfAllowed()
|
respondToChallengeIfAllowed()
|
||||||
}
|
}
|
||||||
this.requestChallengeListener = object: DatabaseTaskNotificationService.RequestChallengeListener {
|
this.requestChallengeListener = object: DatabaseTaskNotificationService.RequestChallengeListener {
|
||||||
override fun onChallengeResponseRequested(hardwareKey: HardwareKey?, seed: ByteArray?) {
|
override fun onChallengeResponseRequested(hardwareKey: HardwareKey, seed: ByteArray?) {
|
||||||
mHardwareKeyResponseHelper?.launchChallengeForResponse(hardwareKey, seed)
|
if (HardwareKeyResponseHelper.isHardwareKeyAvailable(activity, hardwareKey)) {
|
||||||
|
mHardwareKeyResponseHelper?.launchChallengeForResponse(hardwareKey, seed)
|
||||||
|
} else {
|
||||||
|
throw InvalidCredentialsDatabaseException("Driver for $hardwareKey is required.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.ActivityResultCallback
|
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.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class HardwareKeyResponseHelper {
|
class HardwareKeyResponseHelper {
|
||||||
|
|
||||||
@@ -32,14 +36,13 @@ class HardwareKeyResponseHelper {
|
|||||||
fun buildHardwareKeyResponse(onChallengeResponded: (challengeResponse: ByteArray?,
|
fun buildHardwareKeyResponse(onChallengeResponded: (challengeResponse: ByteArray?,
|
||||||
extra: Bundle?) -> Unit) {
|
extra: Bundle?) -> Unit) {
|
||||||
val resultCallback = ActivityResultCallback<ActivityResult> { result ->
|
val resultCallback = ActivityResultCallback<ActivityResult> { result ->
|
||||||
Log.d(TAG, "resultCode from ykdroid: " + result.resultCode)
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
val challengeResponse: ByteArray? = result.data?.getByteArrayExtra("response")
|
val challengeResponse: ByteArray? = result.data?.getByteArrayExtra("response")
|
||||||
Log.d(TAG, "Response: " + challengeResponse.contentToString())
|
Log.d(TAG, "Response form challenge : " + challengeResponse.contentToString())
|
||||||
onChallengeResponded.invoke(challengeResponse,
|
onChallengeResponded.invoke(challengeResponse,
|
||||||
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Response error")
|
Log.e(TAG, "Response from challenge error")
|
||||||
onChallengeResponded.invoke(null,
|
onChallengeResponded.invoke(null,
|
||||||
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
||||||
}
|
}
|
||||||
@@ -58,39 +61,25 @@ class HardwareKeyResponseHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchChallengeForResponse(hardwareKey: HardwareKey?, seed: ByteArray?) {
|
fun launchChallengeForResponse(hardwareKey: HardwareKey, seed: ByteArray?) {
|
||||||
try {
|
when (hardwareKey) {
|
||||||
when (hardwareKey) {
|
HardwareKey.FIDO2_SECRET -> {
|
||||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
// TODO FIDO2
|
||||||
// Transform the seed before sending
|
throw Exception("FIDO2 not implemented")
|
||||||
var challenge: ByteArray? = null
|
|
||||||
if (seed != null) {
|
|
||||||
challenge = ByteArray(64)
|
|
||||||
seed.copyInto(challenge, 0, 0, 32)
|
|
||||||
challenge.fill(32, 32, 64)
|
|
||||||
}
|
|
||||||
// Send to the driver
|
|
||||||
getChallengeResponseResultLauncher?.launch(Intent(YKDROID_CHALLENGE_RESPONSE_INTENT).apply {
|
|
||||||
putExtra(YKDROID_SEED_KEY, challenge)
|
|
||||||
})
|
|
||||||
Log.d(TAG, "Challenge sent : " + challenge.contentToString())
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// TODO other algorithm
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||||
Log.e(
|
// Transform the seed before sending
|
||||||
TAG,
|
var challenge: ByteArray? = null
|
||||||
"Unable to retrieve the challenge response",
|
if (seed != null) {
|
||||||
e
|
challenge = ByteArray(64)
|
||||||
)
|
seed.copyInto(challenge, 0, 0, 32)
|
||||||
e.message?.let { message ->
|
challenge.fill(32, 32, 64)
|
||||||
Toast.makeText(
|
}
|
||||||
activity,
|
// Send to the driver
|
||||||
message,
|
getChallengeResponseResultLauncher!!.launch(Intent(YKDROID_CHALLENGE_RESPONSE_INTENT).apply {
|
||||||
Toast.LENGTH_LONG
|
putExtra(YKDROID_SEED_KEY, challenge)
|
||||||
).show()
|
})
|
||||||
|
Log.d(TAG, "Challenge sent : " + challenge.contentToString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,9 +87,39 @@ class HardwareKeyResponseHelper {
|
|||||||
companion object {
|
companion object {
|
||||||
private val TAG = HardwareKeyResponseHelper::class.java.simpleName
|
private val TAG = HardwareKeyResponseHelper::class.java.simpleName
|
||||||
|
|
||||||
private const val YKDROID_CHALLENGE_RESPONSE_INTENT = "net.pp3345.ykdroid.intent.action.CHALLENGE_RESPONSE"
|
private const val YKDROID_PACKAGE = "net.pp3345.ykdroid"
|
||||||
|
private const val YKDROID_CHALLENGE_RESPONSE_INTENT =
|
||||||
|
"$YKDROID_PACKAGE.intent.action.CHALLENGE_RESPONSE"
|
||||||
private const val YKDROID_SEED_KEY = "challenge"
|
private const val YKDROID_SEED_KEY = "challenge"
|
||||||
private const val EXTRA_BUNDLE_KEY = "EXTRA_BUNDLE_KEY"
|
private const val EXTRA_BUNDLE_KEY = "EXTRA_BUNDLE_KEY"
|
||||||
|
|
||||||
|
fun isHardwareKeyAvailable(activity: FragmentActivity,
|
||||||
|
hardwareKey: HardwareKey,
|
||||||
|
showDialog: Boolean = true): Boolean {
|
||||||
|
return when (hardwareKey) {
|
||||||
|
HardwareKey.FIDO2_SECRET -> {
|
||||||
|
// TODO FIDO2
|
||||||
|
if (showDialog)
|
||||||
|
showHardwareKeyDriverNeeded(activity)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||||
|
// TODO (UriUtil.isExternalAppInstalled(activity, KEEPASSDX_PRO_PACKAGE)
|
||||||
|
UriUtil.isExternalAppInstalled(activity, YKDROID_PACKAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showHardwareKeyDriverNeeded(activity: FragmentActivity) {
|
||||||
|
activity.lifecycleScope.launch {
|
||||||
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
builder.setMessage(R.string.warning_hardware_key_required)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
UriUtil.openExternalApp(activity, UriUtil.KEEPASSDX_PRO_PACKAGE)
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
builder.create().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
private var mActionTaskBinder = ActionTaskBinder()
|
private var mActionTaskBinder = ActionTaskBinder()
|
||||||
private var mActionTaskListeners = mutableListOf<ActionTaskListener>()
|
private var mActionTaskListeners = mutableListOf<ActionTaskListener>()
|
||||||
// Channel to connect asynchronously a listener or a response
|
// Channel to connect asynchronously a listener or a response
|
||||||
private var mRequestChallengeListenerChannel = Channel<RequestChallengeListener?>(0)
|
private var mRequestChallengeListenerChannel = Channel<RequestChallengeListener>(0)
|
||||||
private var mResponseChallengeChannel = Channel<ByteArray?>(0)
|
private var mResponseChallengeChannel = Channel<ByteArray?>(0)
|
||||||
|
|
||||||
private var mActionRunning = false
|
private var mActionRunning = false
|
||||||
@@ -156,7 +156,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RequestChallengeListener {
|
interface RequestChallengeListener {
|
||||||
fun onChallengeResponseRequested(hardwareKey: HardwareKey?, seed: ByteArray?)
|
fun onChallengeResponseRequested(hardwareKey: HardwareKey, seed: ByteArray?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkDatabase() {
|
fun checkDatabase() {
|
||||||
@@ -598,7 +598,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
// Send the request
|
// Send the request
|
||||||
val challengeResponseRequestListener = mRequestChallengeListenerChannel.receive()
|
val challengeResponseRequestListener = mRequestChallengeListenerChannel.receive()
|
||||||
challengeResponseRequestListener?.onChallengeResponseRequested(hardwareKey, seed)
|
challengeResponseRequestListener.onChallengeResponseRequested(hardwareKey, seed)
|
||||||
// Wait the response
|
// Wait the response
|
||||||
response = mResponseChallengeChannel.receive() ?: byteArrayOf()
|
response = mResponseChallengeChannel.receive() ?: byteArrayOf()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,11 +269,11 @@ object UriUtil {
|
|||||||
|
|
||||||
fun contributingUser(context: Context): Boolean {
|
fun contributingUser(context: Context): Boolean {
|
||||||
return (Education.isEducationScreenReclickedPerformed(context)
|
return (Education.isEducationScreenReclickedPerformed(context)
|
||||||
|| isExternalAppInstalled(context, "com.kunzisoft.keepass.pro", false)
|
|| isExternalAppInstalled(context, KEEPASSDX_PRO_PACKAGE, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isExternalAppInstalled(context: Context, packageName: String, showError: Boolean = true): Boolean {
|
fun isExternalAppInstalled(context: Context, packageName: String, showError: Boolean = true): Boolean {
|
||||||
try {
|
try {
|
||||||
context.applicationContext.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
|
context.applicationContext.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
|
||||||
Education.setEducationScreenReclickedPerformed(context)
|
Education.setEducationScreenReclickedPerformed(context)
|
||||||
@@ -317,4 +317,6 @@ object UriUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "UriUtil"
|
private const val TAG = "UriUtil"
|
||||||
|
|
||||||
|
const val KEEPASSDX_PRO_PACKAGE = "com.kunzisoft.keepass.pro"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import android.widget.ArrayAdapter
|
|||||||
import android.widget.Filter
|
import android.widget.Filter
|
||||||
import androidx.appcompat.widget.AppCompatAutoCompleteTextView
|
import androidx.appcompat.widget.AppCompatAutoCompleteTextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||||
import com.kunzisoft.keepass.utils.readEnum
|
import com.kunzisoft.keepass.utils.readEnum
|
||||||
@@ -25,8 +26,9 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
private var mHardwareKey: HardwareKey? = null
|
private var mHardwareKey: HardwareKey? = null
|
||||||
|
|
||||||
|
private val hardwareKeyLayout: TextInputLayout
|
||||||
private val hardwareKeyCompletion: AppCompatAutoCompleteTextView
|
private val hardwareKeyCompletion: AppCompatAutoCompleteTextView
|
||||||
var selectionListener: ((HardwareKey?)-> Unit)? = null
|
var selectionListener: ((HardwareKey)-> Unit)? = null
|
||||||
|
|
||||||
private val mHardwareKeyAdapter = ArrayAdapterNoFilter(context)
|
private val mHardwareKeyAdapter = ArrayAdapterNoFilter(context)
|
||||||
|
|
||||||
@@ -66,6 +68,7 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
|||||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||||
inflater?.inflate(R.layout.view_hardware_key_selection, this)
|
inflater?.inflate(R.layout.view_hardware_key_selection, this)
|
||||||
|
|
||||||
|
hardwareKeyLayout = findViewById(R.id.input_entry_hardware_key_layout)
|
||||||
hardwareKeyCompletion = findViewById(R.id.input_entry_hardware_key_completion)
|
hardwareKeyCompletion = findViewById(R.id.input_entry_hardware_key_completion)
|
||||||
|
|
||||||
hardwareKeyCompletion.inputType = InputType.TYPE_NULL
|
hardwareKeyCompletion.inputType = InputType.TYPE_NULL
|
||||||
@@ -74,7 +77,9 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
|||||||
hardwareKeyCompletion.onItemClickListener =
|
hardwareKeyCompletion.onItemClickListener =
|
||||||
AdapterView.OnItemClickListener { _, _, position, _ ->
|
AdapterView.OnItemClickListener { _, _, position, _ ->
|
||||||
mHardwareKey = HardwareKey.fromPosition(position)
|
mHardwareKey = HardwareKey.fromPosition(position)
|
||||||
selectionListener?.invoke(mHardwareKey)
|
mHardwareKey?.let { hardwareKey ->
|
||||||
|
selectionListener?.invoke(hardwareKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +93,12 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
|||||||
hardwareKeyCompletion.setSelection(value.ordinal)
|
hardwareKeyCompletion.setSelection(value.ordinal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var error: CharSequence?
|
||||||
|
get() = hardwareKeyLayout.error
|
||||||
|
set(value) {
|
||||||
|
hardwareKeyLayout.error = value
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(): Parcelable {
|
override fun onSaveInstanceState(): Parcelable {
|
||||||
val superState = super.onSaveInstanceState()
|
val superState = super.onSaveInstanceState()
|
||||||
val saveState = SavedState(superState)
|
val saveState = SavedState(superState)
|
||||||
|
|||||||
@@ -107,9 +107,19 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
|||||||
onPasswordChecked?.onCheckedChanged(view, checked)
|
onPasswordChecked?.onCheckedChanged(view, checked)
|
||||||
}
|
}
|
||||||
checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
|
checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
|
||||||
|
if (checked) {
|
||||||
|
if (keyFileSelectionView.uri == null) {
|
||||||
|
checkboxKeyFileView.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
onKeyFileChecked?.onCheckedChanged(view, checked)
|
onKeyFileChecked?.onCheckedChanged(view, checked)
|
||||||
}
|
}
|
||||||
checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
|
checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
|
||||||
|
if (checked) {
|
||||||
|
if (hardwareKeySelectionView.hardwareKey == null) {
|
||||||
|
checkboxHardwareView.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
onHardwareKeyChecked?.onCheckedChanged(view, checked)
|
onHardwareKeyChecked?.onCheckedChanged(view, checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +162,8 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
fun isFill(): Boolean {
|
fun isFill(): Boolean {
|
||||||
return checkboxPasswordView.isChecked
|
return checkboxPasswordView.isChecked
|
||||||
|| checkboxKeyFileView.isChecked // TODO better recognition
|
|| (checkboxKeyFileView.isChecked && keyFileSelectionView.uri != null)
|
||||||
|| checkboxHardwareView.isChecked
|
|| (checkboxHardwareView.isChecked && hardwareKeySelectionView.hardwareKey != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMainCredential(): MainCredential {
|
fun getMainCredential(): MainCredential {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/KeepassDXStyle.TextInputLayout.ExposedMenu"
|
style="@style/KeepassDXStyle.TextInputLayout.ExposedMenu"
|
||||||
android:id="@+id/input_entry_hardware_key"
|
android:id="@+id/input_entry_hardware_key_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/hardware_key">
|
android:hint="@string/hardware_key">
|
||||||
|
|||||||
@@ -161,6 +161,7 @@
|
|||||||
<string name="error_no_name">Enter a name.</string>
|
<string name="error_no_name">Enter a name.</string>
|
||||||
<string name="error_word_reserved">This word is reserved and cannot be used.</string>
|
<string name="error_word_reserved">This word is reserved and cannot be used.</string>
|
||||||
<string name="error_nokeyfile">Select a keyfile.</string>
|
<string name="error_nokeyfile">Select a keyfile.</string>
|
||||||
|
<string name="error_no_hardware_key">Select a hardware key.</string>
|
||||||
<string name="error_out_of_memory">No memory to load your entire database.</string>
|
<string name="error_out_of_memory">No memory to load your entire database.</string>
|
||||||
<string name="error_load_database">Could not load your database.</string>
|
<string name="error_load_database">Could not load your database.</string>
|
||||||
<string name="error_load_database_KDF_memory">Could not load the key. Try to lower the KDF \"Memory Usage\".</string>
|
<string name="error_load_database_KDF_memory">Could not load the key. Try to lower the KDF \"Memory Usage\".</string>
|
||||||
@@ -355,6 +356,7 @@
|
|||||||
<string name="warning_database_revoked">Access to the file revoked by the file manager, close the database and reopen it from its location.</string>
|
<string name="warning_database_revoked">Access to the file revoked by the file manager, close the database and reopen it from its location.</string>
|
||||||
<string name="warning_exact_alarm">You have not allowed the app to use an exact alarm. As a result, the features requiring a timer will not be done with an exact time.</string>
|
<string name="warning_exact_alarm">You have not allowed the app to use an exact alarm. As a result, the features requiring a timer will not be done with an exact time.</string>
|
||||||
<string name="warning_keyfile_integrity">The hash of the file is not guaranteed because Android can change its data on the fly. Change the file extension to .bin for correct integrity.</string>
|
<string name="warning_keyfile_integrity">The hash of the file is not guaranteed because Android can change its data on the fly. Change the file extension to .bin for correct integrity.</string>
|
||||||
|
<string name="warning_hardware_key_required">Driver for this hardware is required.</string>
|
||||||
<string name="permission">Permission</string>
|
<string name="permission">Permission</string>
|
||||||
<string name="version_label">Version %1$s</string>
|
<string name="version_label">Version %1$s</string>
|
||||||
<string name="build_label">Build %1$s</string>
|
<string name="build_label">Build %1$s</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user