fix(keymint): execute HMAC operations
This commit is contained in:
@@ -58,6 +58,7 @@ data class KeyMintAttestation(
|
|||||||
val maxUsesPerBoot: Int?,
|
val maxUsesPerBoot: Int?,
|
||||||
val maxBootLevel: Int?,
|
val maxBootLevel: Int?,
|
||||||
val minMacLength: Int?,
|
val minMacLength: Int?,
|
||||||
|
val macLength: Int? = null,
|
||||||
val rsaOaepMgfDigest: List<Int>,
|
val rsaOaepMgfDigest: List<Int>,
|
||||||
) {
|
) {
|
||||||
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
/** 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),
|
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
|
||||||
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
|
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
|
||||||
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
|
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
|
||||||
|
macLength = params.findInteger(Tag.MAC_LENGTH),
|
||||||
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
|
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
|
||||||
) {
|
) {
|
||||||
// Log all parsed parameters for debugging purposes.
|
// Log all parsed parameters for debugging purposes.
|
||||||
|
|||||||
+8
-1
@@ -2,6 +2,7 @@ package org.matrix.TEESimulator.interception.keystore.shim
|
|||||||
|
|
||||||
import android.hardware.security.keymint.Algorithm
|
import android.hardware.security.keymint.Algorithm
|
||||||
import android.hardware.security.keymint.BlockMode
|
import android.hardware.security.keymint.BlockMode
|
||||||
|
import android.hardware.security.keymint.Digest
|
||||||
import android.hardware.security.keymint.EcCurve
|
import android.hardware.security.keymint.EcCurve
|
||||||
import android.hardware.security.keymint.KeyOrigin
|
import android.hardware.security.keymint.KeyOrigin
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
@@ -459,6 +460,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
padding = parsedParams.padding.ifEmpty { keyParams.padding },
|
padding = parsedParams.padding.ifEmpty { keyParams.padding },
|
||||||
nonce = parsedParams.nonce,
|
nonce = parsedParams.nonce,
|
||||||
minMacLength = parsedParams.minMacLength ?: keyParams.minMacLength,
|
minMacLength = parsedParams.minMacLength ?: keyParams.minMacLength,
|
||||||
|
macLength = parsedParams.macLength,
|
||||||
)
|
)
|
||||||
} else parsedParams
|
} else parsedParams
|
||||||
|
|
||||||
@@ -798,7 +800,12 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val algoName =
|
val algoName =
|
||||||
when (parsedParams.algorithm) {
|
when (parsedParams.algorithm) {
|
||||||
Algorithm.AES -> "AES"
|
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 ->
|
else ->
|
||||||
throw android.os.ServiceSpecificException(
|
throw android.os.ServiceSpecificException(
|
||||||
KEYMINT_INVALID_ARGUMENT,
|
KEYMINT_INVALID_ARGUMENT,
|
||||||
|
|||||||
+123
-52
@@ -129,6 +129,14 @@ private object JcaAlgorithmMapper {
|
|||||||
Digest.SHA_2_512 -> "SHA-512"
|
Digest.SHA_2_512 -> "SHA-512"
|
||||||
else -> "SHA-256"
|
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 {
|
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
@@ -271,6 +279,53 @@ private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
|
|||||||
override fun abort() {}
|
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(
|
class SoftwareOperation(
|
||||||
private val txId: Long,
|
private val txId: Long,
|
||||||
keyPair: KeyPair?,
|
keyPair: KeyPair?,
|
||||||
@@ -319,59 +374,75 @@ class SoftwareOperation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
primitive =
|
primitive =
|
||||||
when (purpose) {
|
if (params.algorithm == Algorithm.HMAC) {
|
||||||
KeyPurpose.SIGN -> {
|
// An HMAC key is symmetric (secretKey set, keyPair null), so it must
|
||||||
val kp =
|
// not fall through to the purpose-keyed Signer/Verifier paths, which
|
||||||
keyPair
|
// require a keyPair. secretKey is populated at HMAC keygen and restore,
|
||||||
?: throw ServiceSpecificException(
|
// so the throw is a defensive floor, not a live path.
|
||||||
KeystoreErrorCodes.invalidArgument,
|
MacPrimitive(
|
||||||
"[SoftwareOp TX_ID: $txId] SIGN requested but keyPair is null",
|
secretKey
|
||||||
)
|
?: throw ServiceSpecificException(
|
||||||
Signer(kp, params)
|
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",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user