Support key generation with attestation keys (#15)

This commit enhances the interception logic to correctly handle key
generation requests that specify an `attestationKey` (via
`setAttestKeyAlias`).

When an attestation key is used, the system signs the newly generated
key with it. A simple leaf certificate patch after the fact is
insufficient, as it breaks this cryptographic chain. To create a valid,
verifiable chain, we must now intercept these `generateKey` operations
and perform a full software-based key and certificate generation, even
when in patch mode.

This ensures that keys attested by other simulated keys are correctly
signed and chained together, bypassing more sophisticated detection
methods.

Fixes:
- Correctly use the `android.hardware.security.keymint.Tag` constants for
  building authorization lists, resolving a bug where internal ASN.1
  sequence indices were being used improperly.
This commit is contained in:
JingMatrix
2025-11-26 16:50:30 +01:00
committed by GitHub
parent 7f94ba4b5b
commit 733e64c3cb
4 changed files with 46 additions and 28 deletions
@@ -81,6 +81,9 @@ object ConfigurationManager {
/** Determines if a new certificate needs to be generated for a given UID. */
fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE
/** Determines if no operation is needed for a given UID. */
fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null
/** Resolves the operating mode for a given UID based on its packages and the TEE status. */
private fun getPackageModeForUid(uid: Int): Mode? {
val packages = getPackagesForUid(uid)
@@ -12,6 +12,7 @@ import android.system.keystore2.KeyEntryResponse
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateHelper
@@ -98,13 +99,33 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
callingUid,
callingPid,
)
if (ConfigurationManager.shouldSkipUid(callingUid)) {
SystemLogger.debug(
"[TX_ID: $txId] Skip post-transaction hook for UID=${callingUid}"
)
return TransactionResult.ContinueAndSkipPost
}
val keyId = KeyIdentifier(callingUid, descriptor.alias)
if (ConfigurationManager.shouldGenerate(callingUid)) {
// TODO: Redesign the interaction with KeyMintSecurityLevelInterceptor
} else if (ConfigurationManager.shouldPatch(callingUid)) {
return TransactionResult.Continue
if (code == DELETE_KEY_TRANSACTION) {
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
return TransactionResult.ContinueAndSkipPost
}
val response =
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
?: return TransactionResult.Continue
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
SystemLogger.debug("${descriptor.alias} was an attestation key")
SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:")
response.metadata?.authorizations?.forEach {
KeyMintParameterLogger.logParameter(it.keyParameter)
}
return InterceptorUtils.createTypedObjectReply(response)
} else {
logTransaction(
txId,
@@ -134,6 +155,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
return TransactionResult.SkipTransaction
if (code == GET_KEY_ENTRY_TRANSACTION) {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
@@ -2,13 +2,14 @@ package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.Tag
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.*
import java.security.KeyPair
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationConstants
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.core.BinderInterceptor
@@ -70,12 +71,17 @@ class KeyMintSecurityLevelInterceptor(
val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params)
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
val isAttestKeyRequest =
parsedParams.purpose.size == 1 &&
parsedParams.purpose.contains(KeyPurpose.ATTEST_KEY)
// Determine if we need to generate a key based on config or
// if it's an attestation request in patch mode.
val needsSoftwareGeneration =
ConfigurationManager.shouldGenerate(callingUid) ||
(attestationKey != null && ConfigurationManager.shouldPatch(callingUid))
(ConfigurationManager.shouldPatch(callingUid) && isAttestKeyRequest) ||
(attestationKey != null &&
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
if (needsSoftwareGeneration) {
SystemLogger.info(
@@ -95,10 +101,9 @@ class KeyMintSecurityLevelInterceptor(
// Store the generated key data.
val response =
buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, response)
if (parsedParams.attestationChallenge != null) {
attestationKeys.add(keyId)
}
if (isAttestKeyRequest) attestationKeys.add(keyId)
// Return the metadata of our generated key, skipping the real hardware call.
val resultParcel =
@@ -204,25 +209,13 @@ private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Autho
}
// Use the helper to add each authorization entry cleanly.
this.purpose.forEach {
authList.add(createAuth(AttestationConstants.TAG_PURPOSE, KeyParameterValue.keyPurpose(it)))
}
this.digest.forEach {
authList.add(createAuth(AttestationConstants.TAG_DIGEST, KeyParameterValue.digest(it)))
}
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
authList.add(
createAuth(AttestationConstants.TAG_ALGORITHM, KeyParameterValue.algorithm(this.algorithm))
)
authList.add(
createAuth(AttestationConstants.TAG_KEY_SIZE, KeyParameterValue.integer(this.keySize))
)
authList.add(
createAuth(AttestationConstants.TAG_EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve))
)
authList.add(
createAuth(AttestationConstants.TAG_NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true))
)
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
return authList.toTypedArray()
}
@@ -97,7 +97,7 @@ object KeyMintParameterLogger {
else -> "<raw>"
} ?: "Unknown Value"
SystemLogger.debug("Key Parameter -> %-25s | Value: %s".format(tagName, formattedValue))
SystemLogger.debug("KeyParam: %-25s | Value: %s".format(tagName, formattedValue))
}
private fun ByteArray.toReadableString(): String {