mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: remove ChallengeResponseViewModel and add HardwareKeyActivity
This commit is contained in:
@@ -156,6 +156,8 @@
|
|||||||
android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
|
android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
|
||||||
|
<activity
|
||||||
|
android:name="com.kunzisoft.keepass.hardware.HardwareKeyActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
|
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
|
||||||
android:theme="@style/Theme.Transparent"
|
android:theme="@style/Theme.Transparent"
|
||||||
|
|||||||
@@ -36,7 +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.hardware.HardwareKeyActivity
|
||||||
import com.kunzisoft.keepass.database.element.MainCredential
|
import com.kunzisoft.keepass.database.element.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
|
||||||
@@ -169,7 +169,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
hardwareKeySelectionView.selectionListener = { hardwareKey ->
|
hardwareKeySelectionView.selectionListener = { hardwareKey ->
|
||||||
hardwareKeyCheckBox.isChecked = true
|
hardwareKeyCheckBox.isChecked = true
|
||||||
hardwareKeySelectionView.error =
|
hardwareKeySelectionView.error =
|
||||||
if (!HardwareKeyResponseHelper.isHardwareKeyAvailable(requireActivity(), hardwareKey)) {
|
if (!HardwareKeyActivity.isHardwareKeyAvailable(requireActivity(), hardwareKey)) {
|
||||||
// show hardware driver dialog if required
|
// show hardware driver dialog if required
|
||||||
getString(R.string.error_driver_required, hardwareKey.toString())
|
getString(R.string.error_driver_required, hardwareKey.toString())
|
||||||
} else {
|
} else {
|
||||||
@@ -231,7 +231,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
showEmptyPasswordConfirmationDialog()
|
showEmptyPasswordConfirmationDialog()
|
||||||
} else if (!error
|
} else if (!error
|
||||||
&& hardwareKey != null
|
&& hardwareKey != null
|
||||||
&& !HardwareKeyResponseHelper.isHardwareKeyAvailable(
|
&& !HardwareKeyActivity.isHardwareKeyAvailable(
|
||||||
requireActivity(), hardwareKey, false)
|
requireActivity(), hardwareKey, false)
|
||||||
) {
|
) {
|
||||||
// show hardware driver dialog if required
|
// show hardware driver dialog if required
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.kunzisoft.keepass.database.element.Database
|
|||||||
import com.kunzisoft.keepass.database.element.MainCredential
|
import com.kunzisoft.keepass.database.element.MainCredential
|
||||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.viewmodels.ChallengeResponseViewModel
|
|
||||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||||
|
|
||||||
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
||||||
@@ -18,12 +17,10 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
|||||||
protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
||||||
protected var mDatabase: Database? = null
|
protected var mDatabase: Database? = null
|
||||||
|
|
||||||
private val mChallengeResponseViewModel: ChallengeResponseViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
mDatabaseTaskProvider = DatabaseTaskProvider(this, mChallengeResponseViewModel)
|
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
||||||
|
|
||||||
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
||||||
val databaseWasReloaded = database?.wasReloaded == true
|
val databaseWasReloaded = database?.wasReloaded == true
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.action
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.Context.*
|
import android.content.Context.*
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -43,13 +42,13 @@ 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.HardwareKeyActivity
|
||||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.ProgressMessage
|
import com.kunzisoft.keepass.model.ProgressMessage
|
||||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||||
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_CHALLENGE_RESPONDED
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||||
@@ -86,7 +85,6 @@ 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 com.kunzisoft.keepass.viewmodels.ChallengeResponseViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -94,10 +92,11 @@ import java.util.*
|
|||||||
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
|
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
|
||||||
* Useful to retrieve a database instance and sending tasks commands
|
* Useful to retrieve a database instance and sending tasks commands
|
||||||
*/
|
*/
|
||||||
class DatabaseTaskProvider {
|
class DatabaseTaskProvider(private var context: Context) {
|
||||||
|
|
||||||
private var activity: FragmentActivity? = null
|
// To show dialog only if context is an activity
|
||||||
private var context: Context
|
private var activity: FragmentActivity? = try { context as? FragmentActivity? }
|
||||||
|
catch (_: Exception) { null }
|
||||||
|
|
||||||
var onDatabaseRetrieved: ((database: Database?) -> Unit)? = null
|
var onDatabaseRetrieved: ((database: Database?) -> Unit)? = null
|
||||||
|
|
||||||
@@ -105,7 +104,10 @@ class DatabaseTaskProvider {
|
|||||||
actionTask: String,
|
actionTask: String,
|
||||||
result: ActionRunnable.Result) -> Unit)? = null
|
result: ActionRunnable.Result) -> Unit)? = null
|
||||||
|
|
||||||
private var intentDatabaseTask: Intent
|
private var intentDatabaseTask: Intent = Intent(
|
||||||
|
context.applicationContext,
|
||||||
|
DatabaseTaskNotificationService::class.java
|
||||||
|
)
|
||||||
|
|
||||||
private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null
|
private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null
|
||||||
private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null
|
private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null
|
||||||
@@ -115,54 +117,6 @@ class DatabaseTaskProvider {
|
|||||||
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
||||||
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
|
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
|
||||||
|
|
||||||
private var mChallengeResponseViewModel: ChallengeResponseViewModel? = null
|
|
||||||
|
|
||||||
constructor(activity: FragmentActivity,
|
|
||||||
challengeResponseViewModel: ChallengeResponseViewModel) {
|
|
||||||
this.activity = activity
|
|
||||||
this.context = activity
|
|
||||||
this.intentDatabaseTask = Intent(activity.applicationContext,
|
|
||||||
DatabaseTaskNotificationService::class.java)
|
|
||||||
|
|
||||||
// ViewModel used to keep response if activity recreated
|
|
||||||
this.mChallengeResponseViewModel = challengeResponseViewModel
|
|
||||||
// To manage hardware key challenge response
|
|
||||||
val hardwareKeyResponseHelper = HardwareKeyResponseHelper(activity)
|
|
||||||
hardwareKeyResponseHelper.buildHardwareKeyResponse { responseData, _ ->
|
|
||||||
// TODO Verify database
|
|
||||||
// Send to view model in case activity is restarted and not yet connected to service
|
|
||||||
challengeResponseViewModel.respond(responseData ?: ByteArray(0))
|
|
||||||
}
|
|
||||||
challengeResponseViewModel.dataResponded.observe(activity) { response ->
|
|
||||||
// Consume the response
|
|
||||||
if (response != null) {
|
|
||||||
val binder = mBinder
|
|
||||||
if (binder != null) {
|
|
||||||
binder.getService().respondToChallenge(response)
|
|
||||||
challengeResponseViewModel.consumeResponse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestChallengeListener = object: DatabaseTaskNotificationService.RequestChallengeListener {
|
|
||||||
override fun onChallengeResponseRequested(hardwareKey: HardwareKey, seed: ByteArray?) {
|
|
||||||
if (HardwareKeyResponseHelper.isHardwareKeyAvailable(activity, hardwareKey)) {
|
|
||||||
hardwareKeyResponseHelper.launchChallengeForResponse(hardwareKey, seed)
|
|
||||||
} else {
|
|
||||||
throw InvalidCredentialsDatabaseException(
|
|
||||||
context.getString(R.string.error_driver_required, hardwareKey.toString())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(service: Service) {
|
|
||||||
this.context = service
|
|
||||||
this.intentDatabaseTask = Intent(service.applicationContext,
|
|
||||||
DatabaseTaskNotificationService::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
this.activity = null
|
this.activity = null
|
||||||
this.onDatabaseRetrieved = null
|
this.onDatabaseRetrieved = null
|
||||||
@@ -172,7 +126,6 @@ class DatabaseTaskProvider {
|
|||||||
this.serviceConnection = null
|
this.serviceConnection = null
|
||||||
this.progressTaskDialogFragment = null
|
this.progressTaskDialogFragment = null
|
||||||
this.databaseChangedDialogFragment = null
|
this.databaseChangedDialogFragment = null
|
||||||
this.mChallengeResponseViewModel = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
||||||
@@ -235,7 +188,19 @@ class DatabaseTaskProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var requestChallengeListener: DatabaseTaskNotificationService.RequestChallengeListener? = null
|
private var requestChallengeListener = object: DatabaseTaskNotificationService.RequestChallengeListener {
|
||||||
|
override fun onChallengeResponseRequested(
|
||||||
|
hardwareKey: HardwareKey,
|
||||||
|
seed: ByteArray?
|
||||||
|
) {
|
||||||
|
HardwareKeyActivity
|
||||||
|
.launchHardwareKeyActivity(
|
||||||
|
context,
|
||||||
|
hardwareKey,
|
||||||
|
seed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startDialog(progressMessage: ProgressMessage) {
|
private fun startDialog(progressMessage: ProgressMessage) {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
@@ -280,7 +245,6 @@ class DatabaseTaskProvider {
|
|||||||
getService().checkDatabaseInfo()
|
getService().checkDatabaseInfo()
|
||||||
getService().checkAction()
|
getService().checkAction()
|
||||||
}
|
}
|
||||||
mChallengeResponseViewModel?.resendResponse()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
@@ -295,9 +259,7 @@ class DatabaseTaskProvider {
|
|||||||
service?.addDatabaseListener(databaseListener)
|
service?.addDatabaseListener(databaseListener)
|
||||||
service?.addDatabaseFileInfoListener(databaseInfoListener)
|
service?.addDatabaseFileInfoListener(databaseInfoListener)
|
||||||
service?.addActionTaskListener(actionTaskListener)
|
service?.addActionTaskListener(actionTaskListener)
|
||||||
requestChallengeListener?.let {
|
service?.setRequestChallengeListener(requestChallengeListener)
|
||||||
service?.addRequestChallengeListener(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeServiceListeners(service: DatabaseTaskNotificationService.ActionTaskBinder?) {
|
private fun removeServiceListeners(service: DatabaseTaskNotificationService.ActionTaskBinder?) {
|
||||||
@@ -762,6 +724,13 @@ class DatabaseTaskProvider {
|
|||||||
, ACTION_DATABASE_SAVE)
|
, ACTION_DATABASE_SAVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startChallengeResponded(response: ByteArray?) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putByteArray(DatabaseTaskNotificationService.DATA_BYTES, response)
|
||||||
|
}
|
||||||
|
, ACTION_CHALLENGE_RESPONDED)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = DatabaseTaskProvider::class.java.name
|
private val TAG = DatabaseTaskProvider::class.java.name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package com.kunzisoft.keepass.hardware
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import androidx.activity.result.ActivityResultCallback
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.legacy.DatabaseActivity
|
||||||
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special activity to deal with hardware key drivers,
|
||||||
|
* return the response to the database service once finished
|
||||||
|
*/
|
||||||
|
class HardwareKeyActivity: DatabaseActivity(){
|
||||||
|
|
||||||
|
// To manage hardware key challenge response
|
||||||
|
private val resultCallback = ActivityResultCallback<ActivityResult> { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
val challengeResponse: ByteArray? = result.data?.getByteArrayExtra(HARDWARE_KEY_RESPONSE_KEY)
|
||||||
|
Log.d(TAG, "Response form challenge")
|
||||||
|
mDatabaseTaskProvider?.startChallengeResponded(challengeResponse ?: ByteArray(0))
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Response from challenge error")
|
||||||
|
mDatabaseTaskProvider?.startChallengeResponded(ByteArray(0))
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var activityResultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
|
resultCallback
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onDatabaseRetrieved(database: Database?) {
|
||||||
|
super.onDatabaseRetrieved(database)
|
||||||
|
|
||||||
|
val hardwareKey = HardwareKey.getHardwareKeyFromString(
|
||||||
|
intent.getStringExtra(DATA_HARDWARE_KEY)
|
||||||
|
)
|
||||||
|
if (isHardwareKeyAvailable(this, hardwareKey)) {
|
||||||
|
when (hardwareKey) {
|
||||||
|
/*
|
||||||
|
HardwareKey.FIDO2_SECRET -> {
|
||||||
|
// TODO FIDO2 under development
|
||||||
|
throw Exception("FIDO2 not implemented")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||||
|
launchYubikeyChallengeForResponse(intent.getByteArrayExtra(DATA_SEED))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
// Send to the driver
|
||||||
|
activityResultLauncher.launch(
|
||||||
|
Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT).apply {
|
||||||
|
putExtra(HARDWARE_KEY_CHALLENGE_KEY, challenge)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Log.d(TAG, "Challenge sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
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(
|
||||||
|
context: Context,
|
||||||
|
hardwareKey: HardwareKey,
|
||||||
|
seed: ByteArray?
|
||||||
|
) {
|
||||||
|
context.startActivity(Intent(context, HardwareKeyActivity::class.java).apply {
|
||||||
|
//flags = FLAG_ACTIVITY_NEW_TASK
|
||||||
|
putExtra(DATA_HARDWARE_KEY, hardwareKey.value)
|
||||||
|
putExtra(DATA_SEED, seed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isHardwareKeyAvailable(
|
||||||
|
context: Context,
|
||||||
|
hardwareKey: HardwareKey?,
|
||||||
|
showDialog: Boolean = true
|
||||||
|
): Boolean {
|
||||||
|
if (hardwareKey == null)
|
||||||
|
return false
|
||||||
|
return when (hardwareKey) {
|
||||||
|
/*
|
||||||
|
HardwareKey.FIDO2_SECRET -> {
|
||||||
|
// TODO FIDO2 under development
|
||||||
|
if (showDialog)
|
||||||
|
UnderDevelopmentFeatureDialogFragment()
|
||||||
|
.show(activity.supportFragmentManager, "underDevFeatureDialog")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||||
|
// Check available intent
|
||||||
|
val yubikeyDriverAvailable =
|
||||||
|
Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT)
|
||||||
|
.resolveActivity(context.packageManager) != null
|
||||||
|
if (showDialog && !yubikeyDriverAvailable)
|
||||||
|
showHardwareKeyDriverNeeded(context, hardwareKey)
|
||||||
|
yubikeyDriverAvailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showHardwareKeyDriverNeeded(
|
||||||
|
context: Context,
|
||||||
|
hardwareKey: HardwareKey
|
||||||
|
) {
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
builder
|
||||||
|
.setMessage(
|
||||||
|
context.getString(R.string.error_driver_required, hardwareKey.toString())
|
||||||
|
)
|
||||||
|
.setPositiveButton(R.string.download) { _, _ ->
|
||||||
|
UriUtil.openExternalApp(context, context.getString(R.string.key_driver_app_id))
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
builder.create().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package com.kunzisoft.keepass.hardware
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
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.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
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 {
|
|
||||||
|
|
||||||
private var activity: FragmentActivity? = null
|
|
||||||
private var fragment: Fragment? = null
|
|
||||||
|
|
||||||
private var getChallengeResponseResultLauncher: ActivityResultLauncher<Intent>? = null
|
|
||||||
|
|
||||||
constructor(context: FragmentActivity) {
|
|
||||||
this.activity = context
|
|
||||||
this.fragment = null
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Fragment) {
|
|
||||||
this.activity = context.activity
|
|
||||||
this.fragment = context
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildHardwareKeyResponse(onChallengeResponded: (challengeResponse: ByteArray?,
|
|
||||||
extra: Bundle?) -> Unit) {
|
|
||||||
val resultCallback = ActivityResultCallback<ActivityResult> { result ->
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
val challengeResponse: ByteArray? = result.data?.getByteArrayExtra(HARDWARE_KEY_RESPONSE_KEY)
|
|
||||||
Log.d(TAG, "Response form challenge")
|
|
||||||
onChallengeResponded.invoke(challengeResponse,
|
|
||||||
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Response from challenge error")
|
|
||||||
onChallengeResponded.invoke(null,
|
|
||||||
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getChallengeResponseResultLauncher = if (fragment != null) {
|
|
||||||
fragment?.registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
|
||||||
resultCallback
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
activity?.registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
|
||||||
resultCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun launchChallengeForResponse(hardwareKey: HardwareKey, seed: ByteArray?) {
|
|
||||||
when (hardwareKey) {
|
|
||||||
/*
|
|
||||||
HardwareKey.FIDO2_SECRET -> {
|
|
||||||
// TODO FIDO2 under development
|
|
||||||
throw Exception("FIDO2 not implemented")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
// Send to the driver
|
|
||||||
getChallengeResponseResultLauncher!!.launch(
|
|
||||||
Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT).apply {
|
|
||||||
putExtra(HARDWARE_KEY_CHALLENGE_KEY, challenge)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Log.d(TAG, "Challenge sent")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = HardwareKeyResponseHelper::class.java.simpleName
|
|
||||||
|
|
||||||
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"
|
|
||||||
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 under development
|
|
||||||
if (showDialog)
|
|
||||||
UnderDevelopmentFeatureDialogFragment()
|
|
||||||
.show(activity.supportFragmentManager, "underDevFeatureDialog")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
|
||||||
// Check available intent
|
|
||||||
val yubikeyDriverAvailable =
|
|
||||||
Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT)
|
|
||||||
.resolveActivity(activity.packageManager) != null
|
|
||||||
if (showDialog && !yubikeyDriverAvailable)
|
|
||||||
showHardwareKeyDriverNeeded(activity, hardwareKey)
|
|
||||||
yubikeyDriverAvailable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showHardwareKeyDriverNeeded(
|
|
||||||
activity: FragmentActivity,
|
|
||||||
hardwareKey: HardwareKey
|
|
||||||
) {
|
|
||||||
activity.lifecycleScope.launch {
|
|
||||||
val builder = AlertDialog.Builder(activity)
|
|
||||||
builder
|
|
||||||
.setMessage(
|
|
||||||
activity.getString(R.string.error_driver_required, hardwareKey.toString())
|
|
||||||
)
|
|
||||||
.setPositiveButton(R.string.download) { _, _ ->
|
|
||||||
UriUtil.openExternalApp(activity, activity.getString(R.string.key_driver_app_id))
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
|
||||||
builder.create().show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -124,8 +124,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
mActionTaskListeners.remove(actionTaskListener)
|
mActionTaskListeners.remove(actionTaskListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun addRequestChallengeListener(requestChallengeListener: RequestChallengeListener) {
|
fun setRequestChallengeListener(requestChallengeListener: RequestChallengeListener) {
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
val requestChannel = mRequestChallengeListenerChannel
|
val requestChannel = mRequestChallengeListenerChannel
|
||||||
if (requestChannel == null || requestChannel.isEmpty) {
|
if (requestChannel == null || requestChannel.isEmpty) {
|
||||||
@@ -169,7 +169,10 @@ 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() {
|
||||||
@@ -270,8 +273,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
mResponseChallengeChannel = null
|
mResponseChallengeChannel = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun respondToChallenge(response: ByteArray) {
|
private fun respondToChallenge(response: ByteArray) {
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
val responseChannel = mResponseChallengeChannel
|
val responseChannel = mResponseChallengeChannel
|
||||||
if (responseChannel == null || responseChannel.isEmpty) {
|
if (responseChannel == null || responseChannel.isEmpty) {
|
||||||
@@ -323,6 +326,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (intentAction == ACTION_CHALLENGE_RESPONDED) {
|
||||||
|
intent.getByteArrayExtra(DATA_BYTES)?.let {
|
||||||
|
respondToChallenge(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val actionRunnable: ActionRunnable? = when (intentAction) {
|
val actionRunnable: ActionRunnable? = when (intentAction) {
|
||||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
|
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
|
||||||
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
|
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
|
||||||
@@ -756,7 +765,6 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
databaseToMergeUri,
|
databaseToMergeUri,
|
||||||
databaseToMergeMainCredential,
|
databaseToMergeMainCredential,
|
||||||
{ hardwareKey, seed ->
|
{ hardwareKey, seed ->
|
||||||
// TODO fix first challenge response
|
|
||||||
retrieveResponseFromChallenge(hardwareKey, seed)
|
retrieveResponseFromChallenge(hardwareKey, seed)
|
||||||
},
|
},
|
||||||
database,
|
database,
|
||||||
@@ -1157,6 +1165,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
const val ACTION_DATABASE_UPDATE_PARALLELISM_TASK = "ACTION_DATABASE_UPDATE_PARALLELISM_TASK"
|
const val ACTION_DATABASE_UPDATE_PARALLELISM_TASK = "ACTION_DATABASE_UPDATE_PARALLELISM_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_ITERATIONS_TASK = "ACTION_DATABASE_UPDATE_ITERATIONS_TASK"
|
const val ACTION_DATABASE_UPDATE_ITERATIONS_TASK = "ACTION_DATABASE_UPDATE_ITERATIONS_TASK"
|
||||||
const val ACTION_DATABASE_SAVE = "ACTION_DATABASE_SAVE"
|
const val ACTION_DATABASE_SAVE = "ACTION_DATABASE_SAVE"
|
||||||
|
const val ACTION_CHALLENGE_RESPONDED = "ACTION_CHALLENGE_RESPONDED"
|
||||||
|
|
||||||
const val DATABASE_TASK_TITLE_KEY = "DATABASE_TASK_TITLE_KEY"
|
const val DATABASE_TASK_TITLE_KEY = "DATABASE_TASK_TITLE_KEY"
|
||||||
const val DATABASE_TASK_MESSAGE_KEY = "DATABASE_TASK_MESSAGE_KEY"
|
const val DATABASE_TASK_MESSAGE_KEY = "DATABASE_TASK_MESSAGE_KEY"
|
||||||
@@ -1180,6 +1189,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
const val NEW_NODES_KEY = "NEW_NODES_KEY"
|
const val NEW_NODES_KEY = "NEW_NODES_KEY"
|
||||||
const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time
|
const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time
|
||||||
const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time
|
const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time
|
||||||
|
const val DATA_BYTES = "DATA_BYTES"
|
||||||
|
|
||||||
fun getListNodesFromBundle(database: Database, bundle: Bundle): List<Node> {
|
fun getListNodesFromBundle(database: Database, bundle: Bundle): List<Node> {
|
||||||
val nodesAction = ArrayList<Node>()
|
val nodesAction = ArrayList<Node>()
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.kunzisoft.keepass.viewmodels
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class ChallengeResponseViewModel: ViewModel() {
|
|
||||||
|
|
||||||
val dataResponded : LiveData<ByteArray?> get() = _dataResponded
|
|
||||||
private val _dataResponded = MutableLiveData<ByteArray?>()
|
|
||||||
|
|
||||||
fun respond(byteArray: ByteArray) {
|
|
||||||
_dataResponded.value = byteArray
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resendResponse() {
|
|
||||||
dataResponded.value?.let {
|
|
||||||
_dataResponded.value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun consumeResponse() {
|
|
||||||
_dataResponded.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user