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:
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -60,12 +61,23 @@ 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.
|
||||||
@@ -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
|
* 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.
|
||||||
|
|||||||
+17
@@ -29,6 +29,7 @@ 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
|
||||||
@@ -477,6 +478,22 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
it.tag == Tag.ATTESTATION_ID_SECOND_IMEI
|
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)) {
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user