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:
+18
-10
@@ -211,14 +211,26 @@ 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
|
||||
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
|
||||
val keyParams = data.createTypedArray(KeyParameter.CREATOR)
|
||||
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)
|
||||
|
||||
// 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
|
||||
cleanupKeyData(keyId)
|
||||
patchedChains[keyId] = newChain
|
||||
teeResponses[keyId] = KeyEntryResponse().apply {
|
||||
@@ -247,7 +256,6 @@ class KeyMintSecurityLevelInterceptor(
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user