fix: User Verified response flag

This commit is contained in:
J-Jamet
2025-11-25 19:15:37 +01:00
parent c17fba8ef7
commit d90d175bd8
4 changed files with 74 additions and 35 deletions

View File

@@ -46,13 +46,15 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivity
import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.ALLOWED_AUTHENTICATORS
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addUserVerificationRequired
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addUserVerification
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerificationCondition
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerifiedWithAuth
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isAuthenticatorsAllowed
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isUserVerificationRequired
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeUserVerificationRequired
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeUserVerification
import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
import com.kunzisoft.keepass.database.ContextualDatabase
@@ -91,16 +93,17 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
if (intent.isUserVerificationRequired()) {
val userVerificationCondition = intent.getUserVerificationCondition()
if (userVerificationCondition) {
if (isAuthenticatorsAllowed().not()) {
intent.removeUserVerificationRequired()
intent.removeUserVerification()
sendBroadcast(Intent(LOCK_ACTION))
}
}
// super.onCreate must be after UserVerification to allow database lock
super.onCreate(savedInstanceState)
// Biometric must be after super.onCreate
if (intent.isUserVerificationRequired()) {
if (userVerificationCondition) {
if (isAuthenticatorsAllowed()) {
BiometricPrompt(
this, ContextCompat.getMainExecutor(this),
@@ -127,7 +130,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
passkeyLauncherViewModel.launchAction(intent, mSpecialMode)
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
@@ -146,7 +149,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
lifecycleScope.launch {
// Initialize the parameters
passkeyLauncherViewModel.initialize()
passkeyLauncherViewModel.initialize(userVerified = intent.getUserVerifiedWithAuth())
// Retrieve the UI
passkeyLauncherViewModel.uiState.collect { uiState ->
when (uiState) {
@@ -340,7 +343,8 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
searchInfo: SearchInfo? = null,
appOrigin: AppOrigin? = null,
nodeId: UUID? = null,
userVerificationRequired: Boolean = false
userVerification: UserVerificationRequirement = UserVerificationRequirement.PREFERRED,
userVerifiedWithAuth: Boolean = true
): PendingIntent? {
return PendingIntent.getActivity(
context,
@@ -352,7 +356,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
addAppOrigin(appOrigin)
addNodeId(nodeId)
addAuthCode(nodeId)
addUserVerificationRequired(userVerificationRequired)
addUserVerification(userVerification, userVerifiedWithAuth)
},
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

View File

@@ -157,9 +157,9 @@ class PasskeyProviderService : CredentialProviderService() {
val credentialIdList = publicKeyCredentialRequestOptions.allowCredentials
.map { b64Encode(it.id) }
val searchInfo = buildPasskeySearchInfo(relyingPartyId, credentialIdList)
val userVerificationRequired = publicKeyCredentialRequestOptions
.userVerification == UserVerificationRequirement.REQUIRED
Log.d(TAG, "Build passkey search for UV $userVerificationRequired, " +
//val userVerification = publicKeyCredentialRequestOptions.userVerification
val userVerification = UserVerificationRequirement.REQUIRED
Log.d(TAG, "Build passkey search for UV $userVerification, " +
"RP $relyingPartyId and Credential IDs $credentialIdList")
SearchHelper.checkAutoSearchInfo(
context = this,
@@ -170,7 +170,7 @@ class PasskeyProviderService : CredentialProviderService() {
passkeyEntries = passkeyEntries,
searchInfo = searchInfo,
option = option,
userVerificationRequired = userVerificationRequired
userVerification = userVerification
) {
Log.d(TAG, "Add pending intent for passkey selection with found items")
for (passkeyEntry in items) {
@@ -179,7 +179,8 @@ class PasskeyProviderService : CredentialProviderService() {
specialMode = SpecialMode.SELECTION,
nodeId = passkeyEntry.id,
appOrigin = passkeyEntry.appOrigin,
userVerificationRequired = userVerificationRequired
userVerification = userVerification,
userVerifiedWithAuth = false
)?.let { usagePendingIntent ->
val passkey = passkeyEntry.passkey
passkeyEntries.add(
@@ -208,7 +209,7 @@ class PasskeyProviderService : CredentialProviderService() {
passkeyEntries = passkeyEntries,
searchInfo = searchInfo,
option = option,
userVerificationRequired = userVerificationRequired
userVerification = userVerification,
) {
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
if (credentialIdList.isEmpty()) {
@@ -217,7 +218,8 @@ class PasskeyProviderService : CredentialProviderService() {
context = applicationContext,
specialMode = SpecialMode.SELECTION,
searchInfo = searchInfo,
userVerificationRequired = userVerificationRequired
userVerification = userVerification,
userVerifiedWithAuth = false
)?.let { pendingIntent ->
passkeyEntries.add(
PublicKeyCredentialEntry(
@@ -250,7 +252,8 @@ class PasskeyProviderService : CredentialProviderService() {
PasskeyLauncherActivity.getPendingIntent(
context = applicationContext,
specialMode = SpecialMode.SELECTION,
searchInfo = searchInfo
searchInfo = searchInfo,
userVerifiedWithAuth = true
)?.let { pendingIntent ->
passkeyEntries.add(
PublicKeyCredentialEntry(
@@ -277,15 +280,16 @@ class PasskeyProviderService : CredentialProviderService() {
passkeyEntries: MutableList<CredentialEntry>,
searchInfo: SearchInfo,
option: BeginGetPublicKeyCredentialOption,
userVerificationRequired: Boolean,
userVerification: UserVerificationRequirement,
standardAction: () -> Unit
) {
if (userVerificationRequired && isAuthenticatorsAllowed().not()) {
if (userVerification == UserVerificationRequirement.REQUIRED && isAuthenticatorsAllowed().not()) {
PasskeyLauncherActivity.getPendingIntent(
context = applicationContext,
specialMode = SpecialMode.SELECTION,
searchInfo = searchInfo,
userVerificationRequired = true
userVerification = userVerification,
userVerifiedWithAuth = true
)?.let { pendingIntent ->
passkeyEntries.add(
PublicKeyCredentialEntry(

View File

@@ -60,6 +60,7 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.getOriginFromPrivilegedAllowLists
import com.kunzisoft.keepass.model.AndroidOrigin
import com.kunzisoft.keepass.model.AppOrigin
@@ -67,7 +68,9 @@ import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Passkey
import com.kunzisoft.keepass.utils.AppUtil
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.getEnumExtra
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.utils.putEnumExtra
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
@@ -95,7 +98,8 @@ object PasskeyHelper {
private const val EXTRA_APP_ORIGIN = "com.kunzisoft.keepass.extra.appOrigin"
private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp"
private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode"
private const val EXTRA_UV_REQUIRED = "com.kunzisoft.keepass.extra.userVerification"
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
private const val SEPARATOR = "_"
@@ -113,26 +117,46 @@ object PasskeyHelper {
private val internalSecureRandom: SecureRandom = SecureRandom()
/**
* Add the user verification to the intent
* Add the User Verification to the intent
*/
fun Intent.addUserVerificationRequired(userVerification: Boolean) {
putExtra(EXTRA_UV_REQUIRED, userVerification)
fun Intent.addUserVerification(
userVerification: UserVerificationRequirement,
userVerifiedWithAuth: Boolean
) {
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
}
/**
* Check if the user verification is required
* Get the User Verification from the intent
*/
fun Intent.isUserVerificationRequired(): Boolean {
return getBooleanExtra(EXTRA_UV_REQUIRED, false)
fun Intent.getUserVerificationCondition(): Boolean {
return (getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
?: UserVerificationRequirement.PREFERRED) == UserVerificationRequirement.REQUIRED
}
/**
* Remove the user verification from the intent
* Define if the User is verified with authentification from the intent
*/
fun Intent.removeUserVerificationRequired() {
removeExtra(EXTRA_UV_REQUIRED)
fun Intent.getUserVerifiedWithAuth(): Boolean {
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
}
/**
* Remove the User Verification from the intent
*/
fun Intent.removeUserVerification() {
removeExtra(EXTRA_USER_VERIFICATION)
}
/**
* Remove the User verified with auth from the intent
*/
fun Intent.removeUserVerifiedWithAuth() {
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
}
/**
* Allowed authenticators for the User Verification
*/
@@ -622,6 +646,7 @@ object PasskeyHelper {
requestOptions: PublicKeyCredentialRequestOptions,
clientDataResponse: ClientDataResponse,
passkey: Passkey,
userVerified: Boolean,
defaultBackupEligibility: Boolean,
defaultBackupState: Boolean
): PublicKeyCredential {
@@ -630,7 +655,7 @@ object PasskeyHelper {
response = AuthenticatorAssertionResponse(
requestOptions = requestOptions,
userPresent = true,
userVerified = true,
userVerified = userVerified,
backupEligibility = passkey.backupEligibility ?: defaultBackupEligibility,
backupState = passkey.backupState ?: defaultBackupState,
userHandle = passkey.userHandle,

View File

@@ -24,8 +24,8 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildCreatePublicKeyCredentialResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyPublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerificationCondition
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedGETClientDataResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isUserVerificationRequired
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeAppOrigin
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveAppOrigin
@@ -65,14 +65,16 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
private var mPasskey: Passkey? = null
private var mLockDatabaseAfterSelection: Boolean = false
private var mUserVerified: Boolean = true
private var mBackupEligibility: Boolean = true
private var mBackupState: Boolean = false
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
val uiState: StateFlow<UIState> = mUiState
fun initialize() {
fun initialize(userVerified: Boolean) {
mLockDatabaseAfterSelection = PreferencesUtil.isPasskeyCloseDatabaseEnable(getApplication())
mUserVerified = userVerified
mBackupEligibility = PreferencesUtil.isPasskeyBackupEligibilityEnable(getApplication())
mBackupState = PreferencesUtil.isPasskeyBackupStateEnable(getApplication())
}
@@ -151,9 +153,11 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
}
fun launchAction(
userVerified: Boolean,
intent: Intent,
specialMode: SpecialMode,
) {
this.mUserVerified = userVerified
super.launchActionIfNeeded(intent, specialMode, mDatabase)
}
@@ -162,7 +166,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
specialMode: SpecialMode,
database: ContextualDatabase?
) {
if (intent.isUserVerificationRequired()) {
if (intent.getUserVerificationCondition()) {
if (database != null) {
onDatabaseRetrieved(database)
}
@@ -321,6 +325,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
appOrigin = appOrigin
),
passkey = passkey,
userVerified = mUserVerified,
defaultBackupEligibility = mBackupEligibility,
defaultBackupState = mBackupState
)
@@ -377,6 +382,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
appOrigin = appOrigin
),
passkey = passkey,
userVerified = mUserVerified,
defaultBackupEligibility = mBackupEligibility,
defaultBackupState = mBackupState
)