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.
This commit is contained in:
+7
-2
@@ -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)
|
||||
|
||||
@@ -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<Authorization>?) {
|
||||
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}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
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. */
|
||||
|
||||
Reference in New Issue
Block a user