diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt index 2d776dd..b6afbbb 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt @@ -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, + val padding: List, val purpose: List, val digest: List, 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.findDate(tag: Int): Date? = private fun Array.findBlob(tag: Int): ByteArray? = this.find { it.tag == tag }?.value?.blob +/** Maps to AOSP field = BlockMode (Repeated) */ +private fun Array.findAllBlockMode(tag: Int): List = + this.filter { it.tag == tag }.map { it.value.blockMode } + +/** Maps to AOSP field = BlockMode (Repeated) */ +private fun Array.findAllPaddingMode(tag: Int): List = + this.filter { it.tag == tag }.map { it.value.paddingMode } + /** Maps to AOSP field = KeyPurpose (Repeated) */ private fun Array.findAllKeyPurpose(tag: Int): List = this.filter { it.tag == tag }.map { it.value.keyPurpose } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt index 7a1a5ff..370141b 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt @@ -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() + } + } } } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt index c6958cb..2aef665 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt @@ -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(), + padding = listOf(), purpose = this.purpose, digest = this.digest, rsaPublicExponent = this.rsaPublicExponent, diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 46ca111..df3623a 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -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 by lazy { IKeystoreSecurityLevel.Stub::class @@ -228,11 +334,30 @@ class KeyMintSecurityLevelInterceptor( private val patchedChains = ConcurrentHashMap>() // A set to quickly identify keys that were generated for attestation purposes. private val attestationKeys = ConcurrentHashMap.newKeySet() + // Stores interceptors for active cryptographic operations. + private val interceptedOperations = ConcurrentHashMap() // --- 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? = 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 diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt new file mode 100644 index 0000000..91e0dde --- /dev/null +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt @@ -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 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] } + } + } +} diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt new file mode 100644 index 0000000..79cd2c9 --- /dev/null +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt @@ -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() + } +} diff --git a/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt b/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt index 753ce42..0940cdd 100644 --- a/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt +++ b/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt @@ -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 by lazy { + val blockModeNames: Map 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 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 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, diff --git a/stub/src/main/java/android/hardware/security/keymint/BlockMode.java b/stub/src/main/java/android/hardware/security/keymint/BlockMode.java new file mode 100644 index 0000000..4481f2f --- /dev/null +++ b/stub/src/main/java/android/hardware/security/keymint/BlockMode.java @@ -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; +} diff --git a/stub/src/main/java/android/hardware/security/keymint/PaddingMode.java b/stub/src/main/java/android/hardware/security/keymint/PaddingMode.java new file mode 100644 index 0000000..021b54e --- /dev/null +++ b/stub/src/main/java/android/hardware/security/keymint/PaddingMode.java @@ -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; +} diff --git a/stub/src/main/java/android/system/keystore2/CreateOperationResponse.java b/stub/src/main/java/android/system/keystore2/CreateOperationResponse.java new file mode 100644 index 0000000..a0ff209 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/CreateOperationResponse.java @@ -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 CREATOR = new Creator() { + @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!"); + } +} diff --git a/stub/src/main/java/android/system/keystore2/Domain.java b/stub/src/main/java/android/system/keystore2/Domain.java new file mode 100644 index 0000000..8103aa3 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/Domain.java @@ -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; +} diff --git a/stub/src/main/java/android/system/keystore2/IKeystoreOperation.java b/stub/src/main/java/android/system/keystore2/IKeystoreOperation.java new file mode 100644 index 0000000..d69ffbf --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/IKeystoreOperation.java @@ -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!"); + } + } +} diff --git a/stub/src/main/java/android/system/keystore2/KeyParameters.java b/stub/src/main/java/android/system/keystore2/KeyParameters.java new file mode 100644 index 0000000..e4c4822 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/KeyParameters.java @@ -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 CREATOR = new Creator() { + @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!"); + } +} + diff --git a/stub/src/main/java/android/system/keystore2/OperationChallenge.java b/stub/src/main/java/android/system/keystore2/OperationChallenge.java new file mode 100644 index 0000000..3824f57 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/OperationChallenge.java @@ -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 CREATOR = new Creator() { + @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!"); + } +}