From fb7f0ca098347e7abd0952ff14b4514fca0d6737 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Tue, 19 May 2026 18:01:06 +0100 Subject: [PATCH] fix(intercept): cache non-attested keys for parity After PR #22 and the AUTO-mode extension started caching attested generateKey responses in teeResponses, KEY_ID getKeyEntry lookups for attested keys returned from memory in ~1ms while non-attested keys forwarded to real keystore2 took ~1.5ms. TimingSideChannelProbe measured the 1.55x ratio against its 1.1x threshold and flagged the asymmetry. Forward non-attested generateKey to real keystore2 with post-hook enabled (Continue instead of ContinueAndSkipPost), and extend the GENERATE_KEY post-hook to cache no-chain responses into teeResponses. The KEY_ID lookup added in the previous commit now resolves both paths from memory at matched latency. Cert-chain patching is skipped for the no-chain branch because there is no attestation extension to rewrite. --- .../shim/KeyMintSecurityLevelInterceptor.kt | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) 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 a41a8f4..7e84eea 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 @@ -211,42 +211,50 @@ class KeyMintSecurityLevelInterceptor( val metadata: KeyMetadata = reply.readTypedObject(KeyMetadata.CREATOR) ?: return TransactionResult.SkipTransaction - val originalChain = - CertificateHelper.getCertificateChain(metadata) - ?: return TransactionResult.SkipTransaction - if (originalChain.size > 1) { - // Read the request parcel to extract keyDescriptor and cert date params. - data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) - val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) - ?: return TransactionResult.SkipTransaction - data.readTypedObject(KeyDescriptor.CREATOR) // skip attestationKey - val keyParams = data.createTypedArray(KeyParameter.CREATOR) - val certNotBefore = keyParams?.find { it.tag == Tag.CERTIFICATE_NOT_BEFORE }?.value?.dateTime?.let { Date(it) } - val certNotAfter = keyParams?.find { it.tag == Tag.CERTIFICATE_NOT_AFTER }?.value?.dateTime?.let { Date(it) } - val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid, certNotBefore, certNotAfter) + data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) + val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) + ?: return TransactionResult.SkipTransaction + val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) - // Cache the newly patched chain to ensure consistency across subsequent API calls. - val key = metadata.key - ?: return TransactionResult.SkipTransaction - val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) - CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() - metadata.authorizations = - InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid) - - // We must clean up cached generated keys before storing the patched chain + val originalChain = CertificateHelper.getCertificateChain(metadata) + if (originalChain == null || originalChain.size <= 1) { + // Cache non-attested responses for KEY_ID getKeyEntry parity. + // Without this, the cached attested path returns in ~1ms while + // the forwarded non-attested path takes ~1.5ms, and + // TimingSideChannelProbe flags the 1.55x ratio. cleanupKeyData(keyId) - patchedChains[keyId] = newChain teeResponses[keyId] = KeyEntryResponse().apply { this.metadata = metadata iSecurityLevel = original } - SystemLogger.debug( - "Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])" - ) - - return InterceptorUtils.createTypedObjectReply(metadata) + return TransactionResult.SkipTransaction } + + data.readTypedObject(KeyDescriptor.CREATOR) // skip attestationKey + val keyParams = data.createTypedArray(KeyParameter.CREATOR) + val certNotBefore = keyParams?.find { it.tag == Tag.CERTIFICATE_NOT_BEFORE }?.value?.dateTime?.let { Date(it) } + val certNotAfter = keyParams?.find { it.tag == Tag.CERTIFICATE_NOT_AFTER }?.value?.dateTime?.let { Date(it) } + + val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid, certNotBefore, certNotAfter) + + val key = metadata.key + ?: return TransactionResult.SkipTransaction + CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() + metadata.authorizations = + InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid) + + cleanupKeyData(keyId) + patchedChains[keyId] = newChain + teeResponses[keyId] = KeyEntryResponse().apply { + this.metadata = metadata + iSecurityLevel = original + } + SystemLogger.debug( + "Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])" + ) + + return InterceptorUtils.createTypedObjectReply(metadata) } return TransactionResult.SkipTransaction } @@ -511,7 +519,7 @@ class KeyMintSecurityLevelInterceptor( parsedParams.attestationChallenge != null -> TransactionResult.Continue else -> { cleanupKeyData(keyId) - TransactionResult.ContinueAndSkipPost + TransactionResult.Continue } } }