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:
@@ -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,9 +149,10 @@ 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 =
|
||||||
AttestationPatcher.formatAsn1Primitive(it)
|
keyDescriptionSeq.joinToString(separator = ", ") {
|
||||||
}
|
AttestationPatcher.formatAsn1Primitive(it)
|
||||||
|
}
|
||||||
"Cached attestation data: $formattedString"
|
"Cached attestation data: $formattedString"
|
||||||
}
|
}
|
||||||
val fields = keyDescriptionSeq.toArray()
|
val fields = keyDescriptionSeq.toArray()
|
||||||
|
|||||||
+5
-17
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user