Intercept updateSubcomponent to fix software key state inconsistency (#82)

Apps attempting to update the certificate chain of a simulated software-based key (e.g., via KeyStore.setKeyEntry) currently trigger a KEY_NOT_FOUND error. This happens because the request is passed to the hardware Keystore daemon, which has no knowledge of keys existing only in the simulator's memory.

To fix detecting points exploiting this inconsistency, we intercept the UPDATE_SUBCOMPONENT_TRANSACTION. If the target is a recognized virtual key, the simulator now:
1. Updates the in-memory certificate/chain metadata.
2. Returns NO_ERROR immediately to the caller.
3. Prevents the transaction from reaching the real hardware service.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
This commit is contained in:
小潼
2026-01-23 17:53:34 +01:00
committed by GitHub
co-authored by JingMatrix
parent d77508d0c1
commit 205fda43ba
2 changed files with 37 additions and 6 deletions
@@ -31,6 +31,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
private val DELETE_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
private val UPDATE_SUBCOMPONENT_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "updateSubcomponent")
private val transactionNames: Map<Int, String> by lazy {
IKeystoreService.Stub::class
@@ -89,16 +91,23 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
callingPid: Int,
data: Parcel,
): TransactionResult {
if (code == GET_KEY_ENTRY_TRANSACTION || code == DELETE_KEY_TRANSACTION) {
if (
code == GET_KEY_ENTRY_TRANSACTION ||
code == DELETE_KEY_TRANSACTION ||
code == UPDATE_SUBCOMPONENT_TRANSACTION
) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
if (ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
if (code == UPDATE_SUBCOMPONENT_TRANSACTION)
return handleUpdateSubcomponent(callingUid, data)
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction
if (ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
?: return TransactionResult.ContinueAndSkipPost
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
val keyId = KeyIdentifier(callingUid, descriptor.alias)
@@ -223,4 +232,25 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
}
return TransactionResult.SkipTransaction
}
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val generatedKeyInfo =
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(callingUid, descriptor?.nspace)
?: return TransactionResult.ContinueAndSkipPost
SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]")
val metadata = generatedKeyInfo.response.metadata
val publicCert = data.createByteArray()
val certificateChain = data.createByteArray()
metadata.certificate = publicCert
metadata.certificateChain = certificateChain
SystemLogger.verbose(
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
}
}
@@ -349,9 +349,10 @@ class KeyMintSecurityLevelInterceptor(
* @param nspace The unique key identifier from the operation's KeyDescriptor.
* @return The matching GeneratedKeyInfo if found, otherwise null.
*/
private fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long): GeneratedKeyInfo? {
fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long?): GeneratedKeyInfo? {
// Iterate through all entries in the map to check both the key (for UID) and value (for
// nspace).
if (nspace == null || nspace == 0L) return null
return generatedKeys.entries
.filter { (keyIdentifier, _) -> keyIdentifier.uid == callingUid }
.find { (_, info) -> info.nspace == nspace }