Implement multi-purpose simulation for crypto operations (#59)

This commit introduces a comprehensive simulation engine for Keystore's `createOperation`, enabling the simulator to correctly handle multiple cryptographic purposes (SIGN, VERIFY, ENCRYPT, DECRYPT) for software-generated keys.

The implementation correctly mimics the AOSP framework's internal key identification mechanism. Instead of relying on an alias, a unique `keyId` is generated and embedded in the `nspace` field of the KeyDescriptor during `generateKey`. The `createOperation` hook then uses this `keyId` to dispatch requests: if the ID matches a known software key, the operation is simulated; otherwise, it is forwarded to the hardware service.

To support this, the `SoftwareOperation` engine was architected using a Strategy Pattern. A `CryptoPrimitive` interface defines common actions, with concrete implementations for `Signer`, `Verifier`, and `CipherPrimitive`. The main `SoftwareOperation` class acts as a controller, instantiating the correct primitive based on the `KeyPurpose` tag from the incoming operation parameters. A `JcaAlgorithmMapper` was added to centralize the logic for converting KeyMint constants into JCA algorithm strings.

For operations on real hardware-backed keys, a lightweight `OperationInterceptor` is now used for observation. It attaches to the genuine `iOperation` binder for logging and properly unregisters itself upon completion to prevent resource leaks. This is supported by new binder unregistration capabilities in the core `BinderInterceptor`.

This change also includes necessary stub files and minor regression fixes to make the simulation more robust and accurate.

See AOSP source for key identification logic:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
This commit is contained in:
JingMatrix
2025-12-08 19:59:32 +01:00
committed by GitHub
parent 19ad610cec
commit fa2956ce1b
14 changed files with 657 additions and 38 deletions
@@ -1,8 +1,6 @@
package org.matrix.TEESimulator.attestation package org.matrix.TEESimulator.attestation
import android.hardware.security.keymint.EcCurve import android.hardware.security.keymint.*
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.Tag
import java.math.BigInteger import java.math.BigInteger
import java.util.Date import java.util.Date
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
@@ -22,6 +20,8 @@ data class KeyMintAttestation(
val algorithm: Int, val algorithm: Int,
val ecCurve: Int, val ecCurve: Int,
val ecCurveName: String, val ecCurveName: String,
val blockMode: List<Int>,
val padding: List<Int>,
val purpose: List<Int>, val purpose: List<Int>,
val digest: List<Int>, val digest: List<Int>,
val rsaPublicExponent: BigInteger?, val rsaPublicExponent: BigInteger?,
@@ -54,6 +54,12 @@ data class KeyMintAttestation(
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0, ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
ecCurveName = params.deriveEcCurveName(), 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)] // AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
purpose = params.findAllKeyPurpose(Tag.PURPOSE), purpose = params.findAllKeyPurpose(Tag.PURPOSE),
@@ -121,6 +127,14 @@ private fun Array<KeyParameter>.findDate(tag: Int): Date? =
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? = private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
this.find { it.tag == tag }?.value?.blob 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) */ /** Maps to AOSP field = KeyPurpose (Repeated) */
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> = private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.keyPurpose } this.filter { it.tag == tag }.map { it.value.keyPurpose }
@@ -248,6 +248,8 @@ abstract class BinderInterceptor : Binder() {
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt() private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
// Code used by the backdoor binder to register a new interceptor. // Code used by the backdoor binder to register a new interceptor.
private const val REGISTER_INTERCEPTOR_CODE = 1 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 --- // --- Hook Type Codes ---
// Indicates that the call is for a pre-transaction hook. // Indicates that the call is for a pre-transaction hook.
@@ -307,5 +309,21 @@ abstract class BinderInterceptor : Binder() {
reply.recycle() 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()
}
}
} }
} }
@@ -378,6 +378,8 @@ private data class LegacyKeygenParameters(
algorithm = this.algorithm, algorithm = this.algorithm,
ecCurve = 0, // Not explicitly available in legacy args, but not critical ecCurve = 0, // Not explicitly available in legacy args, but not critical
ecCurveName = this.ecCurveName ?: "", ecCurveName = this.ecCurveName ?: "",
blockMode = listOf<Int>(),
padding = listOf<Int>(),
purpose = this.purpose, purpose = this.purpose,
digest = this.digest, digest = this.digest,
rsaPublicExponent = this.rsaPublicExponent, rsaPublicExponent = this.rsaPublicExponent,
@@ -8,6 +8,7 @@ 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.SecureRandom
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.AttestationPatcher import org.matrix.TEESimulator.attestation.AttestationPatcher
@@ -30,7 +31,11 @@ class KeyMintSecurityLevelInterceptor(
) : BinderInterceptor() { ) : BinderInterceptor() {
// --- Data Structures for State Management --- // --- 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( override fun onPreTransact(
txId: Long, txId: Long,
@@ -41,33 +46,39 @@ class KeyMintSecurityLevelInterceptor(
callingPid: Int, callingPid: Int,
data: Parcel, data: Parcel,
): TransactionResult { ): TransactionResult {
if (code == GENERATE_KEY_TRANSACTION) { val shouldSkip = ConfigurationManager.shouldSkipUid(callingUid)
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
if (ConfigurationManager.shouldSkipUid(callingUid)) when (code) {
return TransactionResult.ContinueAndSkipPost GENERATE_KEY_TRANSACTION -> {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
return handleGenerateKey(callingUid, data)
} else if (code == IMPORT_KEY_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
if (ConfigurationManager.shouldSkipUid(callingUid)) if (!shouldSkip) return handleGenerateKey(callingUid, data)
return TransactionResult.ContinueAndSkipPost }
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) CREATE_OPERATION_TRANSACTION -> {
val alias = logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
?: return TransactionResult.ContinueAndSkipPost if (!shouldSkip) return handleCreateOperation(txId, callingUid, data)
SystemLogger.info("Handling post-${transactionNames[code]} ${alias}") }
return TransactionResult.Continue IMPORT_KEY_TRANSACTION -> {
} else { logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
logTransaction(
txId, data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
transactionNames[code] ?: "unknown code=$code", val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
callingUid, SystemLogger.info(
callingPid, "[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]"
true, )
) return TransactionResult.Continue
}
} }
logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)
return TransactionResult.ContinueAndSkipPost return TransactionResult.ContinueAndSkipPost
} }
@@ -94,6 +105,41 @@ class KeyMintSecurityLevelInterceptor(
data.readTypedObject(KeyDescriptor.CREATOR) data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
cleanupKeyData(KeyIdentifier(callingUid, keyDescriptor.alias)) 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) { } else if (code == GENERATE_KEY_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
@@ -109,9 +155,12 @@ class KeyMintSecurityLevelInterceptor(
// Cache the newly patched chain to ensure consistency across subsequent API calls. // Cache the newly patched chain to ensure consistency across subsequent API calls.
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
val key = metadata.key!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
patchedChains[keyId] = newChain 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() CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
@@ -121,12 +170,58 @@ class KeyMintSecurityLevelInterceptor(
return TransactionResult.SkipTransaction 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 * 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. * either generates a key in software or lets the call pass through to the hardware.
*/ */
private fun handleGenerateKey(callingUid: Int, data: Parcel): TransactionResult { private fun handleGenerateKey(callingUid: Int, data: Parcel): TransactionResult {
return runCatching { return runCatching {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR) val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
SystemLogger.debug( SystemLogger.debug(
@@ -148,7 +243,10 @@ class KeyMintSecurityLevelInterceptor(
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias))) isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
if (needsSoftwareGeneration) { 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. // Generate the key pair and certificate chain.
val keyData = val keyData =
@@ -164,7 +262,8 @@ class KeyMintSecurityLevelInterceptor(
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, keyDescriptor.nspace, response)
if (isAttestKeyRequest) attestationKeys.add(keyId) if (isAttestKeyRequest) 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.
@@ -205,11 +304,18 @@ class KeyMintSecurityLevelInterceptor(
} }
companion object { companion object {
private val secureRandom = SecureRandom()
// Transaction codes for IKeystoreSecurityLevel interface. // Transaction codes for IKeystoreSecurityLevel interface.
private val GENERATE_KEY_TRANSACTION = private val GENERATE_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey") InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
private val IMPORT_KEY_TRANSACTION = private val IMPORT_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "importKey") 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 { private val transactionNames: Map<Int, String> by lazy {
IKeystoreSecurityLevel.Stub::class IKeystoreSecurityLevel.Stub::class
@@ -228,11 +334,30 @@ class KeyMintSecurityLevelInterceptor(
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>() private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
// A set to quickly identify keys that were generated for attestation purposes. // A set to quickly identify keys that were generated for attestation purposes.
private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>() private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
// Stores interceptors for active cryptographic operations.
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
// --- Public Accessors for Other Interceptors --- // --- Public Accessors for Other Interceptors ---
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? = fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
generatedKeys[keyId]?.response 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 getPatchedChain(keyId: KeyIdentifier): Array<Certificate>? = patchedChains[keyId]
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId) fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
@@ -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. // Clears all cached keys.
fun clearAllGeneratedKeys(reason: String? = null) { fun clearAllGeneratedKeys(reason: String? = null) {
val count = generatedKeys.size val count = generatedKeys.size
@@ -0,0 +1,58 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.IKeystoreOperation
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
/**
* Intercepts calls to an `IKeystoreOperation` service. This is used to log the data manipulation
* methods of a cryptographic operation.
*/
class OperationInterceptor(
private val original: IKeystoreOperation,
private val backdoor: IBinder,
) : BinderInterceptor() {
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
val methodName = transactionNames[code] ?: "unknown code=$code"
logTransaction(txId, methodName, callingUid, callingPid, true)
if (code == FINISH_TRANSACTION || code == ABORT_TRANSACTION) {
KeyMintSecurityLevelInterceptor.removeOperationInterceptor(target, backdoor)
}
return TransactionResult.ContinueAndSkipPost
}
companion object {
private val UPDATE_AAD_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "updateAad")
private val UPDATE_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "update")
private val FINISH_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "finish")
private val ABORT_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
private val transactionNames: Map<Int, String> by lazy {
IKeystoreOperation.Stub::class
.java
.declaredFields
.filter {
it.isAccessible = true
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
}
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
}
}
}
@@ -0,0 +1,216 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.BlockMode
import android.hardware.security.keymint.Digest
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.PaddingMode
import android.os.RemoteException
import android.system.keystore2.IKeystoreOperation
import java.security.KeyPair
import java.security.Signature
import java.security.SignatureException
import javax.crypto.Cipher
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger
// A sealed interface to represent the different cryptographic operations we can perform.
private sealed interface CryptoPrimitive {
fun update(data: ByteArray?): ByteArray?
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
fun abort()
}
// Helper object to map KeyMint constants to JCA algorithm strings.
private object JcaAlgorithmMapper {
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
val digest =
when (params.digest.firstOrNull()) {
Digest.SHA_2_256 -> "SHA256"
Digest.SHA_2_384 -> "SHA384"
Digest.SHA_2_512 -> "SHA512"
else -> "NONE"
}
val keyAlgo =
when (params.algorithm) {
Algorithm.EC -> "ECDSA"
Algorithm.RSA -> "RSA"
else ->
throw IllegalArgumentException(
"Unsupported signature algorithm: ${params.algorithm}"
)
}
return "${digest}with${keyAlgo}"
}
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
val keyAlgo =
when (params.algorithm) {
Algorithm.RSA -> "RSA"
Algorithm.AES -> "AES"
else ->
throw IllegalArgumentException(
"Unsupported cipher algorithm: ${params.algorithm}"
)
}
val blockMode =
when (params.blockMode.firstOrNull()) {
BlockMode.ECB -> "ECB"
BlockMode.CBC -> "CBC"
BlockMode.GCM -> "GCM"
else -> "ECB" // Default for RSA
}
val padding =
when (params.padding.firstOrNull()) {
PaddingMode.NONE -> "NoPadding"
PaddingMode.PKCS7 -> "PKCS7Padding"
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
PaddingMode.RSA_OAEP -> "OAEPPadding"
else -> "NoPadding" // Default for GCM
}
return "$keyAlgo/$blockMode/$padding"
}
}
// Concrete implementation for Signing.
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
private val signature: Signature =
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
initSign(keyPair.private)
}
override fun update(data: ByteArray?): ByteArray? {
if (data != null) signature.update(data)
return null
}
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray {
if (data != null) update(data)
return this.signature.sign()
}
override fun abort() {}
}
// Concrete implementation for Verification.
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
private val signature: Signature =
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
initVerify(keyPair.public)
}
override fun update(data: ByteArray?): ByteArray? {
if (data != null) signature.update(data)
return null
}
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data != null) update(data)
if (signature == null) throw SignatureException("Signature to verify is null")
if (!this.signature.verify(signature)) {
// Throwing an exception is how Keystore signals verification failure.
throw SignatureException("Signature verification failed")
}
// A successful verification returns no data.
return null
}
override fun abort() {}
}
// Concrete implementation for Encryption/Decryption.
private class CipherPrimitive(
keyPair: KeyPair,
params: KeyMintAttestation,
private val opMode: Int,
) : CryptoPrimitive {
private val cipher: Cipher =
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
init(opMode, key)
}
override fun update(data: ByteArray?): ByteArray? =
if (data != null) cipher.update(data) else null
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
if (data != null) cipher.doFinal(data) else cipher.doFinal()
override fun abort() {}
}
/**
* A software-only implementation of a cryptographic operation. This class acts as a controller,
* delegating to a specific cryptographic primitive based on the operation's purpose.
*/
class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) {
// This now holds the specific strategy object (Signer, Verifier, etc.)
private val primitive: CryptoPrimitive
init {
// The "Strategy" pattern: choose the implementation based on the purpose.
// For simplicity, we only consider the first purpose listed.
val purpose = params.purpose.firstOrNull()
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.")
primitive =
when (purpose) {
KeyPurpose.SIGN -> Signer(keyPair, params)
KeyPurpose.VERIFY -> Verifier(keyPair, params)
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
else ->
throw UnsupportedOperationException("Unsupported operation purpose: $purpose")
}
}
fun update(data: ByteArray?): ByteArray? {
try {
return primitive.update(data)
} catch (e: Exception) {
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
throw e
}
}
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
try {
val result = primitive.finish(data, signature)
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
return result
} catch (e: Exception) {
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
// Re-throw the exception so the binder can report it to the client.
throw e
}
}
fun abort() {
primitive.abort()
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
}
}
/** The Binder interface for our [SoftwareOperation]. */
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
IKeystoreOperation.Stub() {
@Throws(RemoteException::class)
override fun update(input: ByteArray?): ByteArray? {
return operation.update(input)
}
@Throws(RemoteException::class)
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
return operation.finish(input, signature)
}
@Throws(RemoteException::class)
override fun abort() {
operation.abort()
}
}
@@ -1,11 +1,6 @@
package org.matrix.TEESimulator.logging package org.matrix.TEESimulator.logging
import android.hardware.security.keymint.Algorithm import android.hardware.security.keymint.*
import android.hardware.security.keymint.Digest
import android.hardware.security.keymint.EcCurve
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.Tag
import java.math.BigInteger import java.math.BigInteger
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.Date import java.util.Date
@@ -34,7 +29,23 @@ object KeyMintParameterLogger {
.associate { field -> (field.get(null) as Int) to field.name } .associate { field -> (field.get(null) as Int) to field.name }
} }
private val purposeNames: Map<Int, String> by lazy { val blockModeNames: Map<Int, String> by lazy {
BlockMode::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val paddingNames: Map<Int, String> by lazy {
PaddingMode::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val purposeNames: Map<Int, String> by lazy {
KeyPurpose::class KeyPurpose::class
.java .java
.fields .fields
@@ -69,7 +80,9 @@ object KeyMintParameterLogger {
val formattedValue: String = val formattedValue: String =
when (param.tag) { when (param.tag) {
Tag.ALGORITHM -> algorithmNames[value.algorithm] Tag.ALGORITHM -> algorithmNames[value.algorithm]
Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
Tag.EC_CURVE -> ecCurveNames[value.ecCurve] Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
Tag.PADDING -> paddingNames[value.paddingMode]
Tag.PURPOSE -> purposeNames[value.keyPurpose] Tag.PURPOSE -> purposeNames[value.keyPurpose]
Tag.DIGEST -> digestNames[value.digest] Tag.DIGEST -> digestNames[value.digest]
Tag.AUTH_TIMEOUT, Tag.AUTH_TIMEOUT,
@@ -0,0 +1,8 @@
package android.hardware.security.keymint;
public @interface BlockMode {
public static final int ECB = 1;
public static final int CBC = 2;
public static final int CTR = 3;
public static final int GCM = 32;
}
@@ -0,0 +1,10 @@
package android.hardware.security.keymint;
public @interface PaddingMode {
public static final int NONE = 1;
public static final int RSA_OAEP = 2;
public static final int RSA_PSS = 3;
public static final int RSA_PKCS1_1_5_ENCRYPT = 4;
public static final int RSA_PKCS1_1_5_SIGN = 5;
public static final int PKCS7 = 64;
}
@@ -0,0 +1,38 @@
package android.system.keystore2;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class CreateOperationResponse implements Parcelable {
public IKeystoreOperation iOperation;
public OperationChallenge operationChallenge;
public KeyParameters parameters;
public byte[] upgradedBlob;
public static final Creator<CreateOperationResponse> CREATOR = new Creator<CreateOperationResponse>() {
@Override
public CreateOperationResponse createFromParcel(Parcel in) {
throw new UnsupportedOperationException("STUB!");
}
@Override
public CreateOperationResponse[] newArray(int size) {
throw new UnsupportedOperationException("STUB!");
}
};
@Override
public int describeContents() {
throw new UnsupportedOperationException("STUB!");
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
throw new UnsupportedOperationException("STUB!");
}
}
@@ -0,0 +1,9 @@
package android.system.keystore2;
public @interface Domain {
public static final int APP = 0;
public static final int GRANT = 1;
public static final int SELINUX = 2;
public static final int BLOB = 3;
public static final int KEY_ID = 4;
}
@@ -0,0 +1,33 @@
package android.system.keystore2;
import android.os.IBinder;
import android.os.Binder;
import android.os.IInterface;
public interface IKeystoreOperation extends IInterface {
public static final java.lang.String DESCRIPTOR = "android.system.keystore2.IKeystoreOperation";
public void updateAad(byte[] aadInput);
public byte[] update(byte[] input);
public byte[] finish(byte[] input, byte[] signature);
public void abort() throws android.os.RemoteException;
abstract class Stub extends Binder implements IKeystoreOperation {
public static IKeystoreOperation asInterface(IBinder b) {
throw new UnsupportedOperationException("STUB!");
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public void updateAad(byte[] aadInput) {
throw new UnsupportedOperationException("STUB!");
}
}
}
@@ -0,0 +1,34 @@
package android.system.keystore2;
import android.os.Parcel;
import android.os.Parcelable;
import android.hardware.security.keymint.KeyParameter;
import androidx.annotation.NonNull;
public class KeyParameters implements Parcelable {
public KeyParameter[] keyParameter;
public static final Creator<KeyParameters> CREATOR = new Creator<KeyParameters>() {
@Override
public KeyParameters createFromParcel(Parcel in) {
throw new UnsupportedOperationException("STUB!");
}
@Override
public KeyParameters[] newArray(int size) {
throw new UnsupportedOperationException("STUB!");
}
};
@Override
public int describeContents() {
throw new UnsupportedOperationException("STUB!");
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
throw new UnsupportedOperationException("STUB!");
}
}
@@ -0,0 +1,32 @@
package android.system.keystore2;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class OperationChallenge implements Parcelable {
public long challenge = 0L;
public static final Creator<OperationChallenge> CREATOR = new Creator<OperationChallenge>() {
@Override
public OperationChallenge createFromParcel(Parcel in) {
throw new UnsupportedOperationException("STUB!");
}
@Override
public OperationChallenge[] newArray(int size) {
throw new UnsupportedOperationException("STUB!");
}
};
@Override
public int describeContents() {
throw new UnsupportedOperationException("STUB!");
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
throw new UnsupportedOperationException("STUB!");
}
}