Clean up cached keys on successful import (#18)

Generated and attestation keys are cached, and if a key is imported with the same name, the cached key would be returned instead of the newly imported one.

This change invalidates the cached key when a key is successfully imported with the same alias.
Close #17 as fixed.

The logging has also been improved to be more consistent across the different interceptors.
This commit is contained in:
JingMatrix
2025-11-27 15:43:56 +01:00
committed by GitHub
parent ee02216534
commit 3351a1c932
2 changed files with 58 additions and 28 deletions
@@ -89,24 +89,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
data: Parcel, data: Parcel,
): TransactionResult { ): TransactionResult {
if (code == GET_KEY_ENTRY_TRANSACTION || code == DELETE_KEY_TRANSACTION) { if (code == GET_KEY_ENTRY_TRANSACTION || code == DELETE_KEY_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
data.enforceInterface(IKeystoreService.DESCRIPTOR) data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor = val descriptor =
data.readTypedObject(KeyDescriptor.CREATOR) data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
logTransaction(
txId,
"${transactionNames[code]} (alias=${descriptor.alias})",
callingUid,
callingPid,
)
if (ConfigurationManager.shouldSkipUid(callingUid)) { if (ConfigurationManager.shouldSkipUid(callingUid))
SystemLogger.debug(
"[TX_ID: $txId] Skip post-transaction hook for UID=${callingUid}"
)
return TransactionResult.ContinueAndSkipPost return TransactionResult.ContinueAndSkipPost
}
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
val keyId = KeyIdentifier(callingUid, descriptor.alias) val keyId = KeyIdentifier(callingUid, descriptor.alias)
if (code == DELETE_KEY_TRANSACTION) { if (code == DELETE_KEY_TRANSACTION) {
@@ -119,7 +112,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
?: return TransactionResult.Continue ?: return TransactionResult.Continue
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId)) if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
SystemLogger.debug("${descriptor.alias} was an attestation key") SystemLogger.info("${descriptor.alias} was an attestation key")
SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:") SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:")
response.metadata?.authorizations?.forEach { response.metadata?.authorizations?.forEach {
@@ -155,20 +148,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
return TransactionResult.SkipTransaction return TransactionResult.SkipTransaction
if (code == GET_KEY_ENTRY_TRANSACTION) { if (code == GET_KEY_ENTRY_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
data.enforceInterface(IKeystoreService.DESCRIPTOR) data.enforceInterface(IKeystoreService.DESCRIPTOR)
val keyDescriptor = val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR) data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
logTransaction(
txId,
"post-getKeyEntry (alias=${keyDescriptor.alias})",
callingUid,
callingPid,
)
if (!ConfigurationManager.shouldPatch(callingUid)) if (!ConfigurationManager.shouldPatch(callingUid))
return TransactionResult.SkipTransaction return TransactionResult.SkipTransaction
SystemLogger.info("Handling post-${transactionNames[code]!!} ${keyDescriptor.alias}")
return try { return try {
val response = val response =
reply.readTypedObject(KeyEntryResponse.CREATOR) reply.readTypedObject(KeyEntryResponse.CREATOR)
@@ -40,11 +40,20 @@ class KeyMintSecurityLevelInterceptor(
callingPid: Int, callingPid: Int,
data: Parcel, data: Parcel,
): TransactionResult { ): TransactionResult {
// This interceptor only handles the 'generateKey' transaction directly.
if (code == GENERATE_KEY_TRANSACTION) { if (code == GENERATE_KEY_TRANSACTION) {
logTransaction(txId, "generateKey", callingUid, callingPid) logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
return handleGenerateKey(callingUid, data) return handleGenerateKey(callingUid, data)
} else if (code == IMPORT_KEY_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val alias =
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
?: return TransactionResult.ContinueAndSkipPost
SystemLogger.info("Handling post-${transactionNames[code]} ${alias}")
return TransactionResult.Continue
} else { } else {
logTransaction( logTransaction(
txId, txId,
@@ -57,6 +66,35 @@ class KeyMintSecurityLevelInterceptor(
return TransactionResult.ContinueAndSkipPost return TransactionResult.ContinueAndSkipPost
} }
override fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult {
// We only care about successful 'importKey' transactions to clean cached keys.
if (
code == IMPORT_KEY_TRANSACTION &&
resultCode == 0 &&
reply != null &&
!InterceptorUtils.hasException(reply)
) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction
cleanupKeyData(KeyIdentifier(callingUid, keyDescriptor.alias))
}
return TransactionResult.SkipTransaction
}
/** /**
* Handles the `generateKey` transaction. Based on the configuration for the calling UID, it * Handles the `generateKey` transaction. Based on the configuration for the calling UID, it
* either generates a key in software or lets the call pass through to the hardware. * either generates a key in software or lets the call pass through to the hardware.
@@ -66,7 +104,7 @@ class KeyMintSecurityLevelInterceptor(
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR) val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
SystemLogger.debug( SystemLogger.debug(
"[key, attestationKey]: ${keyDescriptor.alias}, ${attestationKey?.alias}" "Handling generateKey ${keyDescriptor.alias}, attestKey=${attestationKey?.alias}"
) )
val params = data.createTypedArray(KeyParameter.CREATOR)!! val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params) val parsedParams = KeyMintAttestation(params)
@@ -84,9 +122,7 @@ class KeyMintSecurityLevelInterceptor(
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias))) isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
if (needsSoftwareGeneration) { if (needsSoftwareGeneration) {
SystemLogger.info( SystemLogger.info("Generating software key for ${keyId}.")
"Generating software key for alias '${keyDescriptor.alias}' (UID: $callingUid)."
)
// Generate the key pair and certificate chain. // Generate the key pair and certificate chain.
val keyData = val keyData =
@@ -116,11 +152,11 @@ class KeyMintSecurityLevelInterceptor(
// If not generating, clear any stale state for this alias and let the call proceed. // If not generating, clear any stale state for this alias and let the call proceed.
cleanupKeyData(keyId) cleanupKeyData(keyId)
TransactionResult.Continue TransactionResult.ContinueAndSkipPost
} }
.getOrElse { .getOrElse {
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it) SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
TransactionResult.Continue // Fallback to original service on error. TransactionResult.ContinueAndSkipPost
} }
} }
@@ -175,8 +211,12 @@ class KeyMintSecurityLevelInterceptor(
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId) fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
fun cleanupKeyData(keyId: KeyIdentifier) { fun cleanupKeyData(keyId: KeyIdentifier) {
generatedKeys.remove(keyId) if (generatedKeys.remove(keyId) != null) {
attestationKeys.remove(keyId) SystemLogger.debug("Remove generated key ${keyId}")
}
if (attestationKeys.remove(keyId)) {
SystemLogger.debug("Remove cached attestaion key ${keyId}")
}
} }
} }
} }