From 9561f7d9c0b718f3db34b837bafa577717c667b1 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Thu, 4 Jun 2026 20:39:49 +0100 Subject: [PATCH] feat(logging): per-UID forge diagnostics The attestation dossier only fired on a successfully produced chain, so the StrongBox/BHIM failures left nothing on the per-UID plane and had to be reconstructed from marshalled .bin dumps offline. Add three records, all debug- and target-gated like the existing dossier: - keybox-pick: which keybox signs the forge (requested algo, exact match vs EC fail-safe, signer subject) -- makes an EC-only-keybox RSA fallback visible instead of silent. - forge-fail: emit the failure reason on the per-UID plane when a forge throws (e.g. ATTESTATION_KEYS_NOT_PROVISIONED), paired with dispatch. - auth-shape: the emitted authorization list (count, ordered tags, per-auth securityLevel) -- the surface the duck generate-mode parcel fingerprint stride-walks, readable without offline decode. --- .../shim/KeyMintSecurityLevelInterceptor.kt | 9 ++++- .../logging/AttestationDossier.kt | 40 +++++++++++++++++++ .../TEESimulator/pki/CertificateGenerator.kt | 21 +++++++--- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 34d11c5..89336aa 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -746,8 +746,12 @@ class KeyMintSecurityLevelInterceptor( } } } - .getOrElse { - SystemLogger.error("Error during generateKey handling for UID $callingUid.", it) + .getOrElse { ex -> + SystemLogger.error("Error during generateKey handling for UID $callingUid.", ex) + // The dossier only fires on a produced chain; a forge that throws (e.g. a missing + // keybox) would otherwise leave no per-UID record. Pair this with the preceding + // `dispatch` line to see which request failed and why. + SystemLogger.uidLog(callingUid, txId, "forge-fail") { "ex=${ex.message}" } InterceptorUtils.createServiceSpecificErrorReply(SECURE_HW_COMMUNICATION_FAILED) } } @@ -914,6 +918,7 @@ class KeyMintSecurityLevelInterceptor( val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor) + AttestationDossier.logAuthShape(callingUid, txId, response.metadata?.authorizations) generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams) Keystore2Interceptor.forgetDeletedKey(keyId) diff --git a/app/src/main/java/org/matrix/TEESimulator/logging/AttestationDossier.kt b/app/src/main/java/org/matrix/TEESimulator/logging/AttestationDossier.kt index f68c68e..f6dfeba 100644 --- a/app/src/main/java/org/matrix/TEESimulator/logging/AttestationDossier.kt +++ b/app/src/main/java/org/matrix/TEESimulator/logging/AttestationDossier.kt @@ -1,5 +1,7 @@ package org.matrix.TEESimulator.logging +import android.hardware.security.keymint.Tag +import android.system.keystore2.Authorization import java.security.cert.Certificate import java.security.cert.X509Certificate import org.matrix.TEESimulator.attestation.AttestationPatcher @@ -30,4 +32,42 @@ object AttestationDossier { SystemLogger.uidLog(uid, txId, "chain", AttestationPatcher.formatCertChain(chain)) SystemLogger.uidLog(uid, txId, "props", AndroidDeviceUtils.describeSources(uid)) } + + /** + * Records the *shape* of the emitted authorization list — count, ordered tags, and per-auth + * securityLevel. This is the exact surface the duck detector's generate-mode parcel fingerprint + * stride-walks, so logging it readably lets a "fingerprint" detection be compared against the + * known genuine-TEE shape without decoding the marshalled reply offline. + */ + fun logAuthShape(uid: Int, txId: Long, authorizations: Array?) { + if (!SystemLogger.isUidLogged(uid)) return + val auths = authorizations ?: return + val shape = auths.joinToString(",") { "${tagName(it.keyParameter.tag)}/${it.securityLevel}" } + SystemLogger.uidLog(uid, txId, "auth-shape", "n=${auths.size} [$shape]") + } + + /** Names the authorization tags that occur in generate-mode replies; others render as numbers. */ + private fun tagName(tag: Int): String = + when (tag) { + Tag.PURPOSE -> "PURPOSE" + Tag.ALGORITHM -> "ALGORITHM" + Tag.KEY_SIZE -> "KEY_SIZE" + Tag.DIGEST -> "DIGEST" + Tag.PADDING -> "PADDING" + Tag.EC_CURVE -> "EC_CURVE" + Tag.RSA_PUBLIC_EXPONENT -> "RSA_PUBLIC_EXPONENT" + Tag.NO_AUTH_REQUIRED -> "NO_AUTH_REQUIRED" + Tag.ORIGIN -> "ORIGIN" + Tag.OS_VERSION -> "OS_VERSION" + Tag.OS_PATCHLEVEL -> "OS_PATCHLEVEL" + Tag.VENDOR_PATCHLEVEL -> "VENDOR_PATCHLEVEL" + Tag.BOOT_PATCHLEVEL -> "BOOT_PATCHLEVEL" + Tag.CREATION_DATETIME -> "CREATION_DATETIME" + Tag.ROOT_OF_TRUST -> "ROOT_OF_TRUST" + Tag.USER_ID -> "USER_ID" + Tag.USAGE_COUNT_LIMIT -> "USAGE_COUNT_LIMIT" + Tag.UNLOCKED_DEVICE_REQUIRED -> "UNLOCKED_DEVICE_REQUIRED" + Tag.ACTIVE_DATETIME -> "ACTIVE_DATETIME" + else -> "tag${tag and 0x0FFFFFFF}" + } } diff --git a/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt b/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt index d3cddf3..1411c75 100644 --- a/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt +++ b/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt @@ -191,12 +191,21 @@ object CertificateGenerator { // certificate"). Mirrors the patch path's fail-safe // (AttestationPatcher.getKeyboxForUidAndAlgorithm) and the RSA-leaf-under-EC-keybox handling // in commit e6d5e4d. - return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName) - ?: KeyBoxManager.getAnyAttestationKey(keyboxFile) - ?: throw android.os.ServiceSpecificException( - -75, // ATTESTATION_KEYS_NOT_PROVISIONED - "No usable attestation key in $keyboxFile", - ) + val matched = KeyBoxManager.getAttestationKey(keyboxFile, algorithmName) + val keybox = + matched + ?: KeyBoxManager.getAnyAttestationKey(keyboxFile) + ?: throw android.os.ServiceSpecificException( + -75, // ATTESTATION_KEYS_NOT_PROVISIONED + "No usable attestation key in $keyboxFile", + ) + // Surface which keybox actually signs the forge, so an EC-only-keybox fallback (an RSA leaf + // rooted under the EC key) is visible on the per-UID plane instead of silent. + SystemLogger.uidLog(uid, null, "keybox-pick") { + "req=$algorithmName ${if (matched != null) "matched" else "fellback-to-any"} " + + "signer=${getIssuerFromKeybox(keybox)}" + } + return keybox } /** Retrieves the key pair and issuer name for a given attestation key alias. */