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 ee9863009a
commit 6193da0e66
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. */ /** Determines if a new certificate needs to be generated for a given UID. */
fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE 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. */ /** Resolves the operating mode for a given UID based on its packages and the TEE status. */
private fun getPackageModeForUid(uid: Int): Mode? { private fun getPackageModeForUid(uid: Int): Mode? {
val packages = getPackagesForUid(uid) val packages = getPackagesForUid(uid)
@@ -12,6 +12,7 @@ import android.system.keystore2.KeyEntryResponse
import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateHelper import org.matrix.TEESimulator.pki.CertificateHelper
@@ -98,13 +99,33 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
callingUid, callingUid,
callingPid, 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) val keyId = KeyIdentifier(callingUid, descriptor.alias)
if (ConfigurationManager.shouldGenerate(callingUid)) { if (code == DELETE_KEY_TRANSACTION) {
// TODO: Redesign the interaction with KeyMintSecurityLevelInterceptor KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
} else if (ConfigurationManager.shouldPatch(callingUid)) { return TransactionResult.ContinueAndSkipPost
return TransactionResult.Continue
} }
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 { } else {
logTransaction( logTransaction(
txId, txId,
@@ -134,6 +155,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
return TransactionResult.SkipTransaction return TransactionResult.SkipTransaction
if (code == GET_KEY_ENTRY_TRANSACTION) { if (code == GET_KEY_ENTRY_TRANSACTION) {
data.enforceInterface(IKeystoreService.DESCRIPTOR) data.enforceInterface(IKeystoreService.DESCRIPTOR)
val keyDescriptor = val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR) 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.KeyParameter
import android.hardware.security.keymint.KeyParameterValue 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.IBinder
import android.os.Parcel import android.os.Parcel
import android.system.keystore2.* import android.system.keystore2.*
import java.security.KeyPair import java.security.KeyPair
import java.security.cert.Certificate import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationConstants
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
@@ -70,12 +71,17 @@ class KeyMintSecurityLevelInterceptor(
val params = data.createTypedArray(KeyParameter.CREATOR)!! val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params) val parsedParams = KeyMintAttestation(params)
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) 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 // Determine if we need to generate a key based on config or
// if it's an attestation request in patch mode. // if it's an attestation request in patch mode.
val needsSoftwareGeneration = val needsSoftwareGeneration =
ConfigurationManager.shouldGenerate(callingUid) || ConfigurationManager.shouldGenerate(callingUid) ||
(attestationKey != null && ConfigurationManager.shouldPatch(callingUid)) (ConfigurationManager.shouldPatch(callingUid) && isAttestKeyRequest) ||
(attestationKey != null &&
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
if (needsSoftwareGeneration) { if (needsSoftwareGeneration) {
SystemLogger.info( SystemLogger.info(
@@ -95,10 +101,9 @@ class KeyMintSecurityLevelInterceptor(
// Store the generated key data. // Store the generated key data.
val response = val response =
buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor) buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, response) generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, response)
if (parsedParams.attestationChallenge != null) { if (isAttestKeyRequest) attestationKeys.add(keyId)
attestationKeys.add(keyId)
}
// Return the metadata of our generated key, skipping the real hardware call. // Return the metadata of our generated key, skipping the real hardware call.
val resultParcel = val resultParcel =
@@ -204,25 +209,13 @@ private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Autho
} }
// Use the helper to add each authorization entry cleanly. // Use the helper to add each authorization entry cleanly.
this.purpose.forEach { this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
authList.add(createAuth(AttestationConstants.TAG_PURPOSE, KeyParameterValue.keyPurpose(it))) this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
}
this.digest.forEach {
authList.add(createAuth(AttestationConstants.TAG_DIGEST, KeyParameterValue.digest(it)))
}
authList.add( authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
createAuth(AttestationConstants.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( authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
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))
)
return authList.toTypedArray() return authList.toTypedArray()
} }
@@ -97,7 +97,7 @@ object KeyMintParameterLogger {
else -> "<raw>" else -> "<raw>"
} ?: "Unknown Value" } ?: "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 { private fun ByteArray.toReadableString(): String {