fix(dispatch): gate AUTO forge on RSA capability
v282 forged every AUTO attestation that carried a challenge, so a strict app that validates attestation server-side, such as Kraken, rejected the software-forged chain where it accepted a patched real-TEE chain, breaking login. The trigger was algorithm-blind: the AUTO capability probe only mints an EC key, so it could not tell an EC-capable TEE from one that cannot provision RSA attestation keys. Add an isRsaAttestable probe and forge AUTO attestation only for RSA the real TEE cannot provision. EC and RSA-capable devices keep their genuine TEE chain via PATCH, restoring the v280 behavior strict apps depend on while preserving the RSA red fix on incapable devices. Refs #37
This commit is contained in:
@@ -8,6 +8,7 @@ import java.security.KeyStore
|
|||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
|
import java.security.spec.RSAKeyGenParameterSpec
|
||||||
import org.bouncycastle.asn1.ASN1Integer
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||||
import org.bouncycastle.asn1.ASN1OctetString
|
import org.bouncycastle.asn1.ASN1OctetString
|
||||||
@@ -60,6 +61,7 @@ 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"
|
||||||
|
private const val RSA_ATTEST_CHECK_KEY_ALIAS = "TEESimulator_RsaAttestCheck"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -67,6 +69,13 @@ object DeviceAttestationService {
|
|||||||
*/
|
*/
|
||||||
val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() }
|
val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily determines whether the real TEE can attest an RSA key. A device may mint EC keys yet
|
||||||
|
* lack a provisioned RSA attestation key, so [isTeeFunctional] alone over-reports capability.
|
||||||
|
* AUTO dispatch reads this to forge RSA attestation only where the hardware genuinely cannot.
|
||||||
|
*/
|
||||||
|
val isRsaAttestable: Boolean by lazy { checkRsaAttestability() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -107,6 +116,40 @@ object DeviceAttestationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the real TEE can attest an RSA key by generating one with an attestation
|
||||||
|
* challenge. Mirrors [checkTeeFunctionality]; the request runs as the module UID, so it is
|
||||||
|
* skipped by interception and reaches genuine hardware rather than the forge path.
|
||||||
|
*
|
||||||
|
* @return `true` if an RSA key with attestation was generated successfully, `false` otherwise.
|
||||||
|
*/
|
||||||
|
private fun checkRsaAttestability(): Boolean {
|
||||||
|
SystemLogger.info("Performing RSA attestation capability check...")
|
||||||
|
return try {
|
||||||
|
val keyPairGenerator =
|
||||||
|
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore")
|
||||||
|
|
||||||
|
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
|
||||||
|
|
||||||
|
val spec =
|
||||||
|
KeyGenParameterSpec.Builder(RSA_ATTEST_CHECK_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
|
||||||
|
.setAlgorithmParameterSpec(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
|
||||||
|
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||||
|
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
|
||||||
|
.setAttestationChallenge(challenge)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
keyPairGenerator.initialize(spec)
|
||||||
|
keyPairGenerator.generateKeyPair()
|
||||||
|
|
||||||
|
SystemLogger.info("RSA attestation capability check successful.")
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.warning("RSA attestation capability check failed.", e)
|
||||||
|
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.
|
||||||
|
|||||||
+7
-3
@@ -28,6 +28,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
|
||||||
@@ -713,8 +714,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
// Device-ID attestation must be forged, not patched: the real TEE returns
|
// Device-ID attestation must be forged, not patched: the real TEE returns
|
||||||
// CANNOT_ATTEST_IDS, so there is no real chain to patch — only a synthetic one
|
// CANNOT_ATTEST_IDS, so there is no real chain to patch — only a synthetic one
|
||||||
// carrying the requested IDs and rooted under the keybox will satisfy the caller.
|
// carrying the requested IDs and rooted under the keybox will satisfy the caller.
|
||||||
// AUTO attestation forges from the keybox instead of trusting the EC-only TEE
|
// AUTO forges RSA attestation only when the real TEE cannot provision an RSA
|
||||||
// probe, which cannot tell whether the device can attest RSA / device-ID / StrongBox.
|
// attestation key (isRsaAttestable). EC and RSA-capable devices keep their genuine
|
||||||
|
// TEE chain via PATCH, which strict callers accept where a forgery is rejected.
|
||||||
val forceGenerate =
|
val forceGenerate =
|
||||||
oversized ||
|
oversized ||
|
||||||
ConfigurationManager.shouldGenerate(callingUid) ||
|
ConfigurationManager.shouldGenerate(callingUid) ||
|
||||||
@@ -722,7 +724,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
attestationKey != null ||
|
attestationKey != null ||
|
||||||
hasDeviceIdAttestation ||
|
hasDeviceIdAttestation ||
|
||||||
(ConfigurationManager.isAutoMode(callingUid) &&
|
(ConfigurationManager.isAutoMode(callingUid) &&
|
||||||
parsedParams.attestationChallenge != null)
|
parsedParams.attestationChallenge != null &&
|
||||||
|
parsedParams.algorithm == Algorithm.RSA &&
|
||||||
|
!DeviceAttestationService.isRsaAttestable)
|
||||||
|
|
||||||
SystemLogger.trace {
|
SystemLogger.trace {
|
||||||
"[TRACE-$txId] dispatch: forceGen=$forceGenerate hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest"
|
"[TRACE-$txId] dispatch: forceGen=$forceGenerate hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest"
|
||||||
|
|||||||
Reference in New Issue
Block a user