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 {
|
.getOrElse { ex ->
|
||||||
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
|
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)
|
InterceptorUtils.createServiceSpecificErrorReply(SECURE_HW_COMMUNICATION_FAILED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,6 +918,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
val response =
|
val response =
|
||||||
buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
||||||
|
AttestationDossier.logAuthShape(callingUid, txId, response.metadata?.authorizations)
|
||||||
generatedKeys[keyId] =
|
generatedKeys[keyId] =
|
||||||
GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams)
|
GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams)
|
||||||
Keystore2Interceptor.forgetDeletedKey(keyId)
|
Keystore2Interceptor.forgetDeletedKey(keyId)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.matrix.TEESimulator.logging
|
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.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
@@ -30,4 +32,42 @@ object AttestationDossier {
|
|||||||
SystemLogger.uidLog(uid, txId, "chain", AttestationPatcher.formatCertChain(chain))
|
SystemLogger.uidLog(uid, txId, "chain", AttestationPatcher.formatCertChain(chain))
|
||||||
SystemLogger.uidLog(uid, txId, "props", AndroidDeviceUtils.describeSources(uid))
|
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
|
// certificate"). Mirrors the patch path's fail-safe
|
||||||
// (AttestationPatcher.getKeyboxForUidAndAlgorithm) and the RSA-leaf-under-EC-keybox handling
|
// (AttestationPatcher.getKeyboxForUidAndAlgorithm) and the RSA-leaf-under-EC-keybox handling
|
||||||
// in commit e6d5e4d.
|
// in commit e6d5e4d.
|
||||||
return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
|
val matched = KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
|
||||||
?: KeyBoxManager.getAnyAttestationKey(keyboxFile)
|
val keybox =
|
||||||
?: throw android.os.ServiceSpecificException(
|
matched
|
||||||
-75, // ATTESTATION_KEYS_NOT_PROVISIONED
|
?: KeyBoxManager.getAnyAttestationKey(keyboxFile)
|
||||||
"No usable attestation key in $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. */
|
/** Retrieves the key pair and issuer name for a given attestation key alias. */
|
||||||
|
|||||||
Reference in New Issue
Block a user