fix(keystore): forge device-property attestation

The canAttestDeviceIds gate (3575c74) probed the live TEE to decide
whether to honor device-property/ID attestation. That probe is gated on
isTeeFunctional, which is false on every dead-TEE device the module
serves, so GENERATE mode rejected all such requests with
CANNOT_ATTEST_IDS, including GMS Play Integrity's hardware path, which
broke BHIM and any UPI/Play-Integrity-gated app.

Remove the gate. Device-property attestation (BRAND/MODEL/...) now forges
unconditionally, as genuine devices universally attest it. Device-ID
attestation stays governed by the pre-existing caller-permission check,
the real KeyMint rule: privileged callers get it, ordinary apps do not.
Drop the now-unused DeviceAttestationService.canAttestDeviceIds probe.
This commit is contained in:
Enginex0
2026-05-30 14:51:48 +01:00
parent 8d195010fd
commit 254fb0f0a9
2 changed files with 9 additions and 63 deletions
@@ -1,7 +1,6 @@
package org.matrix.TEESimulator.attestation package org.matrix.TEESimulator.attestation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Build
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
@@ -61,23 +60,12 @@ object DeviceAttestationService {
// A unique alias for the key used to perform the TEE functionality check. // A unique alias for the key used to perform the TEE functionality check.
private const val TEE_CHECK_KEY_ALIAS = "TEESimulator_AttestationCheck" private const val TEE_CHECK_KEY_ALIAS = "TEESimulator_AttestationCheck"
// Alias for the device-ID attestation capability probe.
private const val DEVICE_ID_CHECK_KEY_ALIAS = "TEESimulator_DeviceIdCheck"
/** /**
* Lazily determines if the device's TEE is functional by attempting to generate an * Lazily determines if the device's TEE is functional by attempting to generate an
* attestation-backed key pair. The result is cached. * attestation-backed key pair. The result is cached.
*/ */
val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() } val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() }
/**
* Lazily mirrors whether the real TEE can attest device identifiers/properties (the tags added
* by `setDevicePropertiesAttestationIncluded`). Hardware that never provisioned device IDs
* returns CANNOT_ATTEST_IDS; the synthesizer consults this so it never forges a capability the
* real silicon lacks. Cached.
*/
val canAttestDeviceIds: Boolean by lazy { checkDeviceIdAttestation() }
/** /**
* Lazily fetches and parses attestation data from a genuinely generated certificate. The result * Lazily fetches and parses attestation data from a genuinely generated certificate. The result
* is cached. Returns null if the TEE is not functional or parsing fails. * is cached. Returns null if the TEE is not functional or parsing fails.
@@ -118,37 +106,6 @@ object DeviceAttestationService {
} }
} }
/**
* Probes whether the real TEE can satisfy device-ID/property attestation, mirroring its actual
* capability. Gated behind [isTeeFunctional] so a dead TEE never triggers a second doomed
* probe — it simply reports `false` (cannot attest), the faithful result for such hardware.
*/
private fun checkDeviceIdAttestation(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return false
if (!isTeeFunctional) return false
return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val keyPairGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
val spec =
KeyGenParameterSpec.Builder(DEVICE_ID_CHECK_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.setDevicePropertiesAttestationIncluded(true)
.build()
keyPairGenerator.initialize(spec)
keyPairGenerator.generateKeyPair()
runCatching { keyStore.deleteEntry(DEVICE_ID_CHECK_KEY_ALIAS) }
SystemLogger.info("Device-ID attestation supported by TEE.")
true
} catch (_: Exception) {
SystemLogger.info("Device-ID attestation not supported by TEE; mirroring as cannot-attest.")
false
}
}
/** /**
* Retrieves the attestation certificate generated during the TEE check. The key entry is * Retrieves the attestation certificate generated during the TEE check. The key entry is
* deleted after retrieval to clean up. * deleted after retrieval to clean up.
@@ -192,7 +149,8 @@ object DeviceAttestationService {
// The extension's value is an ASN.1 sequence. // The extension's value is an ASN.1 sequence.
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets) val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
SystemLogger.verbose { SystemLogger.verbose {
val formattedString = keyDescriptionSeq.joinToString(separator = ", ") { val formattedString =
keyDescriptionSeq.joinToString(separator = ", ") {
AttestationPatcher.formatAsn1Primitive(it) AttestationPatcher.formatAsn1Primitive(it)
} }
"Cached attestation data: $formattedString" "Cached attestation data: $formattedString"
@@ -29,7 +29,6 @@ import java.util.concurrent.locks.LockSupport
import org.matrix.TEESimulator.attestation.AttestationBuilder import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationConstants import org.matrix.TEESimulator.attestation.AttestationConstants
import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.DeviceAttestationService
import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.core.BinderInterceptor import org.matrix.TEESimulator.interception.core.BinderInterceptor
@@ -478,22 +477,11 @@ class KeyMintSecurityLevelInterceptor(
it.tag == Tag.ATTESTATION_ID_SECOND_IMEI it.tag == Tag.ATTESTATION_ID_SECOND_IMEI
} }
val hasDevicePropertyAttestation = parsedParams.brand != null || // Device-ID attestation mirrors a real KeyMint: privileged callers (GMS/system)
parsedParams.device != null || // get it, ordinary apps get CANNOT_ATTEST_IDS. The permission check below is that
parsedParams.product != null || // rule. Device-property attestation (BRAND/MODEL/...) is honored unconditionally —
parsedParams.manufacturer != null || // genuine devices universally attest it, and it is what Play Integrity's hardware
parsedParams.model != null // path needs. Capability is keyed to the device we present, not the real (dead) TEE.
// Mirror the real TEE's capability: hardware that never provisioned device IDs
// returns CANNOT_ATTEST_IDS. Synthesizing device-ID/property attestation a chip of
// this class cannot produce is an over-capability tell — a genuine device fails the
// same request. Forge health, mirror capability.
if ((hasDeviceIdAttestation || hasDevicePropertyAttestation) &&
!DeviceAttestationService.canAttestDeviceIds) {
SystemLogger.info("[TX_ID: $txId] Real TEE cannot attest device IDs; returning CANNOT_ATTEST_IDS for uid=$callingUid (mirroring hardware)")
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
}
if(hasDeviceIdAttestation && !AndroidPermissionUtils.hasDeviceAttestationPermission(callingUid)) { if(hasDeviceIdAttestation && !AndroidPermissionUtils.hasDeviceAttestationPermission(callingUid)) {
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_ID_ATTESTATION for uid=$callingUid") SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_ID_ATTESTATION for uid=$callingUid")
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS) return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)