feat(interception): close remaining PR #157 compliance gaps

Full diff analysis against upstream's 50 commits revealed 8 functional
gaps after v5.0. These are detectable by conformance tests or detector
apps inspecting KeyMetadata authorizations and operation semantics.

KeyMetadata authorizations:
- Add 9 TEE-enforced tags (CALLER_NONCE, MIN_MAC_LENGTH, ROLLBACK_RESISTANCE,
  EARLY_BOOT_ONLY, ALLOW_WHILE_ON_BODY, TRUSTED_USER_PRESENCE_REQUIRED,
  TRUSTED_CONFIRMATION_REQUIRED, MAX_USES_PER_BOOT, MAX_BOOT_LEVEL)
- Fix CREATION_DATETIME to SOFTWARE security level via createSwAuth
- Add SOFTWARE-enforced date enforcement, USAGE_COUNT_LIMIT, UNLOCKED_DEVICE_REQUIRED

Symmetric key support:
- Generate AES/HMAC keys in software via javax.crypto.KeyGenerator
- GeneratedKeyInfo expanded with nullable keyPair + secretKey fields
- CipherPrimitive accepts java.security.Key for symmetric operations
- SoftwareOperation routes ENCRYPT/DECRYPT to secretKey when available

Operation compliance:
- beginParameters property replaces manual IV wrapping for GCM
- KeyAgreementPrimitive for ECDH AGREE_KEY operations
- handleCreateOperation wrapped in runCatching (crash prevention)
- SECURE_HW_COMMUNICATION_FAILED on software gen failure

Certificate patching:
- Import key cert chain + authorization patching in onPostTransact
- patchAuthorizations added to post-generateKey PATCH mode path
This commit is contained in:
Enginex0
2026-03-19 09:40:48 +01:00
parent 9f03b84364
commit fef17c07ec
4 changed files with 205 additions and 53 deletions
@@ -364,6 +364,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] = KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo( KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
keyData.first, keyData.first,
null,
keyDescriptor.nspace, keyDescriptor.nspace,
response, response,
parsedParameters, parsedParameters,
@@ -303,9 +303,10 @@ object GeneratedKeyPersistence {
return return
} }
val keyPair = generatedKeyInfo.keyPair ?: return
save( save(
keyId = keyId, keyId = keyId,
keyPair = generatedKeyInfo.keyPair, keyPair = keyPair,
nspace = generatedKeyInfo.nspace, nspace = generatedKeyInfo.nspace,
securityLevel = secLevel, securityLevel = secLevel,
certChain = newChain.toList(), certChain = newChain.toList(),
@@ -46,7 +46,8 @@ class KeyMintSecurityLevelInterceptor(
) : BinderInterceptor() { ) : BinderInterceptor() {
data class GeneratedKeyInfo( data class GeneratedKeyInfo(
val keyPair: KeyPair, val keyPair: KeyPair?,
val secretKey: javax.crypto.SecretKey?,
val nspace: Long, val nspace: Long,
val response: KeyEntryResponse, val response: KeyEntryResponse,
val keyParams: KeyMintAttestation? = null, val keyParams: KeyMintAttestation? = null,
@@ -136,6 +137,22 @@ class KeyMintSecurityLevelInterceptor(
} }
attestationKeys.remove(keyId) attestationKeys.remove(keyId)
importedKeys.add(keyId) importedKeys.add(keyId)
if (!ConfigurationManager.shouldSkipUid(callingUid)) {
val metadata: KeyMetadata =
reply.readTypedObject(KeyMetadata.CREATOR)
?: return TransactionResult.SkipTransaction
val originalChain = CertificateHelper.getCertificateChain(metadata)
if (originalChain != null && originalChain.size > 1) {
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
metadata.authorizations =
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
patchedChains[keyId] = newChain
SystemLogger.debug("Cached patched certificate chain for imported key $keyId.")
return InterceptorUtils.createTypedObjectReply(metadata)
}
}
} else if (code == CREATE_OPERATION_TRANSACTION) { } else if (code == CREATE_OPERATION_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
@@ -189,6 +206,8 @@ class KeyMintSecurityLevelInterceptor(
val key = metadata.key!! val key = metadata.key!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
metadata.authorizations =
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
// We must clean up cached generated keys before storing the patched chain // We must clean up cached generated keys before storing the patched chain
cleanupKeyData(keyId) cleanupKeyData(keyId)
@@ -237,7 +256,7 @@ class KeyMintSecurityLevelInterceptor(
txId: Long, txId: Long,
callingUid: Int, callingUid: Int,
data: Parcel, data: Parcel,
): TransactionResult { ): TransactionResult = runCatching {
SystemLogger.debug("[TX_ID: $txId] createOperation parcel: dataSize=${data.dataSize()} dataAvail=${data.dataAvail()} dataPos=${data.dataPosition()}") SystemLogger.debug("[TX_ID: $txId] createOperation parcel: dataSize=${data.dataSize()} dataAvail=${data.dataAvail()} dataPos=${data.dataPosition()}")
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
@@ -287,11 +306,14 @@ class KeyMintSecurityLevelInterceptor(
val params = data.createTypedArray(KeyParameter.CREATOR)!! val params = data.createTypedArray(KeyParameter.CREATOR)!!
val parsedParams = KeyMintAttestation(params).let { p -> val parsedParams = KeyMintAttestation(params).let { p ->
if (p.algorithm != 0) p if (p.algorithm != 0) p
else p.copy(algorithm = when (generatedKeyInfo.keyPair.private.algorithm) { else {
"EC", "ECDSA" -> Algorithm.EC val keyAlgo = generatedKeyInfo.keyPair?.private?.algorithm
"RSA" -> Algorithm.RSA p.copy(algorithm = when (keyAlgo) {
else -> p.algorithm "EC", "ECDSA" -> Algorithm.EC
}) "RSA" -> Algorithm.RSA
else -> generatedKeyInfo.keyParams?.algorithm ?: p.algorithm
})
}
} }
val forced = data.readBoolean() val forced = data.readBoolean()
@@ -318,7 +340,7 @@ class KeyMintSecurityLevelInterceptor(
} else parsedParams } else parsedParams
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, effectiveParams, opLatency) val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, generatedKeyInfo.secretKey, effectiveParams, opLatency)
if (keyParams?.usageCountLimit != null) { if (keyParams?.usageCountLimit != null) {
val limit = keyParams.usageCountLimit val limit = keyParams.usageCountLimit
@@ -328,7 +350,7 @@ class KeyMintSecurityLevelInterceptor(
if (remaining.get() <= 0) { if (remaining.get() <= 0) {
cleanupKeyData(resolvedKeyId) cleanupKeyData(resolvedKeyId)
usageCounters.remove(resolvedKeyId) usageCounters.remove(resolvedKeyId)
throw android.os.ServiceSpecificException(RESPONSE_KEY_NOT_FOUND) return InterceptorUtils.createServiceSpecificErrorReply(RESPONSE_KEY_NOT_FOUND)
} }
softwareOperation.onFinishCallback = { softwareOperation.onFinishCallback = {
if (remaining.decrementAndGet() <= 0) { if (remaining.decrementAndGet() <= 0) {
@@ -347,19 +369,13 @@ class KeyMintSecurityLevelInterceptor(
CreateOperationResponse().apply { CreateOperationResponse().apply {
iOperation = operationBinder iOperation = operationBinder
operationChallenge = null operationChallenge = null
softwareOperation.iv?.let { iv -> parameters = softwareOperation.beginParameters
parameters = KeyParameters().apply {
keyParameter = arrayOf(
KeyParameter().apply {
tag = Tag.NONCE
value = KeyParameterValue.blob(iv)
}
)
}
}
} }
return InterceptorUtils.createTypedObjectReply(response) InterceptorUtils.createTypedObjectReply(response)
}.getOrElse {
SystemLogger.error("Error during createOperation for UID $callingUid.", it)
InterceptorUtils.createServiceSpecificErrorReply(KEYMINT_UNKNOWN_ERROR)
} }
private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult { private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult {
@@ -427,11 +443,6 @@ class KeyMintSecurityLevelInterceptor(
parsedParams.algorithm == Algorithm.HMAC || parsedParams.algorithm == Algorithm.HMAC ||
parsedParams.algorithm == Algorithm.TRIPLE_DES parsedParams.algorithm == Algorithm.TRIPLE_DES
if (isSymmetric) {
SystemLogger.debug("[TX_ID: $txId] Symmetric algorithm ${parsedParams.algorithm} → forwarding to HAL")
return TransactionResult.ContinueAndSkipPost
}
if (securityLevel == SecurityLevel.STRONGBOX && !isStrongBoxCapable(parsedParams)) { if (securityLevel == SecurityLevel.STRONGBOX && !isStrongBoxCapable(parsedParams)) {
SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}) → forwarding to HAL for rejection") SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}) → forwarding to HAL for rejection")
return TransactionResult.ContinueAndSkipPost return TransactionResult.ContinueAndSkipPost
@@ -475,7 +486,7 @@ class KeyMintSecurityLevelInterceptor(
} }
.getOrElse { .getOrElse {
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it) SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
TransactionResult.ContinueAndSkipPost InterceptorUtils.createServiceSpecificErrorReply(SECURE_HW_COMMUNICATION_FAILED)
} }
} }
@@ -487,10 +498,55 @@ class KeyMintSecurityLevelInterceptor(
keyId: KeyIdentifier, keyId: KeyIdentifier,
isAttestKeyRequest: Boolean, isAttestKeyRequest: Boolean,
): TransactionResult { ): TransactionResult {
val startNs = System.nanoTime() val genStartNanos = System.nanoTime()
keyDescriptor.nspace = secureRandom.nextLong() keyDescriptor.nspace = secureRandom.nextLong()
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].") SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
cleanupKeyData(keyId)
val isSymmetric = parsedParams.algorithm != Algorithm.EC &&
parsedParams.algorithm != Algorithm.RSA
if (isSymmetric) {
val algoName = when (parsedParams.algorithm) {
Algorithm.AES -> "AES"
Algorithm.HMAC -> "HmacSHA256"
else -> throw android.os.ServiceSpecificException(
SECURE_HW_COMMUNICATION_FAILED,
"Unsupported symmetric algorithm: ${parsedParams.algorithm}",
)
}
val keyGen = javax.crypto.KeyGenerator.getInstance(algoName)
keyGen.init(parsedParams.keySize)
val secretKey = keyGen.generateKey()
val metadata = KeyMetadata().apply {
keySecurityLevel = securityLevel
key = KeyDescriptor().apply {
domain = Domain.KEY_ID
nspace = keyDescriptor.nspace
alias = null
blob = null
}
certificate = null
certificateChain = null
authorizations = parsedParams.toAuthorizations(callingUid, securityLevel)
modificationTimeMs = System.currentTimeMillis()
}
val response = KeyEntryResponse().apply {
this.metadata = metadata
iSecurityLevel = original
}
generatedKeys[keyId] = GeneratedKeyInfo(null, secretKey, keyDescriptor.nspace, response, parsedParams)
val elapsedMs = (System.nanoTime() - genStartNanos) / 1_000_000
val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS
val delayMs = floor - elapsedMs
if (delayMs > 0) Thread.sleep(delayMs)
return InterceptorUtils.createTypedObjectReply(metadata)
}
val keyData = if (NativeCertGen.isAvailable && attestationKey == null) { val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
generateAttestedKeyPairNative(callingUid, parsedParams) generateAttestedKeyPairNative(callingUid, parsedParams)
?: CertificateGenerator.generateAttestedKeyPair( ?: CertificateGenerator.generateAttestedKeyPair(
@@ -502,9 +558,8 @@ class KeyMintSecurityLevelInterceptor(
) )
} ?: throw Exception("Both native and BouncyCastle cert gen failed.") } ?: throw Exception("Both native and BouncyCastle cert gen failed.")
cleanupKeyData(keyId)
val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor) val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response, parsedParams) generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams)
if (isAttestKeyRequest) attestationKeys.add(keyId) if (isAttestKeyRequest) attestationKeys.add(keyId)
GeneratedKeyPersistence.save( GeneratedKeyPersistence.save(
@@ -521,7 +576,7 @@ class KeyMintSecurityLevelInterceptor(
isAttestationKey = isAttestKeyRequest, isAttestationKey = isAttestKeyRequest,
) )
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000 val elapsedMs = (System.nanoTime() - genStartNanos) / 1_000_000
val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS
val delayMs = floor - elapsedMs val delayMs = floor - elapsedMs
if (delayMs > 0) Thread.sleep(delayMs) if (delayMs > 0) Thread.sleep(delayMs)
@@ -711,7 +766,7 @@ class KeyMintSecurityLevelInterceptor(
) )
val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor) val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response, attestation) generatedKeys[keyId] = GeneratedKeyInfo(keyPair, null, record.nspace, response, attestation)
if (record.isAttestationKey) attestationKeys.add(keyId) if (record.isAttestationKey) attestationKeys.add(keyId)
SystemLogger.debug("Restored persisted key: $keyId") SystemLogger.debug("Restored persisted key: $keyId")
@@ -739,6 +794,8 @@ class KeyMintSecurityLevelInterceptor(
private const val STRONGBOX_OP_LATENCY_FLOOR_MS = 80L private const val STRONGBOX_OP_LATENCY_FLOOR_MS = 80L
private const val KEYMINT_TOO_MANY_OPERATIONS = -29 private const val KEYMINT_TOO_MANY_OPERATIONS = -29
private const val KEYMINT_CANNOT_ATTEST_IDS = -66 private const val KEYMINT_CANNOT_ATTEST_IDS = -66
private const val KEYMINT_UNKNOWN_ERROR = -1000
private const val SECURE_HW_COMMUNICATION_FAILED = -49
private const val MAX_CONCURRENT_OPS_PER_UID = 15 private const val MAX_CONCURRENT_OPS_PER_UID = 15
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4 private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s
@@ -900,6 +957,34 @@ private fun KeyMintAttestation.toAuthorizations(
if (this.rsaPublicExponent != null) { if (this.rsaPublicExponent != null) {
authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong()))) authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
} }
if (this.callerNonce == true) {
authList.add(createAuth(Tag.CALLER_NONCE, KeyParameterValue.boolValue(true)))
}
if (this.minMacLength != null) {
authList.add(createAuth(Tag.MIN_MAC_LENGTH, KeyParameterValue.integer(this.minMacLength)))
}
if (this.rollbackResistance == true) {
authList.add(createAuth(Tag.ROLLBACK_RESISTANCE, KeyParameterValue.boolValue(true)))
}
if (this.earlyBootOnly == true) {
authList.add(createAuth(Tag.EARLY_BOOT_ONLY, KeyParameterValue.boolValue(true)))
}
if (this.allowWhileOnBody == true) {
authList.add(createAuth(Tag.ALLOW_WHILE_ON_BODY, KeyParameterValue.boolValue(true)))
}
if (this.trustedUserPresenceRequired == true) {
authList.add(createAuth(Tag.TRUSTED_USER_PRESENCE_REQUIRED, KeyParameterValue.boolValue(true)))
}
if (this.trustedConfirmationRequired == true) {
authList.add(createAuth(Tag.TRUSTED_CONFIRMATION_REQUIRED, KeyParameterValue.boolValue(true)))
}
if (this.maxUsesPerBoot != null) {
authList.add(createAuth(Tag.MAX_USES_PER_BOOT, KeyParameterValue.integer(this.maxUsesPerBoot)))
}
if (this.maxBootLevel != null) {
authList.add(createAuth(Tag.MAX_BOOT_LEVEL, KeyParameterValue.integer(this.maxBootLevel)))
}
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true))) authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
authList.add(createAuth(Tag.ORIGIN, KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED))) authList.add(createAuth(Tag.ORIGIN, KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED)))
authList.add(createAuth(Tag.OS_VERSION, KeyParameterValue.integer(AndroidDeviceUtils.osVersion))) authList.add(createAuth(Tag.OS_VERSION, KeyParameterValue.integer(AndroidDeviceUtils.osVersion)))
@@ -916,17 +1001,37 @@ private fun KeyMintAttestation.toAuthorizations(
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) { if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch))) authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
} }
authList.add(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
authList.add( fun createSwAuth(tag: Int, value: KeyParameterValue): Authorization {
Authorization().apply { val param = KeyParameter().apply {
this.keyParameter = this.tag = tag
KeyParameter().apply { this.value = value
this.tag = Tag.USER_ID }
this.value = KeyParameterValue.integer(callingUid / 100000) return Authorization().apply {
} this.keyParameter = param
this.securityLevel = SecurityLevel.SOFTWARE this.securityLevel = SecurityLevel.SOFTWARE
} }
) }
authList.add(createSwAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
this.activeDateTime?.let {
authList.add(createSwAuth(Tag.ACTIVE_DATETIME, KeyParameterValue.dateTime(it.time)))
}
this.originationExpireDateTime?.let {
authList.add(createSwAuth(Tag.ORIGINATION_EXPIRE_DATETIME, KeyParameterValue.dateTime(it.time)))
}
this.usageExpireDateTime?.let {
authList.add(createSwAuth(Tag.USAGE_EXPIRE_DATETIME, KeyParameterValue.dateTime(it.time)))
}
this.usageCountLimit?.let {
authList.add(createSwAuth(Tag.USAGE_COUNT_LIMIT, KeyParameterValue.integer(it)))
}
if (this.unlockedDeviceRequired == true) {
authList.add(createSwAuth(Tag.UNLOCKED_DEVICE_REQUIRED, KeyParameterValue.boolValue(true)))
}
authList.add(createSwAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
return authList.toTypedArray() return authList.toTypedArray()
} }
@@ -3,10 +3,14 @@ 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.Digest
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.KeyPurpose import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.PaddingMode import android.hardware.security.keymint.PaddingMode
import android.hardware.security.keymint.Tag
import android.os.ServiceSpecificException import android.os.ServiceSpecificException
import android.system.keystore2.IKeystoreOperation import android.system.keystore2.IKeystoreOperation
import android.system.keystore2.KeyParameters
import java.security.KeyPair import java.security.KeyPair
import java.security.Signature import java.security.Signature
import java.security.SignatureException import java.security.SignatureException
@@ -22,7 +26,7 @@ private sealed interface CryptoPrimitive {
fun update(data: ByteArray?): ByteArray? fun update(data: ByteArray?): ByteArray?
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
fun abort() fun abort()
fun getIv(): ByteArray? = null fun getBeginParameters(): Array<KeyParameter>? = null
} }
private object JcaAlgorithmMapper { private object JcaAlgorithmMapper {
@@ -125,15 +129,14 @@ private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPri
} }
private class CipherPrimitive( private class CipherPrimitive(
keyPair: KeyPair, cryptoKey: java.security.Key,
params: KeyMintAttestation, params: KeyMintAttestation,
private val opMode: Int, private val opMode: Int,
) : CryptoPrimitive { ) : CryptoPrimitive {
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
private val cipher: Cipher = private val cipher: Cipher =
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply { Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private init(opMode, cryptoKey)
init(opMode, key)
} }
override fun updateAad(aadInput: ByteArray?) { override fun updateAad(aadInput: ByteArray?) {
@@ -147,14 +150,45 @@ private class CipherPrimitive(
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? = override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
if (data != null) cipher.doFinal(data) else cipher.doFinal() if (data != null) cipher.doFinal(data) else cipher.doFinal()
override fun getIv(): ByteArray? = if (isAead) cipher.iv else null override fun getBeginParameters(): Array<KeyParameter>? {
val iv = cipher.iv ?: return null
return arrayOf(
KeyParameter().apply {
tag = Tag.NONCE
value = KeyParameterValue.blob(iv)
}
)
}
override fun abort() {}
}
private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
private val agreement: javax.crypto.KeyAgreement =
javax.crypto.KeyAgreement.getInstance("ECDH").apply { init(keyPair.private) }
override fun update(data: ByteArray?): ByteArray? = null
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data == null)
throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"Peer public key required for key agreement",
)
val peerKey =
java.security.KeyFactory.getInstance("EC")
.generatePublic(java.security.spec.X509EncodedKeySpec(data))
agreement.doPhase(peerKey, true)
return agreement.generateSecret()
}
override fun abort() {} override fun abort() {}
} }
class SoftwareOperation( class SoftwareOperation(
private val txId: Long, private val txId: Long,
keyPair: KeyPair, keyPair: KeyPair?,
secretKey: javax.crypto.SecretKey?,
params: KeyMintAttestation, params: KeyMintAttestation,
private val latencyFloorMs: Long = 0L, private val latencyFloorMs: Long = 0L,
) { ) {
@@ -164,8 +198,12 @@ class SoftwareOperation(
var onFinishCallback: (() -> Unit)? = null var onFinishCallback: (() -> Unit)? = null
val iv: ByteArray? val beginParameters: KeyParameters?
get() = primitive.getIv() get() {
val params = primitive.getBeginParameters() ?: return null
if (params.isEmpty()) return null
return KeyParameters().apply { keyParameter = params }
}
init { init {
val purpose = params.purpose.firstOrNull() val purpose = params.purpose.firstOrNull()
@@ -174,10 +212,17 @@ class SoftwareOperation(
primitive = primitive =
when (purpose) { when (purpose) {
KeyPurpose.SIGN -> Signer(keyPair, params) KeyPurpose.SIGN -> Signer(keyPair!!, params)
KeyPurpose.VERIFY -> Verifier(keyPair, params) KeyPurpose.VERIFY -> Verifier(keyPair!!, params)
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE) KeyPurpose.ENCRYPT -> {
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE) val key: java.security.Key = secretKey ?: keyPair!!.public
CipherPrimitive(key, params, Cipher.ENCRYPT_MODE)
}
KeyPurpose.DECRYPT -> {
val key: java.security.Key = secretKey ?: keyPair!!.private
CipherPrimitive(key, params, Cipher.DECRYPT_MODE)
}
KeyPurpose.AGREE_KEY -> KeyAgreementPrimitive(keyPair!!)
else -> else ->
throw ServiceSpecificException( throw ServiceSpecificException(
KeystoreErrorCodes.unsupportedPurpose, KeystoreErrorCodes.unsupportedPurpose,