484701884efc
fix: Separate passkey payload from UV policy (v2.16.3)
- time
- 2026-09-06 19:40 UTC · 1 month ago
- machine
- koh2
- parent
- 832644cd88ad
↓ peach-android-48470188.tar.gzatlas at this save
files changed3 files
VERSION+1 −1app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt+27 −5app/src/test/java/com/peachpasswords/passkeys/CredentialProviderContractTest.kt+12 −2app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt+27 −5
@@ -105,9 +105,10 @@105105 val option = providerRequest?.credentialOptions106106 ?.filterIsInstance<GetPublicKeyCredentialOption>()107107 ?.firstOrNull()108+ val authenticatedRequestJson = intent.getStringExtra(EXTRA_REQUEST_JSON)108109 val requestJson = selectionRequestJson(109110 frameworkJson = option?.requestJson,110- authenticatedJson = intent.getStringExtra(EXTRA_REQUEST_JSON),111+ authenticatedJson = authenticatedRequestJson,111112 )112113 val clientDataHash = selectionClientDataHash(113114 frameworkHash = option?.clientDataHash,@@ -123,7 +124,16 @@123124 frameworkOrigin = frameworkOrigin,124125 authenticatedOrigin = intent.getStringExtra(EXTRA_AUTHENTICATED_ORIGIN),125126 )126- completeAssertion(entryId, requestJson, origin, clientDataHash)127+ completeAssertion(128+ entryId,129+ requestJson,130+ origin,131+ clientDataHash,132+ uvRequiredOverride = selectionRequiresUserVerification(133+ frameworkJson = option?.requestJson,134+ authenticatedJson = authenticatedRequestJson,135+ ),136+ )127137 }128138 129139 private fun showManualChooser() {@@ -233,6 +243,7 @@233243 requestJson: String?,234244 origin: String?,235245 clientDataHash: ByteArray?,246+ uvRequiredOverride: Boolean? = null,236247 ) {237248 val result = Intent()238249 val entry = entryId?.let(vault::getEntry)@@ -242,7 +253,7 @@242253 // success. If UV is required but verification fails, do NOT sign —243254 // fail closed rather than produce a misleading UV=0 assertion.244255 val uvRequired = entry != null && requestJson != null &&245- userVerificationRequirement(requestJson) == UV_REQUIRED256+ (uvRequiredOverride ?: (userVerificationRequirement(requestJson) == UV_REQUIRED))246257 val userVerified = if (uvRequired) {247258 runUserVerificationPrompt()248259 } else {@@ -523,8 +534,19 @@523534 authenticatedHash: ByteArray?,524535 ): ByteArray? = authenticatedHash ?: frameworkHash525536 526-/** Keep UV/challenge semantics from the framework-authenticated BeginGet request. */537+/** Use the final assertion JSON when present; its challenge pairs with the final request. */527538 internal fun selectionRequestJson(528539 frameworkJson: String?,529540 authenticatedJson: String?,530-): String? = authenticatedJson ?: frameworkJson541+): String? = frameworkJson ?: authenticatedJson542+543+/** Never let an OEM's final row payload weaken BeginGet's UV requirement. */544+internal fun selectionRequiresUserVerification(545+ frameworkJson: String?,546+ authenticatedJson: String?,547+): Boolean = listOfNotNull(frameworkJson, authenticatedJson).any { requestJson ->548+ runCatching {549+ Json.parseToJsonElement(requestJson).jsonObject["userVerification"]550+ ?.jsonPrimitive?.contentOrNull == "required"551+ }.getOrDefault(false)552+}app/src/test/java/com/peachpasswords/passkeys/CredentialProviderContractTest.kt+12 −2
@@ -54,13 +54,13 @@5454 }5555 5656 @Test57- fun selectionKeepsAuthenticatedBeginJsonWhenSamsungDropsUvRequirement() {57+ fun selectionKeepsFinalJsonForTheSignedAssertion() {5858 val frameworkJson = """{"challenge":"final"}"""5959 val authenticatedJson =6060 """{"challenge":"begin","userVerification":"required"}"""6161 6262 assertTrue(63- selectionRequestJson(frameworkJson, authenticatedJson) == authenticatedJson,63+ selectionRequestJson(frameworkJson, authenticatedJson) == frameworkJson,6464 )6565 }6666 @@ -69,6 +69,16 @@6969 val frameworkJson = """{"challenge":"final"}"""7070 7171 assertTrue(selectionRequestJson(frameworkJson, null) == frameworkJson)72+ }73+74+ @Test75+ fun authenticatedBeginJsonCanRequireUvWithoutReplacingFinalAssertionJson() {76+ val frameworkJson = """{"challenge":"final"}"""77+ val authenticatedJson =78+ """{"challenge":"begin","userVerification":"required"}"""79+80+ assertTrue(selectionRequiresUserVerification(frameworkJson, authenticatedJson))81+ assertTrue(selectionRequestJson(frameworkJson, authenticatedJson) == frameworkJson)7282 }7383 7484 @Test