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 a1186b7..64145d6 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,22 +1,23 @@ 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.GeneratedKeyPersistence 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 /** @@ -102,10 +103,14 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { data: Parcel, ): TransactionResult { if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) { - logTransaction(txId, transactionNames[code]!!, callingUid, callingPid) + logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true) - if (ConfigurationManager.shouldSkipUid(callingUid)) + val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString() + val isGMS = packages.contains("com.google.android.gms") + + if (isGMS || ConfigurationManager.shouldSkipUid(callingUid)) { return TransactionResult.ContinueAndSkipPost + } return runCatching { val isBatchMode = code == LIST_ENTRIES_BATCHED_TRANSACTION @@ -140,7 +145,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) { @@ -210,78 +222,124 @@ 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 + logTransaction( + txId, + "post-${transactionNames[code]!!} ${keyDescriptor.alias}", + callingUid, + callingPid, + ) + 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) { + runCatching { + val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!! val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) - val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) - if (retainedChain == null) { - SystemLogger.info("[TX_ID: $txId] Skip patching for imported key (no prior attestation).") + + val authorizations = response.metadata.authorizations + val parsedParameters = + KeyMintAttestation( + authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray() + ) + + if (parsedParameters.isImportKey()) { + val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) + if (retainedChain == null) { + SystemLogger.info("[TX_ID: $txId] Skip patching for imported key (no prior attestation).") + return TransactionResult.SkipTransaction + } + SystemLogger.info("[TX_ID: $txId] Imported key overwrote attested alias, serving retained chain for $keyId") + CertificateHelper.updateCertificateChain(response.metadata, retainedChain).getOrThrow() + return InterceptorUtils.createTypedObjectReply(response) + } + + if (parsedParameters.isAttestKey()) { + SystemLogger.warning( + "[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply." + ) + 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) + + GeneratedKeyPersistence.save( + keyId = keyId, + keyPair = keyData.first, + nspace = keyDescriptor.nspace, + securityLevel = response.metadata.keySecurityLevel, + certChain = keyData.second, + algorithm = parsedParameters.algorithm, + keySize = parsedParameters.keySize, + ecCurve = parsedParameters.ecCurve, + purposes = parsedParameters.purpose, + digests = parsedParameters.digest, + isAttestationKey = true, + ) + + return InterceptorUtils.createTypedObjectReply(response) + } + + val originalChain = CertificateHelper.getCertificateChain(response) + + if (originalChain == null || originalChain.size < 2) { + SystemLogger.info( + "[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}." + ) return TransactionResult.SkipTransaction } - SystemLogger.info("[TX_ID: $txId] Imported key overwrote attested alias, serving retained chain for $keyId") - CertificateHelper.updateCertificateChain(response.metadata, retainedChain).getOrThrow() + + 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 { + 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) } - - if (originalChain == null || originalChain.size < 2) { - SystemLogger.info( - "[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}." + .onFailure { + SystemLogger.error( + "[TX_ID: $txId] Failed to modify hardware KeyEntryResponse.", + it, ) return TransactionResult.SkipTransaction } - - // 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." - ) - finalChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) - } - - 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 }