fix(interception): restore key lifecycle tracking and cache invalidation
Restores pre-PR157 custom features: - deletedSoftwareKeys tracking in Keystore2Interceptor (returns KEY_NOT_FOUND for getKeyEntry after software key deletion instead of falling through to hardware) - invalidatePatchedChains() for bulk cert chain cache clearing - Per-UID LRU operation pruning (MAX_CONCURRENT_OPS_PER_UID=15) prevents resource exhaustion from concurrent software operations - SoftwareOperation.isFinalized made public for LRU eviction checks
This commit is contained in:
+6
-1
@@ -61,8 +61,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys whose certs were updated via updateSubcomponent; skip re-patching on getKeyEntry.
|
|
||||||
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
private val deletedSoftwareKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
|
||||||
// Backdoor binder for registering new interceptors at runtime.
|
// Backdoor binder for registering new interceptors at runtime.
|
||||||
private var backdoorBinder: IBinder? = null
|
private var backdoorBinder: IBinder? = null
|
||||||
@@ -225,6 +225,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId)
|
KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId)
|
||||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
||||||
if (isSoftwareKey) {
|
if (isSoftwareKey) {
|
||||||
|
deletedSoftwareKeys.add(keyId)
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response."
|
"[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response."
|
||||||
)
|
)
|
||||||
@@ -239,6 +240,10 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||||
|
|
||||||
|
if (deletedSoftwareKeys.remove(keyId)) {
|
||||||
|
return InterceptorUtils.createErrorReply(7) // KEY_NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
val response =
|
val response =
|
||||||
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
||||||
?: return TransactionResult.Continue
|
?: return TransactionResult.Continue
|
||||||
|
|||||||
+21
-7
@@ -51,8 +51,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
private val securityLevel: Int,
|
private val securityLevel: Int,
|
||||||
) : BinderInterceptor() {
|
) : BinderInterceptor() {
|
||||||
|
|
||||||
// --- Data Structures for State Management ---
|
|
||||||
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||||
|
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||||
|
|
||||||
data class GeneratedKeyInfo(
|
data class GeneratedKeyInfo(
|
||||||
val keyPair: KeyPair?,
|
val keyPair: KeyPair?,
|
||||||
@@ -253,11 +253,17 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
timestamps.addLast(System.nanoTime())
|
timestamps.addLast(System.nanoTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun pruneOpsForUid(callingUid: Int, newOp: SoftwareOperation) {
|
||||||
* Handles the `createOperation` transaction. It checks if the operation is for a key that was
|
val ops = activeOps.computeIfAbsent(callingUid) { ConcurrentLinkedDeque() }
|
||||||
* generated in software. If so, it creates a software-based operation handler. Otherwise, it
|
ops.removeIf { it.isFinalized }
|
||||||
* lets the call proceed to the real hardware service.
|
while (ops.size >= MAX_CONCURRENT_OPS_PER_UID) {
|
||||||
*/
|
val oldest = ops.pollFirst() ?: break
|
||||||
|
oldest.abort()
|
||||||
|
SystemLogger.debug("Pruned oldest op for uid=$callingUid (LRU eviction)")
|
||||||
|
}
|
||||||
|
ops.addLast(newOp)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCreateOperation(
|
private fun handleCreateOperation(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
@@ -402,6 +408,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
effectiveParams,
|
effectiveParams,
|
||||||
opLatency,
|
opLatency,
|
||||||
)
|
)
|
||||||
|
pruneOpsForUid(callingUid, softwareOperation)
|
||||||
|
|
||||||
// Decrement usage counter on finish; delete key when exhausted.
|
// Decrement usage counter on finish; delete key when exhausted.
|
||||||
if (keyParams.usageCountLimit != null && resolvedKeyId != null) {
|
if (keyParams.usageCountLimit != null && resolvedKeyId != null) {
|
||||||
@@ -956,6 +963,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
|
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
|
||||||
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L
|
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L
|
||||||
private const val MAX_ALIAS_LENGTH = 256 * 1024
|
private const val MAX_ALIAS_LENGTH = 256 * 1024
|
||||||
|
private const val MAX_CONCURRENT_OPS_PER_UID = 15
|
||||||
|
|
||||||
private fun isStrongBoxCapable(params: KeyMintAttestation): Boolean = when (params.algorithm) {
|
private fun isStrongBoxCapable(params: KeyMintAttestation): Boolean = when (params.algorithm) {
|
||||||
Algorithm.RSA -> params.keySize <= 2048
|
Algorithm.RSA -> params.keySize <= 2048
|
||||||
@@ -1056,7 +1064,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clears all cached keys.
|
fun invalidatePatchedChains(reason: String? = null) {
|
||||||
|
val count = patchedChains.size
|
||||||
|
if (count == 0) return
|
||||||
|
patchedChains.clear()
|
||||||
|
SystemLogger.info("Invalidated $count patched cert chains${reason?.let { " due to $it" } ?: ""}.")
|
||||||
|
}
|
||||||
|
|
||||||
fun clearAllGeneratedKeys(reason: String? = null) {
|
fun clearAllGeneratedKeys(reason: String? = null) {
|
||||||
val count = generatedKeys.size
|
val count = generatedKeys.size
|
||||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
|
|||||||
+8
-7
@@ -257,7 +257,8 @@ class SoftwareOperation(
|
|||||||
) {
|
) {
|
||||||
private val primitive: CryptoPrimitive
|
private val primitive: CryptoPrimitive
|
||||||
|
|
||||||
@Volatile private var finalized = false
|
@Volatile var isFinalized = false
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val purpose = params.purpose.firstOrNull()
|
val purpose = params.purpose.firstOrNull()
|
||||||
@@ -294,7 +295,7 @@ class SoftwareOperation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkActive() {
|
private fun checkActive() {
|
||||||
if (finalized)
|
if (isFinalized)
|
||||||
throw ServiceSpecificException(
|
throw ServiceSpecificException(
|
||||||
KeystoreErrorCode.INVALID_OPERATION_HANDLE,
|
KeystoreErrorCode.INVALID_OPERATION_HANDLE,
|
||||||
"Operation already finalized.",
|
"Operation already finalized.",
|
||||||
@@ -306,10 +307,10 @@ class SoftwareOperation(
|
|||||||
try {
|
try {
|
||||||
primitive.updateAad(data)
|
primitive.updateAad(data)
|
||||||
} catch (e: ServiceSpecificException) {
|
} catch (e: ServiceSpecificException) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to updateAad.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to updateAad.", e)
|
||||||
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
|
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
|
||||||
}
|
}
|
||||||
@@ -320,10 +321,10 @@ class SoftwareOperation(
|
|||||||
try {
|
try {
|
||||||
return primitive.update(data)
|
return primitive.update(data)
|
||||||
} catch (e: ServiceSpecificException) {
|
} catch (e: ServiceSpecificException) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
||||||
throw mapToServiceSpecificException(e)
|
throw mapToServiceSpecificException(e)
|
||||||
}
|
}
|
||||||
@@ -348,7 +349,7 @@ class SoftwareOperation(
|
|||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
||||||
throw mapToServiceSpecificException(e)
|
throw mapToServiceSpecificException(e)
|
||||||
} finally {
|
} finally {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user