From d7109421efa92bc1277402c2d7dceff7028671f7 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Sat, 11 Jul 2026 16:52:24 +0100 Subject: [PATCH] fix(keymint): execute HMAC operations --- .../attestation/KeyMintAttestation.kt | 2 + .../shim/KeyMintSecurityLevelInterceptor.kt | 9 +- .../keystore/shim/SoftwareOperation.kt | 175 ++++++++++++------ 3 files changed, 133 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt index 4664a6b..dd2dd82 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt @@ -58,6 +58,7 @@ data class KeyMintAttestation( val maxUsesPerBoot: Int?, val maxBootLevel: Int?, val minMacLength: Int?, + val macLength: Int? = null, val rsaOaepMgfDigest: List, ) { /** 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. diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index db8f071..314c93c 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -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, diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt index 6a2e7cf..af97919 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt @@ -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,59 +374,75 @@ class SoftwareOperation( } primitive = - when (purpose) { - KeyPurpose.SIGN -> { - val kp = - keyPair - ?: throw ServiceSpecificException( - KeystoreErrorCodes.invalidArgument, - "[SoftwareOp TX_ID: $txId] SIGN requested but keyPair is null", - ) - Signer(kp, params) + 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 = + keyPair + ?: throw ServiceSpecificException( + KeystoreErrorCodes.invalidArgument, + "[SoftwareOp TX_ID: $txId] SIGN requested but keyPair is null", + ) + Signer(kp, params) + } + KeyPurpose.VERIFY -> { + val kp = + keyPair + ?: throw ServiceSpecificException( + KeystoreErrorCodes.invalidArgument, + "[SoftwareOp TX_ID: $txId] VERIFY requested but keyPair is null", + ) + Verifier(kp, params) + } + KeyPurpose.ENCRYPT -> { + val key: java.security.Key = + secretKey + ?: keyPair?.public + ?: throw ServiceSpecificException( + KeystoreErrorCodes.unsupportedPurpose, + "[SoftwareOp TX_ID: $txId] ENCRYPT requires either secretKey or keyPair.public", + ) + CipherPrimitive(key, params, Cipher.ENCRYPT_MODE, txId) + } + KeyPurpose.DECRYPT -> { + val key: java.security.Key = + secretKey + ?: keyPair?.private + ?: throw ServiceSpecificException( + KeystoreErrorCodes.unsupportedPurpose, + "[SoftwareOp TX_ID: $txId] DECRYPT requires either secretKey or keyPair.private", + ) + CipherPrimitive(key, params, Cipher.DECRYPT_MODE, txId) + } + KeyPurpose.AGREE_KEY -> { + val kp = + keyPair + ?: throw ServiceSpecificException( + KeystoreErrorCodes.invalidArgument, + "[SoftwareOp TX_ID: $txId] AGREE_KEY requested but keyPair is null", + ) + KeyAgreementPrimitive(kp) + } + else -> + throw ServiceSpecificException( + KeystoreErrorCodes.unsupportedPurpose, + "Unsupported operation purpose: $purpose", + ) } - KeyPurpose.VERIFY -> { - val kp = - keyPair - ?: throw ServiceSpecificException( - KeystoreErrorCodes.invalidArgument, - "[SoftwareOp TX_ID: $txId] VERIFY requested but keyPair is null", - ) - Verifier(kp, params) - } - KeyPurpose.ENCRYPT -> { - val key: java.security.Key = - secretKey - ?: keyPair?.public - ?: throw ServiceSpecificException( - KeystoreErrorCodes.unsupportedPurpose, - "[SoftwareOp TX_ID: $txId] ENCRYPT requires either secretKey or keyPair.public", - ) - CipherPrimitive(key, params, Cipher.ENCRYPT_MODE, txId) - } - KeyPurpose.DECRYPT -> { - val key: java.security.Key = - secretKey - ?: keyPair?.private - ?: throw ServiceSpecificException( - KeystoreErrorCodes.unsupportedPurpose, - "[SoftwareOp TX_ID: $txId] DECRYPT requires either secretKey or keyPair.private", - ) - CipherPrimitive(key, params, Cipher.DECRYPT_MODE, txId) - } - KeyPurpose.AGREE_KEY -> { - val kp = - keyPair - ?: throw ServiceSpecificException( - KeystoreErrorCodes.invalidArgument, - "[SoftwareOp TX_ID: $txId] AGREE_KEY requested but keyPair is null", - ) - KeyAgreementPrimitive(kp) - } - else -> - throw ServiceSpecificException( - KeystoreErrorCodes.unsupportedPurpose, - "Unsupported operation purpose: $purpose", - ) } }