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:
@@ -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
|
||||
@@ -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?,
|
||||
@@ -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),
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+162
-28
@@ -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
|
||||
@@ -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,
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
@@ -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 =
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
+58
@@ -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] }
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
@@ -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
|
||||
|
||||
import android.hardware.security.keymint.Algorithm
|
||||
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 android.hardware.security.keymint.*
|
||||
import java.math.BigInteger
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.Date
|
||||
@@ -34,7 +29,23 @@ object KeyMintParameterLogger {
|
||||
.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
|
||||
.java
|
||||
.fields
|
||||
@@ -69,7 +80,9 @@ object KeyMintParameterLogger {
|
||||
val formattedValue: String =
|
||||
when (param.tag) {
|
||||
Tag.ALGORITHM -> algorithmNames[value.algorithm]
|
||||
Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
|
||||
Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
|
||||
Tag.PADDING -> paddingNames[value.paddingMode]
|
||||
Tag.PURPOSE -> purposeNames[value.keyPurpose]
|
||||
Tag.DIGEST -> digestNames[value.digest]
|
||||
Tag.AUTH_TIMEOUT,
|
||||
|
||||
Reference in New Issue
Block a user