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.
This commit is contained in:
Enginex0
2026-05-19 18:01:06 +01:00
parent 6d11362034
commit 182450d3b4
@@ -211,14 +211,26 @@ class KeyMintSecurityLevelInterceptor(
val metadata: KeyMetadata = val metadata: KeyMetadata =
reply.readTypedObject(KeyMetadata.CREATOR) reply.readTypedObject(KeyMetadata.CREATOR)
?: return TransactionResult.SkipTransaction ?: 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) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
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)
teeResponses[keyId] = KeyEntryResponse().apply {
this.metadata = metadata
iSecurityLevel = original
}
return TransactionResult.SkipTransaction
}
data.readTypedObject(KeyDescriptor.CREATOR) // skip attestationKey data.readTypedObject(KeyDescriptor.CREATOR) // skip attestationKey
val keyParams = data.createTypedArray(KeyParameter.CREATOR) val keyParams = data.createTypedArray(KeyParameter.CREATOR)
val certNotBefore = keyParams?.find { it.tag == Tag.CERTIFICATE_NOT_BEFORE }?.value?.dateTime?.let { Date(it) } val certNotBefore = keyParams?.find { it.tag == Tag.CERTIFICATE_NOT_BEFORE }?.value?.dateTime?.let { Date(it) }
@@ -226,15 +238,12 @@ class KeyMintSecurityLevelInterceptor(
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid, certNotBefore, certNotAfter) val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid, certNotBefore, certNotAfter)
// Cache the newly patched chain to ensure consistency across subsequent API calls.
val key = metadata.key val key = metadata.key
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
metadata.authorizations = metadata.authorizations =
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid) InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
// We must clean up cached generated keys before storing the patched chain
cleanupKeyData(keyId) cleanupKeyData(keyId)
patchedChains[keyId] = newChain patchedChains[keyId] = newChain
teeResponses[keyId] = KeyEntryResponse().apply { teeResponses[keyId] = KeyEntryResponse().apply {
@@ -247,7 +256,6 @@ class KeyMintSecurityLevelInterceptor(
return InterceptorUtils.createTypedObjectReply(metadata) return InterceptorUtils.createTypedObjectReply(metadata)
} }
}
return TransactionResult.SkipTransaction return TransactionResult.SkipTransaction
} }
@@ -511,7 +519,7 @@ class KeyMintSecurityLevelInterceptor(
parsedParams.attestationChallenge != null -> TransactionResult.Continue parsedParams.attestationChallenge != null -> TransactionResult.Continue
else -> { else -> {
cleanupKeyData(keyId) cleanupKeyData(keyId)
TransactionResult.ContinueAndSkipPost TransactionResult.Continue
} }
} }
} }