fix(keystore): grant plane serves patch-mode keys
Domain.GRANT readback only recognized synthetic keys (generatedKeys), so patch-mode keys (real TEE key whose attestation we patch on read, cached in teeResponses) fell through to the real keystore2 unpatched. Android 16 made KeyStoreManager.grantKeyAccess a public API, so the owner read returned our patched chain while the grant read returned the raw real chain -> duck SELF_/ISOLATED_CHAIN_SPLIT. Gate grant()/ungrant()/resolveGrant on ownsKeyResponse() (synthetic OR patch-mode) so every access plane serves the same cached KeyEntryResponse. Pre-36 still answers PERMISSION_DENIED; no behavior change on Android 15.
This commit is contained in:
+6
-4
@@ -339,11 +339,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
?: return TransactionResult.ContinueAndSkipPost
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
val granteeUid = data.readInt()
|
val granteeUid = data.readInt()
|
||||||
val accessVector = data.readInt()
|
val accessVector = data.readInt()
|
||||||
// Only synthetic keys are ours; real keys fall through to the real keystore2, which
|
// Synthetic (generatedKeys) AND patch-mode (teeResponses) keys are ours; both must grant
|
||||||
// applies the same SELinux gate the platform would.
|
// coherently so the Domain.GRANT readback returns the same chain the owner read returns.
|
||||||
|
// Real hardware keys fall through to the real keystore2, which applies the same SELinux
|
||||||
|
// gate the platform would.
|
||||||
val ownerKeyId =
|
val ownerKeyId =
|
||||||
resolveOwnerKeyId(key, callingUid)
|
resolveOwnerKeyId(key, callingUid)
|
||||||
?.takeIf { KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(it) }
|
?.takeIf { KeyMintSecurityLevelInterceptor.ownsKeyResponse(it) }
|
||||||
?: return TransactionResult.ContinueAndSkipPost
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
// Version-gated to mirror the real TEE 1:1. Pre-Android-16, grant was a hidden API and
|
// Version-gated to mirror the real TEE 1:1. Pre-Android-16, grant was a hidden API and
|
||||||
// SELinux denied untrusted_app, so keystore2 returns PERMISSION_DENIED. Android 16
|
// SELinux denied untrusted_app, so keystore2 returns PERMISSION_DENIED. Android 16
|
||||||
@@ -372,7 +374,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
val granteeUid = data.readInt()
|
val granteeUid = data.readInt()
|
||||||
val ownerKeyId =
|
val ownerKeyId =
|
||||||
resolveOwnerKeyId(key, callingUid)
|
resolveOwnerKeyId(key, callingUid)
|
||||||
?.takeIf { KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(it) }
|
?.takeIf { KeyMintSecurityLevelInterceptor.ownsKeyResponse(it) }
|
||||||
?: return TransactionResult.ContinueAndSkipPost
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
// Same version gate as grant(): denied pre-36, revoke the virtualized grant on 36+.
|
// Same version gate as grant(): denied pre-36, revoke the virtualized grant on 36+.
|
||||||
if (Build.VERSION.SDK_INT < GRANT_PUBLIC_API_SDK) {
|
if (Build.VERSION.SDK_INT < GRANT_PUBLIC_API_SDK) {
|
||||||
|
|||||||
+11
-1
@@ -1220,9 +1220,19 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
/** Caller-bound resolve: only the designated grantee, only while the key exists. */
|
/** Caller-bound resolve: only the designated grantee, only while the key exists. */
|
||||||
fun resolveGrant(grantId: Long, callerUid: Int): SoftwareGrant? =
|
fun resolveGrant(grantId: Long, callerUid: Int): SoftwareGrant? =
|
||||||
softwareGrants[grantId]?.takeIf {
|
softwareGrants[grantId]?.takeIf {
|
||||||
it.granteeUid == callerUid && generatedKeys.containsKey(it.ownerKeyId)
|
it.granteeUid == callerUid && ownsKeyResponse(it.ownerKeyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when this interceptor holds a coherent [KeyEntryResponse] for [keyId] — synthetic
|
||||||
|
* (`generatedKeys`) OR patch-mode (`teeResponses`, a real TEE key whose attestation we
|
||||||
|
* patched). The grant plane must virtualize both: gating on `generatedKeys` alone left
|
||||||
|
* patch-mode keys' `Domain.GRANT` readback falling through to the real keystore2 unpatched,
|
||||||
|
* splitting the grant chain against the owner's patched read (duck SELF_/ISOLATED_CHAIN_SPLIT,
|
||||||
|
* surfaced once Android 16 made KeyStoreManager.grantKeyAccess a public API).
|
||||||
|
*/
|
||||||
|
fun ownsKeyResponse(keyId: KeyIdentifier): Boolean = getGeneratedKeyResponse(keyId) != null
|
||||||
|
|
||||||
fun revokeGrant(ownerKeyId: KeyIdentifier, granteeUid: Int) {
|
fun revokeGrant(ownerKeyId: KeyIdentifier, granteeUid: Int) {
|
||||||
softwareGrants.entries
|
softwareGrants.entries
|
||||||
.filter { it.value.ownerKeyId == ownerKeyId && it.value.granteeUid == granteeUid }
|
.filter { it.value.ownerKeyId == ownerKeyId && it.value.granteeUid == granteeUid }
|
||||||
|
|||||||
Reference in New Issue
Block a user