diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt index bcad31e..1fb5f0f 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt @@ -1,21 +1,22 @@ package org.matrix.TEESimulator.interception.keystore import android.annotation.SuppressLint -import android.hardware.security.keymint.KeyOrigin import android.hardware.security.keymint.SecurityLevel -import android.hardware.security.keymint.Tag import android.os.Build import android.os.IBinder import android.os.Parcel import android.system.keystore2.IKeystoreService import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyEntryResponse +import java.security.SecureRandom import java.security.cert.Certificate import org.matrix.TEESimulator.attestation.AttestationPatcher +import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor import org.matrix.TEESimulator.logging.KeyMintParameterLogger import org.matrix.TEESimulator.logging.SystemLogger +import org.matrix.TEESimulator.pki.CertificateGenerator import org.matrix.TEESimulator.pki.CertificateHelper /** @@ -127,7 +128,14 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { data.readTypedObject(KeyDescriptor.CREATOR) ?: return TransactionResult.ContinueAndSkipPost - SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}") + if (descriptor.alias != null) { + SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}") + } else { + SystemLogger.info( + "Skip ${transactionNames[code]!!} for key [alias, blob, domain, nspace]: [${descriptor.alias}, ${descriptor.blob}, ${descriptor.domain}, ${descriptor.nspace}]" + ) + return TransactionResult.ContinueAndSkipPost + } val keyId = KeyIdentifier(callingUid, descriptor.alias) if (code == DELETE_KEY_TRANSACTION) { @@ -201,71 +209,106 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { TransactionResult.SkipTransaction } } else if (code == GET_KEY_ENTRY_TRANSACTION) { - logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) - data.enforceInterface(IKeystoreService.DESCRIPTOR) val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return TransactionResult.SkipTransaction - if (!ConfigurationManager.shouldPatch(callingUid)) - return TransactionResult.SkipTransaction - - SystemLogger.info("Handling post-${transactionNames[code]!!} ${keyDescriptor.alias}") - return try { - val response = - reply.readTypedObject(KeyEntryResponse.CREATOR) - ?: return TransactionResult.SkipTransaction - reply.setDataPosition(0) // Reset for potential reuse. - - val originalChain = CertificateHelper.getCertificateChain(response) - val authorizations = response.metadata?.authorizations - val origin = - authorizations - ?.find { it.keyParameter.tag == Tag.ORIGIN } - ?.let { it.keyParameter.value.origin } - - if (origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED) { - SystemLogger.info("[TX_ID: $txId] Skip patching for imported keys.") - return TransactionResult.SkipTransaction - } + logTransaction( + txId, + "post-${transactionNames[code]!!} ${keyDescriptor.alias}", + callingUid, + callingPid, + ) - if (originalChain == null || originalChain.size < 2) { - SystemLogger.info( - "[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}." - ) - return TransactionResult.SkipTransaction + runCatching { + val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!! + val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) + + val authorizations = response.metadata.authorizations + val parsedParameters = + KeyMintAttestation( + authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray() + ) + + if (parsedParameters.isImportKey()) { + SystemLogger.info("[TX_ID: $txId] Skip patching for imported keys.") + return TransactionResult.SkipTransaction + } + + if (parsedParameters.isAttestKey()) { + SystemLogger.warning( + "[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply." + ) + // Attest keys that are not under our control should be overriden. + val keyData = + CertificateGenerator.generateAttestedKeyPair( + callingUid, + keyId.alias, + null, + parsedParameters, + response.metadata.keySecurityLevel, + ) ?: throw Exception("Failed to create overriding attest key pair.") + + CertificateHelper.updateCertificateChain( + response.metadata, + keyData.second.toTypedArray(), + ) + .getOrThrow() + + keyDescriptor.nspace = SecureRandom().nextLong() + KeyMintSecurityLevelInterceptor.generatedKeys[keyId] = + KeyMintSecurityLevelInterceptor.GeneratedKeyInfo( + keyData.first, + keyDescriptor.nspace, + response, + ) + KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId) + return InterceptorUtils.createTypedObjectReply(response) + } + + val originalChain = CertificateHelper.getCertificateChain(response) + + // Check if we should perform attestation patch. + if (originalChain == null || originalChain.size < 2) { + SystemLogger.info( + "[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}." + ) + return TransactionResult.SkipTransaction + } + + // First, try to retrieve the already-patched chain from our cache to ensure + // consistency. + val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) + + val finalChain: Array + if (cachedChain != null) { + SystemLogger.debug( + "[TX_ID: $txId] Using cached patched certificate chain for $keyId." + ) + finalChain = cachedChain + } else { + // If no chain is cached (e.g., key existed before simulator started), + // perform a live patch as a fallback. This may still be detectable. + SystemLogger.info( + "[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback." + ) + finalChain = + AttestationPatcher.patchCertificateChain(originalChain, callingUid) + } + + CertificateHelper.updateCertificateChain(response.metadata, finalChain) + .getOrThrow() + + return InterceptorUtils.createTypedObjectReply(response) } - - // Perform the attestation patch. - val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) - - // First, try to retrieve the already-patched chain from our cache to ensure - // consistency. - val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) - - val finalChain: Array - if (cachedChain != null) { - SystemLogger.debug( - "[TX_ID: $txId] Using cached patched certificate chain for $keyId." - ) - finalChain = cachedChain - } else { - // If no chain is cached (e.g., key existed before simulator started), - // perform a live patch as a fallback. This may still be detectable. - SystemLogger.info( - "[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback." + .onFailure { + SystemLogger.error( + "[TX_ID: $txId] Failed to modify hardware KeyEntryResponse.", + it, ) - finalChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) + return TransactionResult.SkipTransaction } - - CertificateHelper.updateCertificateChain(response.metadata, finalChain).getOrThrow() - - InterceptorUtils.createTypedObjectReply(response) - } catch (e: Exception) { - SystemLogger.error("[TX_ID: $txId] Failed to patch certificate chain.", e) - TransactionResult.SkipTransaction - } } return TransactionResult.SkipTransaction }