diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt index aef1fcb..443cd8b 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt @@ -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. 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 69955cf..4573953 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 @@ -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)