fix(shim): match real gen-mode auth shape

Real keystore2 (captured on-device, MediaTek SDK 35) emits 11 EC
authorizations in the generateKey KeyMetadata: no VENDOR_PATCHLEVEL or
BOOT_PATCHLEVEL, and USER_ID tagged at SecurityLevel.SOFTWARE. The shim
emitted 13 with both patchlevels and USER_ID at KEYSTORE.

Duck-Detector's generate-mode parcel fingerprint stride-walks the reply
and keys on the 13-entry layout, so the two extra entries were the tell.
Drop both patchlevels from the authorization list (they remain in the
attestation extension via AttestationBuilder, so attestation content is
unchanged) and move USER_ID to SOFTWARE to mirror the captured device.

On-device 23106RN0DA: generate-mode fingerprint signal gone (0 local),
TEE tamper score 18 -> 8.

Refs Phase 7 .omc/plans/tee-fingerprint-phase-7-generate-mode-coherence.md
This commit is contained in:
Enginex0
2026-05-30 13:42:31 +01:00
parent cf4fb127f2
commit 43e948efb3
@@ -1359,14 +1359,13 @@ private fun KeyMintAttestation.toAuthorizations(
if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.OS_PATCHLEVEL, KeyParameterValue.integer(osPatch)))
}
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.VENDOR_PATCHLEVEL, KeyParameterValue.integer(vendorPatch)))
}
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
}
// Real keystore2 (captured on-device: MediaTek, Android 15) does NOT surface
// VENDOR_PATCHLEVEL or BOOT_PATCHLEVEL in the generateKey KeyMetadata.authorizations
// — they exist only in the attestation extension. Emitting them yielded a
// 13-authorization EC reply where the genuine HAL emits 11, which is precisely the
// structural tell Duck-Detector's generate-mode parcel fingerprint keys on (its
// stride-walk lands on the 13-entry layout). Both values remain in the attestation
// extension via AttestationBuilder, so attestation content is unchanged.
/**
* Keystore-enforced authorizations (CREATION_DATETIME, ACTIVE_DATETIME,
@@ -1408,7 +1407,17 @@ private fun KeyMintAttestation.toAuthorizations(
authList.add(createKeystoreAuth(Tag.UNLOCKED_DEVICE_REQUIRED, KeyParameterValue.boolValue(true)))
}
authList.add(createKeystoreAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
// Captured real keystore2 tags USER_ID at SecurityLevel.SOFTWARE (0), even though
// CREATION_DATETIME above is KEYSTORE (100). Mirror that split exactly.
authList.add(
Authorization().apply {
this.keyParameter = KeyParameter().apply {
this.tag = Tag.USER_ID
this.value = KeyParameterValue.integer(callingUid / 100000)
}
this.securityLevel = SecurityLevel.SOFTWARE
},
)
return authList.toTypedArray()
}