From f25265c0a207c95fe3c205fa29cdc5b195f88128 Mon Sep 17 00:00:00 2001 From: Christina Fu Date: Mon, 18 May 2026 17:07:50 -0700 Subject: [PATCH 1/2] Add ML-KEM support to KRA key archival This change adds ML-KEM key archival support to KRA when using an ML-KEM storage certificate: - When an ML-KEM storage certificate is detected, the archival process uses ML-KEM encapsulation to derive a shared secret (instead of generating and wrapping a session key with RSA/EC as in traditional archival). - The user's private key is wrapped with the shared secret using AES-KWP, producing a backward-compatible privateKeyData structure: SEQUENCE { kemCiphertext, wrappedPrivateKey } - Runtime enforcement ensures that ML-KEM storage certificates cannot be used with the allowEncDecrypt_archival mode, as ML-KEM does not support this alternative encryption path. - The privateKeyData structure remains compatible with existing code: RSA/EC storage: SEQUENCE { wrappedSessionKey, wrappedPrivateKey } ML-KEM storage: SEQUENCE { kemCiphertext, wrappedPrivateKey } Recovery (unwrap) support will be added in a separate PR. Assisted-by: Claude IDM-5472 IDM-6363 --- .../com/netscape/kra/EnrollmentService.java | 29 +++- .../java/com/netscape/kra/StorageKeyUnit.java | 135 +++++++++++++----- 2 files changed, 123 insertions(+), 41 deletions(-) diff --git a/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java b/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java index e5c49967d19..137501eb909 100644 --- a/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java +++ b/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.math.BigInteger; import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateException; import java.util.Arrays; @@ -72,6 +73,7 @@ import com.netscape.cmscore.request.Request; import com.netscape.cmscore.security.JssSubsystem; import com.netscape.cmscore.util.StatsSubsystem; +import com.netscape.cmsutil.crypto.CryptoUtil; /** * A class represents archival request processor. It @@ -407,9 +409,12 @@ this is done below with unwrap() // // privateKeyData ::= SEQUENCE { - // sessionKey OCTET_STRING, - // encKey OCTET_STRING, - // } + // sessionKey OCTET_STRING, // RSA/EC storage: wrapped session key + // // ML-KEM storage: KEM ciphertext + // encKey OCTET_STRING, // User private key wrapped with session key/shared secret + // } + // + // Note: allowEncDecrypt_archival is not supported for ML-KEM storage at this time. // if (statsSub != null) { statsSub.startTiming("encrypt_user_key"); @@ -421,6 +426,12 @@ this is done below with unwrap() params = mStorageUnit.getWrappingParams(allowEncDecrypt_archival); if (allowEncDecrypt_archival == true) { + String storageAlg = mStorageUnit.getPublicKey().getAlgorithm(); + if (CryptoUtil.isAlgorithmMLKEM(storageAlg)) { + String msg = "allowEncDecrypt_archival not supported with ML-KEM storage certificate"; + logger.error("EnrollmentService: " + msg); + throw new EKRAException(msg); + } logger.info("EnrollmentService: Encrypting internal private key"); privateKeyData = mStorageUnit.encryptInternalPrivate(unwrapped, params); } else { @@ -508,6 +519,18 @@ this is done below with unwrap() rec.set(KeyRecord.ATTR_META_INFO, metaInfo); // key size does not apply to EC rec.setKeySize(-1); + } else if (CryptoUtil.isAlgorithmMLKEM(keyAlg)) { + // ML-KEM keys: extract strength parameter + try { + int strength = CryptoUtil.getMLKEMStrength(keyAlg); + rec.setKeySize(Integer.valueOf(strength)); + + logger.debug("EnrollmentService: ML-KEM key archived - algorithm: " + keyAlg + ", strength: " + strength); + + } catch (NoSuchAlgorithmException e) { + logger.warn("EnrollmentService: Unable to determine ML-KEM strength: " + e.getMessage(), e); + rec.setKeySize(-1); + } } // if record already has a serial number, yell out. diff --git a/base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java b/base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java index e654c0a4c2d..36f6c47e14f 100644 --- a/base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java +++ b/base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java @@ -1115,15 +1115,50 @@ public byte[] encryptInternalPrivate(byte priKey[], WrappingParams params) throw logger.debug("StorageKeyUnit.encryptInternalPrivate"); CryptoToken internalToken = getInternalToken(); - // (1) generate session key - SymmetricKey sk = CryptoUtil.generateKey( - internalToken, - params.getSkKeyGenAlgorithm(), - params.getSkLength(), - null, - false); + PublicKey storagePubKey = getPublicKey(); + String storageAlg = storagePubKey.getAlgorithm(); + + SymmetricKey sk; + byte[] session; + + // Check if storage cert is ML-KEM (PQC) + if (CryptoUtil.isAlgorithmMLKEM(storageAlg)) { + logger.debug("StorageKeyUnit.encryptInternalPrivate: Using ML-KEM storage cert"); + + // ML-KEM requires the public key to be imported as a + // PKCS#11 object on the token for PK11_Encapsulate + internalToken.importPublicKey(storagePubKey, false); + + // (1) ML-KEM encapsulation to generate shared secret + CryptoUtil.KEMEncapsulation kemResult = CryptoUtil.encapsulateMLKEM( + storagePubKey, + params.getSkLength()); + + sk = kemResult.sharedSecret; + session = kemResult.ciphertext; + + logger.debug("StorageKeyUnit.encryptInternalPrivate: ML-KEM encapsulation complete"); + + } else { + // (1) RSA/EC: generate session key + logger.debug("StorageKeyUnit.encryptInternalPrivate: Using RSA/EC storage cert"); - // (2) wrap private key with session key + sk = CryptoUtil.generateKey( + internalToken, + params.getSkKeyGenAlgorithm(), + params.getSkLength(), + null, + false); + + // (3) wrap session with storage public + session = CryptoUtil.wrapUsingPublicKey( + internalToken, + storagePubKey, + sk, + params.getSkWrapAlgorithm()); + } + + // (2) wrap private key with session key (or shared secret for ML-KEM) byte[] pri = CryptoUtil.encryptUsingSymmetricKey( internalToken, sk, @@ -1131,17 +1166,11 @@ public byte[] encryptInternalPrivate(byte priKey[], WrappingParams params) throw params.getPayloadEncryptionAlgorithm(), params.getPayloadEncryptionIV()); - // (3) wrap session with storage public - byte[] session = CryptoUtil.wrapUsingPublicKey( - internalToken, - getPublicKey(), - sk, - params.getSkWrapAlgorithm()); - // use MY own structure for now: // SEQUENCE { - // encryptedSession OCTET STRING, - // encryptedPrivate OCTET STRING + // encryptedSession OCTET STRING, // RSA/EC: wrapped session key + // // ML-KEM: KEM ciphertext + // encryptedPrivate OCTET STRING // Private key wrapped with session key/shared secret // } DerOutputStream tmp = new DerOutputStream(); @@ -1176,21 +1205,57 @@ private byte[] _wrap(PrivateKey priKey, SymmetricKey symmKey, WrappingParams par logger.debug("StorageKeyUnit.wrap interal."); CryptoToken token = getToken(); - SymmetricKey.Usage usages[] = new SymmetricKey.Usage[2]; - usages[0] = SymmetricKey.Usage.WRAP; - usages[1] = SymmetricKey.Usage.UNWRAP; + PublicKey storagePubKey = getPublicKey(); + String storageAlg = storagePubKey.getAlgorithm(); - // (1) generate session key - SymmetricKey sk = CryptoUtil.generateKey( - token, - params.getSkKeyGenAlgorithm(), - params.getSkLength(), - usages, - true); + SymmetricKey sk; + byte[] session; - // (2) wrap private key with session key - // KeyWrapper wrapper = internalToken.getKeyWrapper( + // Check if storage cert is ML-KEM (PQC) + if (CryptoUtil.isAlgorithmMLKEM(storageAlg)) { + logger.debug("StorageKeyUnit:wrap() Using ML-KEM storage cert"); + // ML-KEM requires the public key to be imported as a + // PKCS#11 object on the token for PK11_Encapsulate + token.importPublicKey(storagePubKey, false); + + // (1) ML-KEM encapsulation to generate shared secret + CryptoUtil.KEMEncapsulation kemResult = CryptoUtil.encapsulateMLKEM( + storagePubKey, + params.getSkLength()); + + sk = kemResult.sharedSecret; + session = kemResult.ciphertext; + + logger.debug("StorageKeyUnit:wrap() ML-KEM encapsulation complete - ciphertext size: " + + session.length + " bytes"); + + } else { + // (1) RSA/EC: generate session key + logger.debug("StorageKeyUnit:wrap() Using RSA/EC storage cert"); + + SymmetricKey.Usage usages[] = new SymmetricKey.Usage[2]; + usages[0] = SymmetricKey.Usage.WRAP; + usages[1] = SymmetricKey.Usage.UNWRAP; + + sk = CryptoUtil.generateKey( + token, + params.getSkKeyGenAlgorithm(), + params.getSkLength(), + usages, + true); + + // (3) wrap session key with storage public key + session = CryptoUtil.wrapUsingPublicKey( + token, + storagePubKey, + sk, + params.getSkWrapAlgorithm()); + + logger.debug("StorageKeyUnit:wrap() session key wrapped"); + } + + // (2) wrap private key with session key (or shared secret for ML-KEM) byte pri[] = null; if (priKey != null) { @@ -1211,17 +1276,11 @@ private byte[] _wrap(PrivateKey priKey, SymmetricKey symmKey, WrappingParams par logger.debug("StorageKeyUnit:wrap() privKey wrapped"); - byte[] session = CryptoUtil.wrapUsingPublicKey( - token, - getPublicKey(), - sk, - params.getSkWrapAlgorithm()); - logger.debug("StorageKeyUnit:wrap() session key wrapped"); - // use MY own structure for now: // SEQUENCE { - // encryptedSession OCTET STRING, - // encryptedPrivate OCTET STRING + // encryptedSession OCTET STRING, // RSA/EC: wrapped session key + // // ML-KEM: KEM ciphertext + // encryptedPrivate OCTET STRING // User private key wrapped with session key/shared secret // } DerOutputStream tmp = new DerOutputStream(); From 2fb4436587d8e7d39174cc9e022fb78a604b8e5b Mon Sep 17 00:00:00 2001 From: Christina Fu Date: Thu, 28 May 2026 11:38:43 -0700 Subject: [PATCH 2/2] Improve ML-KEM keyRecord metadata and upgrade to AES-256 Skip non-applicable metadata fields for ML-KEM key archival: - sessionKeyWrapAlgorithm and sessionKeyKeyGenAlgorithm are not applicable to ML-KEM (uses encapsulation, not wrapping/generation) - payloadEncryptionOID and payloadEncryptionIV are only used when kra.allowEncDecrypt.archival=true (which ML-KEM does not support) Add storageKeyAlgorithm field to keyRecord metadata to clearly indicate the storage certificate algorithm (e.g., "ML-KEM-1024", "RSA", "EC") for better visibility and debugging. Upgrade default session key size from AES-128 to AES-256 for improved security (applies to RSA, EC, and ML-KEM storage certs). Add recommended security settings to KRA CS.cfg: - keyWrap.useOAEP=true for RSA session key wrapping - kra.legacyPKCS12=false with AES-256-KWP for PKCS#12 encryption - Add explanatory comments for configuration clarity Before (ML-KEM keyRecord metadata): metaInfo: sessionKeyWrapAlgorithm:RSA metaInfo: payloadEncrypted:false metaInfo: sessionKeyKeyGenAlgorithm:AES metaInfo: sessionKeyType:AES metaInfo: sessionKeyLength:128 metaInfo: payloadEncryptionOID:2.16.840.1.101.3.4.1.2 metaInfo: payloadEncryptionIV:N5Dpy30TAonmtwgnCdFpKg== metaInfo: payloadWrapAlgorithm:AES KeyWrap/Padding algorithm: 2.16.840.1.101.3.4.4.2 After (ML-KEM keyRecord metadata): metaInfo: storageKeyAlgorithm:ML-KEM metaInfo: payloadEncrypted:false metaInfo: sessionKeyType:AES metaInfo: sessionKeyLength:256 metaInfo: payloadWrapAlgorithm:AES KeyWrap/Padding algorithm: 2.16.840.1.101.3.4.4.2 Assisted-by: Claude IDM-5472 IDM-6363 --- base/kra/shared/conf/CS.cfg | 23 +++++++- .../com/netscape/kra/AsymKeyGenService.java | 3 +- .../com/netscape/kra/EnrollmentService.java | 3 +- .../com/netscape/kra/NetkeyKeygenService.java | 3 +- .../netscape/kra/SecurityDataProcessor.java | 3 +- .../com/netscape/kra/SymKeyGenService.java | 3 +- .../cms/servlet/key/KeyRecordParser.java | 1 + .../com/netscape/cmscore/dbs/KeyRecord.java | 59 ++++++++++++++++--- 8 files changed, 85 insertions(+), 13 deletions(-) diff --git a/base/kra/shared/conf/CS.cfg b/base/kra/shared/conf/CS.cfg index 7208b5bd73e..6a2eb314fee 100644 --- a/base/kra/shared/conf/CS.cfg +++ b/base/kra/shared/conf/CS.cfg @@ -143,7 +143,14 @@ kra.storageUnit.wrapping.0.payloadEncryptionIV=AQEBAQEBAQE= kra.storageUnit.wrapping.0.payloadWrapAlgorithm=DES3/CBC/Pad kra.storageUnit.wrapping.0.payloadWrapIV=AQEBAQEBAQE= kra.storageUnit.wrapping.0.sessionKeyType=DESede -kra.storageUnit.wrapping.1.sessionKeyLength=128 +kra.storageUnit.wrapping._000=## +kra.storageUnit.wrapping._001=## wrapping.1 settings apply to RSA, EC, and ML-KEM storage certs +kra.storageUnit.wrapping._002=## For ML-KEM: sessionKeyWrapAlgorithm and sessionKeyKeyGenAlgorithm +kra.storageUnit.wrapping._003=## are not used (ML-KEM uses encapsulation, not wrapping/generation) +kra.storageUnit.wrapping._004=## payloadEncryption* fields only used when kra.allowEncDecrypt.archival=true +kra.storageUnit.wrapping._005=## (ML-KEM does not support allowEncDecrypt.archival) +kra.storageUnit.wrapping._006=## +kra.storageUnit.wrapping.1.sessionKeyLength=256 kra.storageUnit.wrapping.1.sessionKeyWrapAlgorithm=RSA kra.storageUnit.wrapping.1.payloadEncryptionPadding=PKCS5Padding kra.storageUnit.wrapping.1.sessionKeyKeyGenAlgorithm=AES @@ -155,6 +162,20 @@ kra.storageUnit.wrapping.1.sessionKeyType=AES kra.storageUnit.wrapping.choice=1 kra.storageUnit.nickName= kra.transportUnit.nickName= +kra._000=## +kra._001=## Key Wrapping and PKCS#12 Security Settings +kra._002=## +kra._003=## For improved security, the following settings are recommended: +kra._004=## +kra._005=## 1. Use OAEP for RSA session key wrapping (applies to RSA storage certs only) +kra._006=## ML-KEM storage certs use encapsulation, not RSA wrapping +keyWrap.useOAEP=true +kra._007=## +kra._008=## 2. Use AES-256-KWP for PKCS#12 encryption (applies to all storage certs) +kra._009=## Legacy mode uses DES3, which is less secure +kra.legacyPKCS12=false +kra.nonLegacyAlg=AES/None/PKCS5Padding/Kwp/256 +kra._010=## log._000=## log._001=## Logging log._002=## diff --git a/base/kra/src/main/java/com/netscape/kra/AsymKeyGenService.java b/base/kra/src/main/java/com/netscape/kra/AsymKeyGenService.java index 82b42f87579..b73c06c596c 100644 --- a/base/kra/src/main/java/com/netscape/kra/AsymKeyGenService.java +++ b/base/kra/src/main/java/com/netscape/kra/AsymKeyGenService.java @@ -325,7 +325,8 @@ public boolean serviceRequest(Request request) throws EBaseException { } try { - record.setWrappingParams(params, allowEncDecrypt_archival); + String storageKeyAlg = storageUnit.getPublicKey().getAlgorithm(); + record.setWrappingParams(params, allowEncDecrypt_archival, storageKeyAlg); } catch (Exception e) { errmsg = "Unable to store wrapping parameters: " + e.getMessage(); logger.error("AsymKeyGenService: " + errmsg); diff --git a/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java b/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java index 137501eb909..1b3dd6bf79a 100644 --- a/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java +++ b/base/kra/src/main/java/com/netscape/kra/EnrollmentService.java @@ -558,7 +558,8 @@ this is done below with unwrap() } try { - rec.setWrappingParams(params, allowEncDecrypt_archival); + String storageKeyAlg = mStorageUnit.getPublicKey().getAlgorithm(); + rec.setWrappingParams(params, allowEncDecrypt_archival, storageKeyAlg); } catch (Exception e) { logger.error("EnrollmentService: Unable to store wrapping parameters: " + e.getMessage(), e); // TODO(alee) Set correct audit message here diff --git a/base/kra/src/main/java/com/netscape/kra/NetkeyKeygenService.java b/base/kra/src/main/java/com/netscape/kra/NetkeyKeygenService.java index 008c7a5deb9..d86554723a1 100644 --- a/base/kra/src/main/java/com/netscape/kra/NetkeyKeygenService.java +++ b/base/kra/src/main/java/com/netscape/kra/NetkeyKeygenService.java @@ -606,7 +606,8 @@ public boolean serviceRequest(Request request) throw new Exception("Unable to generate next serial number"); } - rec.setWrappingParams(params, allowEncDecrypt_archival); + String storageKeyAlg = mStorageUnit.getPublicKey().getAlgorithm(); + rec.setWrappingParams(params, allowEncDecrypt_archival, storageKeyAlg); logger.debug("NetkeyKeygenService: before addKeyRecord"); rec.set(KeyRecord.ATTR_ID, serialNo); diff --git a/base/kra/src/main/java/com/netscape/kra/SecurityDataProcessor.java b/base/kra/src/main/java/com/netscape/kra/SecurityDataProcessor.java index a2439d224a6..02df5109e72 100644 --- a/base/kra/src/main/java/com/netscape/kra/SecurityDataProcessor.java +++ b/base/kra/src/main/java/com/netscape/kra/SecurityDataProcessor.java @@ -335,7 +335,8 @@ public boolean archive(Request request) } try { - rec.setWrappingParams(params, doEncrypt); + String storageKeyAlg = storageUnit.getPublicKey().getAlgorithm(); + rec.setWrappingParams(params, doEncrypt, storageKeyAlg); } catch (Exception e) { logger.error("Unable to store wrapping parameters: " + e.getMessage(), e); diff --git a/base/kra/src/main/java/com/netscape/kra/SymKeyGenService.java b/base/kra/src/main/java/com/netscape/kra/SymKeyGenService.java index 71e5d48a7d3..2b3dc1f43a0 100644 --- a/base/kra/src/main/java/com/netscape/kra/SymKeyGenService.java +++ b/base/kra/src/main/java/com/netscape/kra/SymKeyGenService.java @@ -276,7 +276,8 @@ public boolean serviceRequest(Request request) } try { - rec.setWrappingParams(params, allowEncDecrypt_archival); + String storageKeyAlg = mStorageUnit.getPublicKey().getAlgorithm(); + rec.setWrappingParams(params, allowEncDecrypt_archival, storageKeyAlg); } catch (Exception e) { String message = "Unable to store wrapping parameters: " + e.getMessage(); logger.error("SymKeyGenService: " + message, e); diff --git a/base/server/src/main/java/com/netscape/cms/servlet/key/KeyRecordParser.java b/base/server/src/main/java/com/netscape/cms/servlet/key/KeyRecordParser.java index b13e8c1f5a1..f81bb3a2216 100644 --- a/base/server/src/main/java/com/netscape/cms/servlet/key/KeyRecordParser.java +++ b/base/server/src/main/java/com/netscape/cms/servlet/key/KeyRecordParser.java @@ -47,6 +47,7 @@ public class KeyRecordParser { public final static String OUT_RECOVERED_ON = "recoveredOn"; /* parameters to populate WrappingParams */ + public final static String OUT_STORAGE_KEY_ALGORITHM = "storageKeyAlgorithm"; public final static String OUT_SK_TYPE = "sessionKeyType"; public final static String OUT_SK_KEYGEN_ALGORITHM = "sessionKeyKeyGenAlgorithm"; public final static String OUT_SK_LENGTH = "sessionKeyLength"; diff --git a/base/server/src/main/java/com/netscape/cmscore/dbs/KeyRecord.java b/base/server/src/main/java/com/netscape/cmscore/dbs/KeyRecord.java index 6728f24fc03..716ebe30c16 100644 --- a/base/server/src/main/java/com/netscape/cmscore/dbs/KeyRecord.java +++ b/base/server/src/main/java/com/netscape/cmscore/dbs/KeyRecord.java @@ -33,6 +33,7 @@ import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.dbs.keydb.KeyState; import com.netscape.cms.servlet.key.KeyRecordParser; +import com.netscape.cmsutil.crypto.CryptoUtil; /** * A class represents a Key record. It maintains the key @@ -457,26 +458,67 @@ public String getRealm() throws EBaseException { return realm; } + /** + * Sets wrapping parameters for key record metadata. + * Backwards compatibility method - calls overloaded version with storageKeyAlg=null. + */ public void setWrappingParams(WrappingParams params, boolean doEncrypt) throws Exception { + setWrappingParams(params, doEncrypt, null); + } + + /** + * Sets wrapping parameters for key record metadata. + * + * There are two archival paths: + * 1. Normal wrapping path (doEncrypt=false): + * - Uses StorageKeyUnit.wrap() → wrapUsingSymmetricKey() + * - Records: sessionKey*, payloadWrapAlgorithm, payloadWrappingIV + * - For ML-KEM: skips sessionKeyWrapAlgorithm, sessionKeyKeyGenAlgorithm + * + * 2. Encryption path (doEncrypt=true, allowEncDecrypt_archival=true): + * - Uses StorageKeyUnit.encryptInternalPrivate() → encryptUsingSymmetricKey() + * - Records: sessionKey*, payloadEncryptionOID, payloadEncryptionIV + * - Note: ML-KEM does NOT support this path (blocked in EnrollmentService) + * + * @param params Wrapping parameters from storage unit + * @param doEncrypt Whether encryption was used (vs wrapping) + * @param storageKeyAlg Storage cert public key algorithm (e.g., "RSA", "EC", "ML-KEM-1024") + */ + public void setWrappingParams(WrappingParams params, boolean doEncrypt, String storageKeyAlg) throws Exception { if (mMetaInfo == null) { mMetaInfo = new MetaInfo(); } + + // Detect if ML-KEM is used + boolean isMLKEM = CryptoUtil.isAlgorithmMLKEM(storageKeyAlg); + + // Record the storage key algorithm for clarity + if (storageKeyAlg != null) { + mMetaInfo.set(KeyRecordParser.OUT_STORAGE_KEY_ALGORITHM, storageKeyAlg); + } + // set session key parameters mMetaInfo.set(KeyRecordParser.OUT_SK_LENGTH, String.valueOf(params.getSkLength())); if (params.getSkType() != null) { mMetaInfo.set(KeyRecordParser.OUT_SK_TYPE, params.getSkType().toString()); } - if (params.getSkKeyGenAlgorithm() != null) { + + // For ML-KEM, skip sessionKeyKeyGenAlgorithm (key is derived via KEM, not generated) + if (!isMLKEM && params.getSkKeyGenAlgorithm() != null) { // JSS doesn't have a name map or a functional OID map // for now, save the "name" mMetaInfo.set(KeyRecordParser.OUT_SK_KEYGEN_ALGORITHM, params.getSkKeyGenAlgorithm().toString()); } - if (params.getSkWrapAlgorithm() != null) { + + // For ML-KEM, skip sessionKeyWrapAlgorithm (uses encapsulation, not wrapping) + if (!isMLKEM && params.getSkWrapAlgorithm() != null) { mMetaInfo.set(KeyRecordParser.OUT_SK_WRAP_ALGORITHM, params.getSkWrapAlgorithm().toString()); } // set payload parameters - if (params.getPayloadEncryptionAlgorithm() != null) { + // Only set encryption parameters if doEncrypt=true (allowEncDecrypt_archival path) + // Normal wrapping path uses payloadWrapAlgorithm instead + if (doEncrypt && params.getPayloadEncryptionAlgorithm() != null) { EncryptionAlgorithm encrypt = params.getPayloadEncryptionAlgorithm(); try { OBJECT_IDENTIFIER oid = encrypt.toOID(); @@ -488,17 +530,19 @@ public void setWrappingParams(WrappingParams params, boolean doEncrypt) throws E mMetaInfo.set(KeyRecordParser.OUT_PL_ENCRYPTION_PADDING, encrypt.getPadding().toString()); } } - if (params.getPayloadWrapAlgorithm() != null) { + // Wrapping parameters are used in normal path (doEncrypt=false) + if (!doEncrypt && params.getPayloadWrapAlgorithm() != null) { mMetaInfo.set(KeyRecordParser.OUT_PL_WRAP_ALGORITHM, params.getPayloadWrapAlgorithm().toString()); } - if (params.getPayloadWrappingIV() != null) { + if (!doEncrypt && params.getPayloadWrappingIV() != null) { // store as base64 encoded string mMetaInfo.set( KeyRecordParser.OUT_PL_WRAP_IV, Base64.encodeBase64String(params.getPayloadWrappingIV().getIV()) ); } - if (params.getPayloadEncryptionIV() != null) { + // Only set encryption IV if doEncrypt=true (allowEncDecrypt_archival path) + if (doEncrypt && params.getPayloadEncryptionIV() != null) { // store as base 64 encoded string mMetaInfo.set( KeyRecordParser.OUT_PL_ENCRYPTION_IV, @@ -529,10 +573,11 @@ public WrappingParams getWrappingParams(WrappingParams oldParams) throws Excepti data = mMetaInfo.get(KeyRecordParser.OUT_PL_WRAP_ALGORITHM); if (data != null) params.setPayloadWrapAlgorithm(data.toString()); + // Only set encryption algorithm if present (only stored when doEncrypt=true) if (mMetaInfo.get(KeyRecordParser.OUT_PL_ENCRYPTION_OID) != null) { String oidString = mMetaInfo.get(KeyRecordParser.OUT_PL_ENCRYPTION_OID).toString(); params.setPayloadEncryptionAlgorithm(EncryptionAlgorithm.fromOID(new OBJECT_IDENTIFIER(oidString))); - } else { + } else if (mMetaInfo.get(KeyRecordParser.OUT_PL_ENCRYPTION_ALGORITHM) != null) { params.setPayloadEncryptionAlgorithm( mMetaInfo.get(KeyRecordParser.OUT_PL_ENCRYPTION_ALGORITHM).toString(), mMetaInfo.get(KeyRecordParser.OUT_PL_ENCRYPTION_MODE).toString(),