diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c4dd5bef2..66cb1d194 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -178,19 +178,22 @@
+ android:exported="false"
+ android:excludeFromRecents="true" />
+ android:exported="false"
+ android:excludeFromRecents="true" />
+ android:exported="true"
+ android:excludeFromRecents="true">
@@ -209,8 +212,8 @@
android:name="com.kunzisoft.keepass.credentialprovider.activity.PasskeyLauncherActivity"
android:theme="@style/Theme.Transparent"
android:configChanges="keyboardHidden"
- android:excludeFromRecents="true"
android:exported="false"
+ android:excludeFromRecents="true"
tools:targetApi="upside_down_cake" />
{ 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 val mHardwareKeyLauncherViewModel: HardwareKeyLauncherViewModel by viewModels()
- private var activityResultLauncher: ActivityResultLauncher = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult(),
- resultCallback
- )
+ private var activityResultLauncher: ActivityResultLauncher =
+ this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ mHardwareKeyLauncherViewModel.manageSelectionResult(it)
+ }
override fun applyCustomStyle(): Boolean {
return false
@@ -48,65 +45,60 @@ class HardwareKeyActivity: DatabaseModeActivity(){
return false
}
- override fun onDatabaseRetrieved(database: ContextualDatabase) {
- val hardwareKey = HardwareKey.getHardwareKeyFromString(
- intent.getStringExtra(DATA_HARDWARE_KEY)
- )
- if (isHardwareKeyAvailable(this, hardwareKey, true) {
- mDatabaseViewModel.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 -> {
- finish()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lifecycleScope.launch {
+ mHardwareKeyLauncherViewModel.uiState.collect { uiState ->
+ when (uiState) {
+ is HardwareKeyLauncherViewModel.UIState.Loading -> {}
+ is HardwareKeyLauncherViewModel.UIState.LaunchChallengeActivityForResponse -> {
+ // Send to the driver
+ activityResultLauncher.launch(
+ buildHardwareKeyChallenge(uiState.challenge)
+ )
+ }
+ is HardwareKeyLauncherViewModel.UIState.OnChallengeResponded -> {
+ mDatabaseViewModel.onChallengeResponded(uiState.response)
+ }
}
}
}
}
- 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)
+ override fun onDatabaseRetrieved(database: ContextualDatabase) {
+ super.onDatabaseRetrieved(database)
+ mHardwareKeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
+ }
+
+ override fun onDatabaseActionFinished(
+ database: ContextualDatabase,
+ actionTask: String,
+ result: ActionRunnable.Result
+ ) {
+ super.onDatabaseActionFinished(database, actionTask, result)
+ when (actionTask) {
+ ACTION_DATABASE_LOAD_TASK -> {
+ finish()
}
- )
- 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)
+ context.startActivity(
+ Intent(
+ context,
+ 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 -> {
// Check available intent
- val yubikeyDriverAvailable =
- Intent(YUBIKEY_CHALLENGE_RESPONSE_INTENT)
- .resolveActivity(context.packageManager) != null
- if (showDialog && !yubikeyDriverAvailable
- && context is Activity)
+ // TODO Dialog
+ val yubikeyDriverAvailable = isYubikeyDriverAvailable(context)
+ if (showDialog && !yubikeyDriverAvailable && context is Activity) {
showHardwareKeyDriverNeeded(context, hardwareKey) {
onDialogDismissed?.onDismiss(it)
context.finish()
}
+ }
yubikeyDriverAvailable
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt
index f6d3033a0..7eb876f2a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt
@@ -82,7 +82,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
mSelectionResult = null
}
- abstract fun manageRegistrationResult(activityResult: ActivityResult)
+ open fun manageRegistrationResult(activityResult: ActivityResult) {}
open fun onExceptionOccurred(e: Throwable) {
showError(e)
@@ -93,7 +93,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
specialMode: SpecialMode,
database: ContextualDatabase?
) {
- if (database != null && database.loaded) {
+ if (database != null) {
onDatabaseRetrieved(database)
}
if (isResultLauncherRegistered.not()) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/HardwareKeyLauncherViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/HardwareKeyLauncherViewModel.kt
new file mode 100644
index 000000000..a7fdbcfd0
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/HardwareKeyLauncherViewModel.kt
@@ -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.Loading)
+ val uiState: StateFlow = 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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
index 4bfcb93e1..40f07cd28 100644
--- a/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
@@ -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.Type
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.SnapFileDatabaseInfo
import com.kunzisoft.keepass.settings.PreferencesUtil