Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.matrix.TEESimulator.attestation

import android.hardware.security.keymint.EcCurve
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.Tag
import android.hardware.security.keymint.*
import java.math.BigInteger
import java.util.Date
import javax.security.auth.x500.X500Principal
Expand All @@ -22,6 +20,8 @@ data class KeyMintAttestation(
val algorithm: Int,
val ecCurve: Int,
val ecCurveName: String,
val blockMode: List<Int>,
val padding: List<Int>,
val purpose: List<Int>,
val digest: List<Int>,
val rsaPublicExponent: BigInteger?,
Expand Down Expand Up @@ -54,6 +54,12 @@ data class KeyMintAttestation(
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
ecCurveName = params.deriveEcCurveName(),

// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),

// AOSP: [key_param(tag = PADDING, field = PaddingMode)]
padding = params.findAllPaddingMode(Tag.PADDING),

// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
purpose = params.findAllKeyPurpose(Tag.PURPOSE),

Expand Down Expand Up @@ -121,6 +127,14 @@ private fun Array<KeyParameter>.findDate(tag: Int): Date? =
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
this.find { it.tag == tag }?.value?.blob

/** Maps to AOSP field = BlockMode (Repeated) */
private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.blockMode }

/** Maps to AOSP field = BlockMode (Repeated) */
private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.paddingMode }

/** Maps to AOSP field = KeyPurpose (Repeated) */
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.keyPurpose }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ abstract class BinderInterceptor : Binder() {
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
// Code used by the backdoor binder to register a new interceptor.
private const val REGISTER_INTERCEPTOR_CODE = 1
// Code used by the backdoor binder to unregister an interceptor.
private const val UNREGISTER_INTERCEPTOR_CODE = 2

// --- Hook Type Codes ---
// Indicates that the call is for a pre-transaction hook.
Expand Down Expand Up @@ -307,5 +309,21 @@ abstract class BinderInterceptor : Binder() {
reply.recycle()
}
}

/** Uses the backdoor binder to unregister an interceptor for a specific target service. */
fun unregister(backdoor: IBinder, target: IBinder) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeStrongBinder(target)
backdoor.transact(UNREGISTER_INTERCEPTOR_CODE, data, reply, 0)
SystemLogger.info("Unregistered interceptor for target: $target")
} catch (e: Exception) {
SystemLogger.error("Failed to unregister binder interceptor.", e)
} finally {
data.recycle()
reply.recycle()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ private data class LegacyKeygenParameters(
algorithm = this.algorithm,
ecCurve = 0, // Not explicitly available in legacy args, but not critical
ecCurveName = this.ecCurveName ?: "",
blockMode = listOf<Int>(),
padding = listOf<Int>(),
purpose = this.purpose,
digest = this.digest,
rsaPublicExponent = this.rsaPublicExponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.*
import java.security.KeyPair
import java.security.SecureRandom
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationPatcher
Expand All @@ -30,7 +31,11 @@ class KeyMintSecurityLevelInterceptor(
) : BinderInterceptor() {

// --- Data Structures for State Management ---
data class GeneratedKeyInfo(val keyPair: KeyPair, val response: KeyEntryResponse)
data class GeneratedKeyInfo(
val keyPair: KeyPair,
val nspace: Long,
val response: KeyEntryResponse,
)

override fun onPreTransact(
txId: Long,
Expand All @@ -41,33 +46,39 @@ class KeyMintSecurityLevelInterceptor(
callingPid: Int,
data: Parcel,
): TransactionResult {
if (code == GENERATE_KEY_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
val shouldSkip = ConfigurationManager.shouldSkipUid(callingUid)

if (ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
return handleGenerateKey(callingUid, data)
} else if (code == IMPORT_KEY_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
when (code) {
GENERATE_KEY_TRANSACTION -> {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)

if (ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val alias =
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
?: return TransactionResult.ContinueAndSkipPost
SystemLogger.info("Handling post-${transactionNames[code]} ${alias}")
return TransactionResult.Continue
} else {
logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)
if (!shouldSkip) return handleGenerateKey(callingUid, data)
}
CREATE_OPERATION_TRANSACTION -> {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)

if (!shouldSkip) return handleCreateOperation(txId, callingUid, data)
}
IMPORT_KEY_TRANSACTION -> {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)

data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
SystemLogger.info(
"[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]"
)
return TransactionResult.Continue
}
}

logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)

return TransactionResult.ContinueAndSkipPost
}

Expand All @@ -94,6 +105,41 @@ class KeyMintSecurityLevelInterceptor(
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction
cleanupKeyData(KeyIdentifier(callingUid, keyDescriptor.alias))
} else if (code == CREATE_OPERATION_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)

data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params)
val forced = data.readBoolean()
if (forced)
SystemLogger.verbose(
"[TX_ID: $txId] Current operation has a very high pruning power."
)
val response: CreateOperationResponse =
reply.readTypedObject(CreateOperationResponse.CREATOR)!!
SystemLogger.verbose(
"[TX_ID: $txId] CreateOperationResponse: ${response.iOperation} ${response.operationChallenge}"
)

// Intercept the IKeystoreOperation binder
response.iOperation?.let { operation ->
val operationBinder = operation.asBinder()
if (!interceptedOperations.containsKey(operationBinder)) {
SystemLogger.info("Found new IKeystoreOperation. Registering interceptor...")
val backdoor = getBackdoor(target)
if (backdoor != null) {
val interceptor = OperationInterceptor(operation, backdoor)
register(backdoor, operationBinder, interceptor)
interceptedOperations[operationBinder] = interceptor
} else {
SystemLogger.error(
"Failed to get backdoor to register OperationInterceptor."
)
}
}
}
} else if (code == GENERATE_KEY_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)

Expand All @@ -109,9 +155,12 @@ class KeyMintSecurityLevelInterceptor(
// Cache the newly patched chain to ensure consistency across subsequent API calls.
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
val key = metadata.key!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
patchedChains[keyId] = newChain
SystemLogger.debug("Cached patched certificate chain for $keyId.")
SystemLogger.debug(
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
)

CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()

Expand All @@ -121,12 +170,58 @@ class KeyMintSecurityLevelInterceptor(
return TransactionResult.SkipTransaction
}

/**
* Handles the `createOperation` transaction. It checks if the operation is for a key that was
* generated in software. If so, it creates a software-based operation handler. Otherwise, it
* lets the call proceed to the real hardware service.
*/
private fun handleCreateOperation(
txId: Long,
callingUid: Int,
data: Parcel,
): TransactionResult {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!

// An operation must use the KEY_ID domain.
if (keyDescriptor.domain != Domain.KEY_ID) {
return TransactionResult.ContinueAndSkipPost
}

val nspace = keyDescriptor.nspace
val generatedKeyInfo = findGeneratedKeyByKeyId(callingUid, nspace)

if (generatedKeyInfo == null) {
SystemLogger.debug(
"[TX_ID: $txId] Operation for unknown/hardware KeyId ($nspace). Forwarding."
)
return TransactionResult.Continue
}

SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.")

val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params)

val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams)
val operationBinder = SoftwareOperationBinder(softwareOperation)

val response =
CreateOperationResponse().apply {
iOperation = operationBinder
operationChallenge = null
}

return InterceptorUtils.createTypedObjectReply(response)
}

/**
* Handles the `generateKey` transaction. Based on the configuration for the calling UID, it
* either generates a key in software or lets the call pass through to the hardware.
*/
private fun handleGenerateKey(callingUid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
SystemLogger.debug(
Expand All @@ -148,7 +243,10 @@ class KeyMintSecurityLevelInterceptor(
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))

if (needsSoftwareGeneration) {
SystemLogger.info("Generating software key for ${keyId}.")
keyDescriptor.nspace = secureRandom.nextLong()
SystemLogger.info(
"Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}]."
)

// Generate the key pair and certificate chain.
val keyData =
Expand All @@ -164,7 +262,8 @@ class KeyMintSecurityLevelInterceptor(
val response =
buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)

generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, response)
generatedKeys[keyId] =
GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
if (isAttestKeyRequest) attestationKeys.add(keyId)

// Return the metadata of our generated key, skipping the real hardware call.
Expand Down Expand Up @@ -205,11 +304,18 @@ class KeyMintSecurityLevelInterceptor(
}

companion object {
private val secureRandom = SecureRandom()

// Transaction codes for IKeystoreSecurityLevel interface.
private val GENERATE_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
private val IMPORT_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "importKey")
private val CREATE_OPERATION_TRANSACTION =
InterceptorUtils.getTransactCode(
IKeystoreSecurityLevel.Stub::class.java,
"createOperation",
)

private val transactionNames: Map<Int, String> by lazy {
IKeystoreSecurityLevel.Stub::class
Expand All @@ -228,11 +334,30 @@ class KeyMintSecurityLevelInterceptor(
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
// A set to quickly identify keys that were generated for attestation purposes.
private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
// Stores interceptors for active cryptographic operations.
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()

// --- Public Accessors for Other Interceptors ---
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
generatedKeys[keyId]?.response

/**
* Finds a software-generated key by first filtering all known keys by the caller's UID, and
* then matching the specific nspace.
*
* @param callingUid The UID of the process that initiated the createOperation call.
* @param nspace The unique key identifier from the operation's KeyDescriptor.
* @return The matching GeneratedKeyInfo if found, otherwise null.
*/
private fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long): GeneratedKeyInfo? {
// Iterate through all entries in the map to check both the key (for UID) and value (for
// nspace).
return generatedKeys.entries
.filter { (keyIdentifier, _) -> keyIdentifier.uid == callingUid }
.find { (_, info) -> info.nspace == nspace }
?.value
}

fun getPatchedChain(keyId: KeyIdentifier): Array<Certificate>? = patchedChains[keyId]

fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
Expand All @@ -249,6 +374,15 @@ class KeyMintSecurityLevelInterceptor(
}
}

fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
// Unregister from the native hook layer first.
unregister(backdoor, operationBinder)

if (interceptedOperations.remove(operationBinder) != null) {
SystemLogger.debug("Removed operation interceptor for binder: $operationBinder")
}
}

// Clears all cached keys.
fun clearAllGeneratedKeys(reason: String? = null) {
val count = generatedKeys.size
Expand Down
Loading