fix(keymint): enforce operation authorizations

Add checkOperationAuthorizations to the AuthorizeCreate chain so the
interceptor rejects operations whose parameters are incompatible with
the key, matching real KeyMint HAL behavior:

- block mode, padding, digest, and RSA-OAEP MGF digest must each be a
  subset of the key's authorized set;
- AES-GCM rejects a requested MAC length below the key minimum;
- RSA-OAEP requires a digest.

Add the four backing KeyMint error codes (INCOMPATIBLE_BLOCK_MODE,
INCOMPATIBLE_PADDING_MODE, INCOMPATIBLE_DIGEST, INVALID_MAC_LENGTH) to
KeystoreErrorCodes, resolved at runtime with AOSP-correct fallbacks.

The check reads the raw request params (AuthorizeCreate.check is called
with parsedParams), so no op-param construction change is needed, and
execution is unaffected: our SoftwareOperation already runs GCM (128-bit
tag) and OAEP.
This commit is contained in:
Enginex0
2026-07-11 13:23:29 +01:00
parent 422f78ebcf
commit ab4f41eb2a
2 changed files with 59 additions and 0 deletions
@@ -1,8 +1,10 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.BlockMode
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.PaddingMode
import android.hardware.security.keymint.Tag
import org.matrix.TEESimulator.attestation.KeyMintAttestation
@@ -18,6 +20,7 @@ object AuthorizeCreate {
// Algorithm-level rejection runs before purpose-list check (AOSP HAL behavior)
return checkAlgorithmPurpose(keyParams, purpose)
?: checkPurpose(keyParams, purpose)
?: checkOperationAuthorizations(keyParams, opParams)
?: checkTemporalValidity(keyParams, purpose)
?: checkCallerNonce(keyParams, purpose, rawOpParams)
}
@@ -41,6 +44,46 @@ object AuthorizeCreate {
return null
}
private fun checkOperationAuthorizations(
keyParams: KeyMintAttestation,
opParams: KeyMintAttestation,
): Int? {
if (opParams.blockMode.any { it !in keyParams.blockMode }) {
return KeystoreErrorCodes.incompatibleBlockMode
}
if (opParams.padding.any { it !in keyParams.padding }) {
return KeystoreErrorCodes.incompatiblePaddingMode
}
if (opParams.digest.any { it !in keyParams.digest }) {
return KeystoreErrorCodes.incompatibleDigest
}
if (opParams.rsaOaepMgfDigest.any { it !in keyParams.rsaOaepMgfDigest }) {
return KeystoreErrorCodes.incompatibleDigest
}
if (keyParams.algorithm == Algorithm.AES && opParams.blockMode.contains(BlockMode.GCM)) {
val requestedMacLength = opParams.minMacLength
val keyMinMacLength = keyParams.minMacLength
if (
requestedMacLength != null &&
keyMinMacLength != null &&
requestedMacLength < keyMinMacLength
) {
return KeystoreErrorCodes.invalidMacLength
}
}
if (
keyParams.algorithm == Algorithm.RSA &&
opParams.padding.contains(PaddingMode.RSA_OAEP) &&
opParams.digest.isEmpty()
) {
return KeystoreErrorCodes.incompatibleDigest
}
return null
}
private fun checkTemporalValidity(keyParams: KeyMintAttestation, purpose: Int): Int? {
val now = System.currentTimeMillis()
@@ -501,6 +501,22 @@ internal object KeystoreErrorCodes {
resolveField("android.hardware.security.keymint.ErrorCode", "UNKNOWN_ERROR", -1000)
}
val incompatibleBlockMode: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_BLOCK_MODE", -8)
}
val incompatiblePaddingMode: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_PADDING_MODE", -11)
}
val incompatibleDigest: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_DIGEST", -13)
}
val invalidMacLength: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_MAC_LENGTH", -57)
}
fun resolveField(className: String, fieldName: String, fallback: Int): Int =
runCatching { Class.forName(className).getField(fieldName).getInt(null) }
.getOrElse {