d993f918cad9
fix: Unify Samsung passkey paths and open chooser (v2.16.1)
- time
- 2026-09-06 18:00 UTC · 1 month ago
- machine
- koh2
- parent
- e3315622e288
↓ peach-android-d993f918.tar.gzatlas at this save
files changed3 files
VERSION+1 −1app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt+40 −10app/src/test/java/com/peachpasswords/passkeys/CredentialProviderContractTest.kt+2 −2app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt+40 −10
@@ -131,13 +131,36 @@131131 val allowlist = PasskeyCallerOrigin.privilegedAllowlist(this)132132 val origin = request?.callingAppInfo?.let { PasskeyCallerOrigin.resolve(it, allowlist) }133133 ?: intent.getStringExtra(EXTRA_AUTHENTICATED_ORIGIN)134- val choices = request?.beginGetCredentialOptions134+ val requestChoices = request?.beginGetCredentialOptions135135 ?.filterIsInstance<BeginGetPublicKeyCredentialOption>()136136 ?.flatMap { option ->137137 PasskeyCredentialPlanner.candidates(vault.getEntries(), option.requestJson, origin)138- .map { ManualChoice(it.entryId, it.displayName, it.username, option) }138+ .map {139+ ManualChoice(140+ it.entryId,141+ it.displayName,142+ it.username,143+ option.requestJson,144+ option.clientDataHash,145+ )146+ }139147 }140148 .orEmpty()149+ val choices = requestChoices.ifEmpty {150+ val requestJson = intent.getStringExtra(EXTRA_REQUEST_JSON)151+ val authenticatedHash = intent.getByteArrayExtra(EXTRA_AUTHENTICATED_CLIENT_DATA_HASH)152+ if (requestJson == null) emptyList()153+ else PasskeyCredentialPlanner.candidates(vault.getEntries(), requestJson, origin)154+ .map {155+ ManualChoice(156+ entryId = it.entryId,157+ displayName = it.displayName,158+ username = it.username,159+ requestJson = requestJson,160+ clientDataHash = authenticatedHash,161+ )162+ }163+ }141164 if (choices.isEmpty()) {142165 finish()143166 return@@ -179,15 +202,15 @@179202 },180203 )181204 LazyColumn {182- items(visible, key = { "${it.entryId}:${it.option.requestJson.hashCode()}" }) { choice ->205+ items(visible, key = { "${it.entryId}:${it.requestJson.hashCode()}" }) { choice ->183206 Column(184207 Modifier.fillMaxWidth().clickable {185208 lifecycleScope.launch {186209 completeAssertion(187210 choice.entryId,188- choice.option.requestJson,211+ choice.requestJson,189212 origin,190- choice.option.clientDataHash,213+ choice.clientDataHash,191214 )192215 }193216 }.padding(vertical = 12.dp),@@ -447,10 +470,15 @@447470 request: BeginGetCredentialRequest,448471 authenticatedOrigin: String?,449472 ): PendingIntent {473+ val option = request.beginGetCredentialOptions474+ .filterIsInstance<BeginGetPublicKeyCredentialOption>()475+ .firstOrNull()450476 val intent = Intent(context, PasskeyCredentialSelectionActivity::class.java)451477 .putExtra(EXTRA_MODE, MODE_CHOOSE)452478 .putExtra(EXTRA_BEGIN_REQUEST, BeginGetCredentialRequest.asBundle(request))453479 .putExtra(EXTRA_AUTHENTICATED_ORIGIN, authenticatedOrigin)480+ .putExtra(EXTRA_REQUEST_JSON, option?.requestJson)481+ .putExtra(EXTRA_AUTHENTICATED_CLIENT_DATA_HASH, option?.clientDataHash)454482 val mutableFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0455483 return PendingIntent.getActivity(456484 context,@@ -465,7 +493,8 @@465493 val entryId: String,466494 val displayName: String,467495 val username: String,468- val option: BeginGetPublicKeyCredentialOption,496+ val requestJson: String,497+ val clientDataHash: ByteArray?,469498 )470499 }471500 @@ -481,11 +510,12 @@481510 ?: authenticatedOrigin?.takeIf { it.isNotBlank() }482511 483512 /**484- * Prefer request data attached by Credential Manager to the selected row. Some485- * OEM confirmation-button paths omit that final request, so retain the same486- * framework-authenticated hash that arrived in the BeginGet request.513+ * Keep the framework-authenticated BeginGet hash whenever it is available.514+ * Samsung can omit or replace the final row request depending on whether the515+ * row or confirmation button was tapped; both UI paths must sign identical516+ * client data.487517 */488518 internal fun selectionClientDataHash(489519 frameworkHash: ByteArray?,490520 authenticatedHash: ByteArray?,491-): ByteArray? = frameworkHash ?: authenticatedHash521+): ByteArray? = authenticatedHash ?: frameworkHashapp/src/test/java/com/peachpasswords/passkeys/CredentialProviderContractTest.kt+2 −2
@@ -43,13 +43,13 @@4343 }4444 4545 @Test46- fun selectionPrefersTheFinalFrameworkHash() {46+ fun selectionKeepsTheAuthenticatedBeginHashWhenSamsungReplacesFinalHash() {4747 val frameworkHash = byteArrayOf(4, 5, 6)4848 val authenticatedHash = byteArrayOf(1, 2, 3)4949 5050 assertTrue(5151 selectionClientDataHash(frameworkHash, authenticatedHash)!!52- .contentEquals(frameworkHash),52+ .contentEquals(authenticatedHash),5353 )5454 }5555