fix(keymint): execute HMAC operations

This commit is contained in:
Enginex0
2026-07-11 16:52:24 +01:00
parent c9918952f4
commit d7109421ef
3 changed files with 133 additions and 53 deletions
@@ -58,6 +58,7 @@ data class KeyMintAttestation(
val maxUsesPerBoot: Int?,
val maxBootLevel: Int?,
val minMacLength: Int?,
val macLength: Int? = null,
val rsaOaepMgfDigest: List<Int>,
) {
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
@@ -134,6 +135,7 @@ data class KeyMintAttestation(
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
macLength = params.findInteger(Tag.MAC_LENGTH),
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
) {
// Log all parsed parameters for debugging purposes.
@@ -2,6 +2,7 @@ package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.BlockMode
import android.hardware.security.keymint.Digest
import android.hardware.security.keymint.EcCurve
import android.hardware.security.keymint.KeyOrigin
import android.hardware.security.keymint.KeyParameter
@@ -459,6 +460,7 @@ class KeyMintSecurityLevelInterceptor(
padding = parsedParams.padding.ifEmpty { keyParams.padding },
nonce = parsedParams.nonce,
minMacLength = parsedParams.minMacLength ?: keyParams.minMacLength,
macLength = parsedParams.macLength,
)
} else parsedParams
@@ -798,7 +800,12 @@ class KeyMintSecurityLevelInterceptor(
val algoName =
when (parsedParams.algorithm) {
Algorithm.AES -> "AES"
Algorithm.HMAC -> "HmacSHA256"
Algorithm.HMAC ->
when (parsedParams.digest.firstOrNull()) {
Digest.SHA_2_384 -> "HmacSHA384"
Digest.SHA_2_512 -> "HmacSHA512"
else -> "HmacSHA256"
}
else ->
throw android.os.ServiceSpecificException(
KEYMINT_INVALID_ARGUMENT,
@@ -129,6 +129,14 @@ private object JcaAlgorithmMapper {
Digest.SHA_2_512 -> "SHA-512"
else -> "SHA-256"
}
fun mapMacAlgorithm(params: KeyMintAttestation): String =
when (params.digest.firstOrNull()) {
Digest.SHA_2_256 -> "HmacSHA256"
Digest.SHA_2_384 -> "HmacSHA384"
Digest.SHA_2_512 -> "HmacSHA512"
else -> "HmacSHA256"
}
}
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
@@ -271,6 +279,53 @@ private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
override fun abort() {}
}
private class MacPrimitive(
secretKey: javax.crypto.SecretKey,
private val params: KeyMintAttestation,
private val txId: Long,
) : CryptoPrimitive {
private val mac: javax.crypto.Mac =
javax.crypto.Mac.getInstance(JcaAlgorithmMapper.mapMacAlgorithm(params)).apply {
init(secretKey)
}
override fun update(data: ByteArray?): ByteArray? {
if (data != null) mac.update(data)
return null
}
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data != null) mac.update(data)
val full = mac.doFinal()
// Tag.MAC_LENGTH is optional on the AndroidKeyStore Mac SPI; default to the
// full digest length so real Mac use keeps working when it is omitted.
val tagBytes = (params.macLength ?: (full.size * 8)) / 8
val tag = full.copyOf(tagBytes)
if (params.purpose.firstOrNull() == KeyPurpose.VERIFY) {
if (signature == null) {
throw ServiceSpecificException(
KeystoreErrorCodes.verificationFailed,
"MAC to verify is null",
)
}
if (!java.security.MessageDigest.isEqual(tag, signature)) {
throw ServiceSpecificException(
KeystoreErrorCodes.verificationFailed,
"MAC verification failed",
)
}
return null
}
SystemLogger.debug {
"[SoftwareOp TX_ID: $txId] hmac-op digest=${params.digest.firstOrNull()} " +
"macLen=${params.macLength} tag=${tag.size}B result=ok"
}
return tag
}
override fun abort() {}
}
class SoftwareOperation(
private val txId: Long,
keyPair: KeyPair?,
@@ -319,6 +374,21 @@ class SoftwareOperation(
}
primitive =
if (params.algorithm == Algorithm.HMAC) {
// An HMAC key is symmetric (secretKey set, keyPair null), so it must
// not fall through to the purpose-keyed Signer/Verifier paths, which
// require a keyPair. secretKey is populated at HMAC keygen and restore,
// so the throw is a defensive floor, not a live path.
MacPrimitive(
secretKey
?: throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"[SoftwareOp TX_ID: $txId] HMAC op but secretKey null",
),
params,
txId,
)
} else {
when (purpose) {
KeyPurpose.SIGN -> {
val kp =
@@ -374,6 +444,7 @@ class SoftwareOperation(
)
}
}
}
private fun checkActive() {
if (finalized) {