fix(keystore): mirror TEE device-ID capability

generateKey synthesized device-property attestation unconditionally:
the BRAND/DEVICE/PRODUCT/MANUFACTURER/MODEL tags that
setDevicePropertiesAttestationIncluded emits. A forged key thus
succeeded where real silicon returns CANNOT_ATTEST_IDS. Hardware that
never provisioned device IDs cannot attest them, so forging them is an
over-capability tell: a genuine device of the same class fails the
identical request.

Add DeviceAttestationService.canAttestDeviceIds, a lazy probe that asks
the real TEE to attest device properties once and caches the verdict.
It is gated behind isTeeFunctional, so a silent or dead TEE
short-circuits to "cannot attest" without a second doomed probe.

handleGenerateKey now returns KEYMINT_CANNOT_ATTEST_IDS for any
device-ID or device-property attestation the real TEE cannot satisfy,
uniformly across AUTO, PATCH, and GENERATE. Basic attestation carries
none of these tags and is untouched.

Verified on 23106RN0DA: kknd under GENERATE now WARNs, matching a stock
locked-bootloader device. Principle: forge health, mirror capability.
This commit is contained in:
Enginex0
2026-05-30 13:42:31 +01:00
parent 8cebcf14a8
commit a58c4798c3
2 changed files with 60 additions and 0 deletions
@@ -1,6 +1,7 @@
package org.matrix.TEESimulator.attestation
import android.annotation.SuppressLint
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyPairGenerator
@@ -60,12 +61,23 @@ object DeviceAttestationService {
// A unique alias for the key used to perform the TEE functionality check.
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
* attestation-backed key pair. The result is cached.
*/
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
* is cached. Returns null if the TEE is not functional or parsing fails.
@@ -106,6 +118,37 @@ 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
* deleted after retrieval to clean up.
@@ -29,6 +29,7 @@ import java.util.concurrent.locks.LockSupport
import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationConstants
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.DeviceAttestationService
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.core.BinderInterceptor
@@ -477,6 +478,22 @@ class KeyMintSecurityLevelInterceptor(
it.tag == Tag.ATTESTATION_ID_SECOND_IMEI
}
val hasDevicePropertyAttestation = parsedParams.brand != null ||
parsedParams.device != null ||
parsedParams.product != null ||
parsedParams.manufacturer != null ||
parsedParams.model != null
// 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)) {
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_ID_ATTESTATION for uid=$callingUid")
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)