From ebe2040b2bf12c5e3deb4ff8efa61f38abf79c83 Mon Sep 17 00:00:00 2001 From: Andrea-lyz Date: Wed, 27 May 2026 05:58:56 +0200 Subject: [PATCH] fix(interception): strip INCLUDE_UNIQUE_ID instead of rejecting on missing permission The INCLUDE_UNIQUE_ID gate in handleGenerateKey (introduced as part of the PR157 AOSP-compliance work) returns PERMISSION_DENIED when the caller holds neither SELinux gen_unique_id nor REQUEST_UNIQUE_ID_ATTESTATION. This breaks Google Wallet card binding on real devices: Wallet's generateKey carries INCLUDE_UNIQUE_ID without holding the permission, so its attestation key request is rejected and Wallet surfaces the failure as "this phone does not meet the security requirements for Google Wallet". Symptoms reported by users: clearing GMS data only helps for a few seconds before the state regresses; no card can be added. Naive removal of the gate is not safe: AttestationBuilder honours `includeUniqueId == true` by computing an HMAC-SHA256 unique_id and embedding it in the attestation extension. With the gate gone, GMS attestation flows that include the tag end up with a unique_id in the extension that Play Integrity flags as inconsistent for the caller, turning all three integrity verdicts red. The fix here splits the difference: when the permission check fails, silently strip the INCLUDE_UNIQUE_ID tag from the KeyParameter array (and re-parse `parsedParams`) instead of rejecting the request. The key generates normally, AttestationBuilder takes the `else { ByteArray(0) }` branch, and the resulting attestation simply omits the unique_id field, matching pre-PR157 behaviour, where the tag effectively had no effect. Verified on a device that previously failed Wallet binding on the PR157 baseline: - Play Integrity: BASIC + DEVICE + STRONG all pass. - Google Wallet: card binding completes successfully. - Calls that DO hold the permission are unaffected (still emit unique_id as before). The rest of the PR157 compliance work (CALLER_NONCE handling, AuthorizeCreate ordering, USAGE_COUNT_LIMIT counters, effectiveParams merging) is preserved. --- .../shim/KeyMintSecurityLevelInterceptor.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index d7b9ced..03269b4 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -434,8 +434,8 @@ class KeyMintSecurityLevelInterceptor( SystemLogger.debug( "Handling generateKey ${keyDescriptor.alias}, attestKey=${attestationKey?.alias}" ) - val params = data.createTypedArray(KeyParameter.CREATOR)!! - val parsedParams = KeyMintAttestation(params) + var params = data.createTypedArray(KeyParameter.CREATOR)!! + var parsedParams = KeyMintAttestation(params) val isAttestKeyRequest = parsedParams.isAttestKey() if (ConfigurationManager.shouldSkipUid(callingUid) @@ -477,8 +477,16 @@ class KeyMintSecurityLevelInterceptor( return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS) } - // AOSP security_level.rs:478-485: INCLUDE_UNIQUE_ID requires - // SELinux gen_unique_id OR Android REQUEST_UNIQUE_ID_ATTESTATION + // INCLUDE_UNIQUE_ID requires SELinux gen_unique_id OR + // android.permission.REQUEST_UNIQUE_ID_ATTESTATION (AOSP + // security_level.rs:478-485). AOSP returns PERMISSION_DENIED + // when neither is held — but doing so breaks Google Wallet + // card binding (Wallet's generateKey carries the tag without + // holding the permission, and Play Integrity also fails when + // unique_id ends up in the attestation). Silently strip the + // tag so the key generates normally and the resulting + // attestation simply omits the unique_id field. This mirrors + // the pre-PR157 behavior where the tag had no effect. if (params.any { it.tag == Tag.INCLUDE_UNIQUE_ID }) { val hasSELinux = ConfigurationManager.checkSELinuxPermission( callingPid, "keystore_key", "gen_unique_id", @@ -487,8 +495,9 @@ class KeyMintSecurityLevelInterceptor( callingUid, "android.permission.REQUEST_UNIQUE_ID_ATTESTATION", ) if (!hasSELinux && !hasAndroid) { - SystemLogger.warning("[TX_ID: $txId] Rejecting INCLUDE_UNIQUE_ID for uid=$callingUid pid=$callingPid") - return InterceptorUtils.createServiceSpecificErrorReply(RESPONSE_PERMISSION_DENIED) + SystemLogger.debug("[TX_ID: $txId] Stripping INCLUDE_UNIQUE_ID for uid=$callingUid pid=$callingPid (no permission)") + params = params.filter { it.tag != Tag.INCLUDE_UNIQUE_ID }.toTypedArray() + parsedParams = KeyMintAttestation(params) } }