fix(keystore): repair attestation generation gaps
Two gaps in attestation generation surfaced by a tester's Key Attestation app runs on build 259. Device-ID attestation (IMEI/serial) via Shizuku arrives as a privileged UID (shell/system) absent from target.txt, so it was skipped and the real TEE rejected it with CANNOT_ATTEST_IDS (-66). Stop skipping requests that carry device-ID tags, and force the forge path for them (the real TEE cannot attest IDs, so there is no chain to patch). The permission gate still rejects ordinary apps, mirroring a real device. 'Use attest key' produced WRONG_PUBLIC_KEY_TYPE: a reused persistent attest key is designated by KEY_ID with a null alias, so the lookup missed and the leaf was silently re-rooted under the keybox, double-rooting the chain the caller assembles. Resolve the attest key by KEY_ID as well as alias, and refuse to emit a leaf rather than fall back to the keybox when a designated attest key cannot be resolved.
This commit is contained in:
+41
-4
@@ -591,10 +591,16 @@ class KeyMintSecurityLevelInterceptor(
|
||||
)
|
||||
}
|
||||
|
||||
// Device-ID attestation (IMEI/MEID/serial) is a factory-provisioned capability the
|
||||
// real TEE frequently cannot satisfy (it returns CANNOT_ATTEST_IDS). A privileged
|
||||
// caller — shell/system, e.g. the Key Attestation app via Shizuku — is entitled to
|
||||
// request it, but arrives un-targeted, so don't skip it: it must reach the forge
|
||||
// path below. The permission gate still rejects ordinary apps further down.
|
||||
if (
|
||||
ConfigurationManager.shouldSkipUid(callingUid) &&
|
||||
attestationKey == null &&
|
||||
!isAttestKeyRequest
|
||||
!isAttestKeyRequest &&
|
||||
!hasDeviceIdAttestation
|
||||
) {
|
||||
logProbe("SKIP")
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
@@ -702,11 +708,15 @@ class KeyMintSecurityLevelInterceptor(
|
||||
|
||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||
|
||||
// Device-ID attestation must be forged, not patched: the real TEE returns
|
||||
// CANNOT_ATTEST_IDS, so there is no real chain to patch — only a synthetic one
|
||||
// carrying the requested IDs and rooted under the keybox will satisfy the caller.
|
||||
val forceGenerate =
|
||||
oversized ||
|
||||
ConfigurationManager.shouldGenerate(callingUid) ||
|
||||
isAttestKeyRequest ||
|
||||
attestationKey != null
|
||||
attestationKey != null ||
|
||||
hasDeviceIdAttestation
|
||||
|
||||
SystemLogger.trace {
|
||||
"[TRACE-$txId] dispatch: forceGen=$forceGenerate hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest"
|
||||
@@ -858,6 +868,19 @@ class KeyMintSecurityLevelInterceptor(
|
||||
return InterceptorUtils.createTypedObjectReply(metadata, diagnosticTag = "gen-mode-sym")
|
||||
}
|
||||
|
||||
// The framework can designate the attest key by alias OR — for a persistent key the caller
|
||||
// reuses across sessions — by KEY_ID with a null alias. Resolve both: the leaf must be
|
||||
// signed by the attest key the caller chains to via getCertChain, never silently re-rooted
|
||||
// under the keybox (which double-roots the assembled chain and fails verification).
|
||||
val attestKeyAlias: String? =
|
||||
attestationKey?.let { it.alias ?: findGeneratedAliasByKeyId(callingUid, it.nspace) }
|
||||
if (attestationKey != null && attestKeyAlias == null) {
|
||||
throw android.os.ServiceSpecificException(
|
||||
KEYMINT_INVALID_ARGUMENT,
|
||||
"Designated attest key not resolvable (nspace=${attestationKey.nspace}) for uid $callingUid",
|
||||
)
|
||||
}
|
||||
|
||||
var forgePath = "FORGE-bouncycastle"
|
||||
val keyData =
|
||||
if (NativeCertGen.isAvailable && attestationKey == null) {
|
||||
@@ -867,7 +890,7 @@ class KeyMintSecurityLevelInterceptor(
|
||||
?: CertificateGenerator.generateAttestedKeyPair(
|
||||
callingUid,
|
||||
keyDescriptor.alias,
|
||||
attestationKey?.alias,
|
||||
attestKeyAlias,
|
||||
parsedParams,
|
||||
securityLevel,
|
||||
)
|
||||
@@ -875,7 +898,7 @@ class KeyMintSecurityLevelInterceptor(
|
||||
CertificateGenerator.generateAttestedKeyPair(
|
||||
callingUid,
|
||||
keyDescriptor.alias,
|
||||
attestationKey?.alias,
|
||||
attestKeyAlias,
|
||||
parsedParams,
|
||||
securityLevel,
|
||||
)
|
||||
@@ -1559,6 +1582,20 @@ class KeyMintSecurityLevelInterceptor(
|
||||
?.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the alias of a generated key addressed by KEY_ID. The framework hands a reused
|
||||
* (persistent) attest key to generateKey as a KEY_ID descriptor with a null alias; this
|
||||
* maps it back to the alias our cache is keyed by, so the leaf is signed by the attest key
|
||||
* the caller will chain to rather than silently re-rooted under the keybox.
|
||||
*/
|
||||
fun findGeneratedAliasByKeyId(callingUid: Int, nspace: Long?): String? {
|
||||
if (nspace == null || nspace == 0L) return null
|
||||
return generatedKeys.entries
|
||||
.firstOrNull { (keyId, info) -> keyId.uid == callingUid && info.nspace == nspace }
|
||||
?.key
|
||||
?.alias
|
||||
}
|
||||
|
||||
fun findTeeResponseByKeyId(callingUid: Int, nspace: Long?): KeyEntryResponse? {
|
||||
if (nspace == null || nspace == 0L) return null
|
||||
return teeResponses.entries
|
||||
|
||||
@@ -103,10 +103,24 @@ object CertificateGenerator {
|
||||
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||
|
||||
val wantsAttestKey =
|
||||
attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
val attestKeyInfo =
|
||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
getAttestationKeyInfo(uid, attestKeyAlias)
|
||||
} else null
|
||||
if (wantsAttestKey) getAttestationKeyInfo(uid, attestKeyAlias) else null
|
||||
|
||||
// When the caller designates an attest key, the leaf MUST be signed by it and returned
|
||||
// alone (the caller appends the attest key's own chain). Re-rooting under the keybox
|
||||
// here instead yields a self-rooted leaf that, concatenated with the attest key chain,
|
||||
// double-roots and fails verification (WRONG_PUBLIC_KEY_TYPE). Refuse rather than emit
|
||||
// a
|
||||
// broken chain.
|
||||
if (wantsAttestKey && attestKeyInfo == null) {
|
||||
SystemLogger.error(
|
||||
"Designated attest key '$attestKeyAlias' not found for uid $uid; refusing to " +
|
||||
"emit a keybox-rooted leaf that would break the caller's chain."
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
val (signingKey, issuer) =
|
||||
attestKeyInfo?.let { it.first to it.second }
|
||||
|
||||
Reference in New Issue
Block a user