diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt index 3412c7e..137a353 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt @@ -124,8 +124,14 @@ object AttestationPatcher { // Sign the newly built certificate with the private key from our keybox. val signer = JcaContentSignerBuilder(sigAlgName).build(keybox.keyPair.private) + val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer)) - return JcaX509CertificateConverter().getCertificate(builder.build(signer)) + // Log the signature of the newly created certificate to observe its non-deterministic + // nature. + val signatureBytes = (newCertificate as X509Certificate).signature + SystemLogger.verbose("Signature of patched leaf cert: ${signatureBytes.toHex()}") + + return newCertificate } private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox { 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 74ecc6e..8140925 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 @@ -9,6 +9,7 @@ import android.os.Parcel import android.system.keystore2.IKeystoreService import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyEntryResponse +import java.security.cert.Certificate import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor @@ -185,8 +186,28 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { } // Perform the attestation patch. - val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) - CertificateHelper.updateCertificateChain(response.metadata, newChain).getOrThrow() + 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) { 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 10a2d87..3484649 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 @@ -101,6 +101,14 @@ class KeyMintSecurityLevelInterceptor( ?: return TransactionResult.SkipTransaction if (originalChain.size > 1) { val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) + + // Cache the newly patched chain to ensure consistency across subsequent API calls. + data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) + val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! + val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) + patchedChains[keyId] = newChain + SystemLogger.debug("Cached patched certificate chain for $keyId.") + CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() return InterceptorUtils.createTypedObjectReply(metadata) @@ -217,6 +225,8 @@ class KeyMintSecurityLevelInterceptor( // Stores keys generated entirely in software. val generatedKeys = ConcurrentHashMap() + // Caches patched certificate chains to prevent re-generation and signature inconsistencies. + private val patchedChains = ConcurrentHashMap>() // A set to quickly identify keys that were generated for attestation purposes. private val attestationKeys = ConcurrentHashMap.newKeySet() @@ -224,12 +234,17 @@ class KeyMintSecurityLevelInterceptor( fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? = generatedKeys[keyId]?.response + fun getPatchedChain(keyId: KeyIdentifier): Array? = patchedChains[keyId] + fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId) fun cleanupKeyData(keyId: KeyIdentifier) { if (generatedKeys.remove(keyId) != null) { SystemLogger.debug("Remove generated key ${keyId}") } + if (patchedChains.remove(keyId) != null) { + SystemLogger.debug("Remove patched chain for ${keyId}") + } if (attestationKeys.remove(keyId)) { SystemLogger.debug("Remove cached attestaion key ${keyId}") } @@ -240,6 +255,7 @@ class KeyMintSecurityLevelInterceptor( val count = generatedKeys.size val reasonMessage = reason?.let { " due to $it" } ?: "" generatedKeys.clear() + patchedChains.clear() attestationKeys.clear() SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.") }