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:
+37
-29
@@ -211,42 +211,50 @@ 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)
|
|
||||||
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 originalChain = CertificateHelper.getCertificateChain(metadata)
|
||||||
val key = metadata.key
|
if (originalChain == null || originalChain.size <= 1) {
|
||||||
?: return TransactionResult.SkipTransaction
|
// Cache non-attested responses for KEY_ID getKeyEntry parity.
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
// Without this, the cached attested path returns in ~1ms while
|
||||||
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
// the forwarded non-attested path takes ~1.5ms, and
|
||||||
metadata.authorizations =
|
// TimingSideChannelProbe flags the 1.55x ratio.
|
||||||
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
|
|
||||||
|
|
||||||
// We must clean up cached generated keys before storing the patched chain
|
|
||||||
cleanupKeyData(keyId)
|
cleanupKeyData(keyId)
|
||||||
patchedChains[keyId] = newChain
|
|
||||||
teeResponses[keyId] = KeyEntryResponse().apply {
|
teeResponses[keyId] = KeyEntryResponse().apply {
|
||||||
this.metadata = metadata
|
this.metadata = metadata
|
||||||
iSecurityLevel = original
|
iSecurityLevel = original
|
||||||
}
|
}
|
||||||
SystemLogger.debug(
|
return TransactionResult.SkipTransaction
|
||||||
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
|
|
||||||
)
|
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(metadata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user