feat(keystore): probe trail for generateKey

Add a debug-only structured log line per generateKey, emitted at every
outcome (SKIP, the four REJECTs, FORWARD_HAL, FORGE, PATCH, PASSTHROUGH).
Each line carries the resolving package (via the cached
ConfigurationManager.getPackagesForUid), alias, algorithm, StrongBox
flag, the attestation tag set, and the verdict, so triaging "app X
broke" becomes a single logcat grep instead of decoding parcel dumps.

Gated on SystemLogger.isDebugBuild: release builds return before any
string is built, keeping the path silent and artifact-free. Logs to
logcat only, never to files.
This commit is contained in:
Enginex0
2026-05-30 14:54:57 +01:00
parent 254fb0f0a9
commit 5bd563d8db
@@ -443,8 +443,39 @@ class KeyMintSecurityLevelInterceptor(
var parsedParams = KeyMintAttestation(params)
val isAttestKeyRequest = parsedParams.isAttestKey()
val hasDeviceIdAttestation = params.any {
it.tag == Tag.ATTESTATION_ID_IMEI ||
it.tag == Tag.ATTESTATION_ID_MEID ||
it.tag == Tag.ATTESTATION_ID_SERIAL ||
it.tag == Tag.DEVICE_UNIQUE_ATTESTATION ||
it.tag == Tag.ATTESTATION_ID_SECOND_IMEI
}
// Debug-only probe trail: one greppable line per generateKey carrying the resolving
// package and the outcome. Release builds short-circuit before any string is built,
// so this is silent and artifact-free in production.
fun logProbe(outcome: String) {
if (!SystemLogger.isDebugBuild) return
val pkg = ConfigurationManager.getPackagesForUid(callingUid).firstOrNull()
?: "uid:$callingUid"
val tags = buildList {
if (parsedParams.attestationChallenge != null) add("challenge")
if (parsedParams.brand != null || parsedParams.device != null ||
parsedParams.product != null || parsedParams.manufacturer != null ||
parsedParams.model != null) add("props")
if (hasDeviceIdAttestation) add("ids")
if (params.any { it.tag == Tag.INCLUDE_UNIQUE_ID }) add("unique_id")
}.joinToString(",")
SystemLogger.debug(
"[probe] tx=$txId uid=$callingUid pkg=$pkg alias=${keyDescriptor.alias} " +
"algo=${parsedParams.algorithm} sb=${securityLevel == SecurityLevel.STRONGBOX} " +
"tags=[$tags] -> $outcome"
)
}
if (ConfigurationManager.shouldSkipUid(callingUid)
&& attestationKey == null && !isAttestKeyRequest) {
logProbe("SKIP")
return TransactionResult.ContinueAndSkipPost
}
@@ -456,27 +487,22 @@ class KeyMintSecurityLevelInterceptor(
val challenge = parsedParams.attestationChallenge
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT) {
SystemLogger.warning("[TX_ID: $txId] Rejecting oversized attestation challenge: ${challenge.size} bytes (max ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})")
logProbe("REJECT:challenge_len")
return InterceptorUtils.createErrorReply(KEYMINT_INVALID_INPUT_LENGTH)
}
if (params.any { it.tag == Tag.CREATION_DATETIME }) {
SystemLogger.warning("[TX_ID: $txId] Rejecting CREATION_DATETIME in generateKey params")
logProbe("REJECT:creation_datetime")
return InterceptorUtils.createErrorReply(RESPONSE_INVALID_ARGUMENT)
}
if (params.any { it.tag == Tag.DEVICE_UNIQUE_ATTESTATION } && !AndroidPermissionUtils.hasUniqueIdAttestationPermission(callingUid)) {
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_UNIQUE_ATTESTATION for uid=$callingUid")
logProbe("REJECT:cannot_attest_unique")
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
}
val hasDeviceIdAttestation = params.any {
it.tag == Tag.ATTESTATION_ID_IMEI ||
it.tag == Tag.ATTESTATION_ID_MEID ||
it.tag == Tag.ATTESTATION_ID_SERIAL ||
it.tag == Tag.DEVICE_UNIQUE_ATTESTATION ||
it.tag == Tag.ATTESTATION_ID_SECOND_IMEI
}
// Device-ID attestation mirrors a real KeyMint: privileged callers (GMS/system)
// get it, ordinary apps get CANNOT_ATTEST_IDS. The permission check below is that
// rule. Device-property attestation (BRAND/MODEL/...) is honored unconditionally —
@@ -484,6 +510,7 @@ class KeyMintSecurityLevelInterceptor(
// path needs. Capability is keyed to the device we present, not the real (dead) TEE.
if(hasDeviceIdAttestation && !AndroidPermissionUtils.hasDeviceAttestationPermission(callingUid)) {
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_ID_ATTESTATION for uid=$callingUid")
logProbe("REJECT:cannot_attest_ids")
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
}
@@ -517,6 +544,7 @@ class KeyMintSecurityLevelInterceptor(
if (securityLevel == SecurityLevel.STRONGBOX && !isStrongBoxCapable(parsedParams)) {
SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}) → forwarding to HAL for rejection")
logProbe("FORWARD_HAL")
return TransactionResult.ContinueAndSkipPost
}
@@ -531,9 +559,16 @@ class KeyMintSecurityLevelInterceptor(
SystemLogger.trace { "[TRACE-$txId] dispatch: forceGen=$forceGenerate hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest" }
when {
forceGenerate -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
parsedParams.attestationChallenge != null -> TransactionResult.Continue
forceGenerate -> {
logProbe("FORGE")
doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
}
parsedParams.attestationChallenge != null -> {
logProbe("PATCH")
TransactionResult.Continue
}
else -> {
logProbe("PASSTHROUGH")
cleanupKeyData(keyId)
TransactionResult.Continue
}