fix(operation): pass operation-time params through to CipherPrimitive

createOperation was building effectiveParams from key-generation params
but dropping operation-time fields (nonce, blockMode, padding,
minMacLength). This caused GCM decrypt to fail with
"IV must be specified in GCM mode" since the nonce from the begin call
never reached CipherPrimitive.

Also adds nonce field to KeyMintAttestation and handles GCM/CBC/CTR IV
initialization in CipherPrimitive.
This commit is contained in:
Enginex0
2026-03-26 05:03:01 +01:00
parent 76461ad39a
commit 7d470cf830
4 changed files with 16 additions and 1 deletions
@@ -46,6 +46,7 @@ data class KeyMintAttestation(
val usageExpireDateTime: Date?,
val usageCountLimit: Int?,
val callerNonce: Boolean?,
val nonce: ByteArray?,
val unlockedDeviceRequired: Boolean?,
val includeUniqueId: Boolean?,
val rollbackResistance: Boolean?,
@@ -121,6 +122,7 @@ data class KeyMintAttestation(
usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME),
usageCountLimit = params.findInteger(Tag.USAGE_COUNT_LIMIT),
callerNonce = params.findBoolean(Tag.CALLER_NONCE),
nonce = params.findBlob(Tag.NONCE),
unlockedDeviceRequired = params.findBoolean(Tag.UNLOCKED_DEVICE_REQUIRED),
includeUniqueId = params.findBoolean(Tag.INCLUDE_UNIQUE_ID),
rollbackResistance = params.findBoolean(Tag.ROLLBACK_RESISTANCE),
@@ -436,6 +436,7 @@ private data class LegacyKeygenParameters(
usageExpireDateTime = null,
usageCountLimit = null,
callerNonce = null,
nonce = null,
unlockedDeviceRequired = null,
includeUniqueId = null,
rollbackResistance = null,
@@ -360,6 +360,10 @@ class KeyMintSecurityLevelInterceptor(
keyParams.copy(
purpose = parsedParams.purpose,
digest = parsedParams.digest.ifEmpty { keyParams.digest },
blockMode = parsedParams.blockMode.ifEmpty { keyParams.blockMode },
padding = parsedParams.padding.ifEmpty { keyParams.padding },
nonce = parsedParams.nonce,
minMacLength = parsedParams.minMacLength ?: keyParams.minMacLength,
)
} else parsedParams
@@ -863,6 +867,7 @@ class KeyMintSecurityLevelInterceptor(
usageExpireDateTime = null,
usageCountLimit = null,
callerNonce = null,
nonce = null,
unlockedDeviceRequired = null,
includeUniqueId = null,
rollbackResistance = null,
@@ -137,7 +137,14 @@ private class CipherPrimitive(
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
private val cipher: Cipher =
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
init(opMode, cryptoKey)
val nonce = params.nonce
if (nonce != null && isAead) {
init(opMode, cryptoKey, javax.crypto.spec.GCMParameterSpec(128, nonce))
} else if (nonce != null) {
init(opMode, cryptoKey, javax.crypto.spec.IvParameterSpec(nonce))
} else {
init(opMode, cryptoKey)
}
}
override fun updateAad(aadInput: ByteArray?) {