Skip to content

Add ML-KEM support to KRA key archival#5358

Draft
ladycfu wants to merge 1 commit into
dogtagpki:masterfrom
ladycfu:pqc-kra-archival
Draft

Add ML-KEM support to KRA key archival#5358
ladycfu wants to merge 1 commit into
dogtagpki:masterfrom
ladycfu:pqc-kra-archival

Conversation

@ladycfu
Copy link
Copy Markdown
Contributor

@ladycfu ladycfu commented May 19, 2026

This change adds ML-KEM key archival support for storage key wrapping:
Depends on #5363 (CryptoUtil ML-KEM bits fix)

EnrollmentService.java:

  • Add missing imports for CryptoUtil and NoSuchAlgorithmException
  • Update privateKeyData comment to clarify RSA/EC vs ML-KEM storage structure
  • Add note that allowEncDecrypt_archival is not supported for ML-KEM at this time
  • Existing ML-KEM metadata handling uses CryptoUtil.getMLKEMStrength()
  • Calls StorageKeyUnit.wrap() at line 431 (no changes needed)

StorageKeyUnit.java:

  • Detect ML-KEM storage certificate by checking algorithm name
  • ML-KEM path: Use CryptoUtil.encapsulateMLKEM() to generate shared secret and KEM ciphertext
  • RSA/EC path: Existing session key generation flow (unchanged)
  • Both paths: Wrap user private key with session key/shared secret using AES-KWP
  • Return SEQUENCE { session/ciphertext, wrappedPrivateKey } - same structure for backward compatibility
  • Update comments to clarify dual usage

The privateKeyData structure remains compatible:

  • For RSA/EC storage: SEQUENCE { wrappedSessionKey, wrappedPrivateKey }
  • For ML-KEM storage: SEQUENCE { kemCiphertext, wrappedPrivateKey }

Recovery (unwrap) support will be added in a separate PR.

IDM-5472
Assisted-by: Claude

Summary by CodeRabbit

New Features

  • Introduced support for ML-KEM post-quantum cryptographic keys in key enrollment and storage operations
  • Implemented ML-KEM encapsulation mechanism for secure session key wrapping
  • Improved cryptographic key strength detection with enhanced error handling and diagnostic logging for post-quantum algorithms

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 537e1d95-fa3c-479d-99d2-7ac6019f5e0d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add ML-KEM support to KRA key archival' directly and clearly summarizes the main change: introducing ML-KEM support for KRA key archival storage key wrapping.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for ML-KEM (Post-Quantum Cryptography) within the KRA enrollment and storage workflows. Key changes include updating EnrollmentService to extract ML-KEM key strength and modifying StorageKeyUnit to implement KEM encapsulation for shared secret generation. Feedback identifies a unit mismatch where bits were passed instead of bytes for the KEM key size and a potential NullPointerException when checking the key algorithm string.

Comment on lines +1190 to +1192
CryptoUtil.KEMEncapsulation kemResult = CryptoUtil.encapsulateMLKEM(
storagePubKey,
params.getSkLength());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The params.getSkLength() method returns the key length in bits (e.g., 128 or 256), but CryptoUtil.encapsulateMLKEM expects the keySize parameter in bytes. Passing bits instead of bytes will cause the KEM encapsulation to request an incorrectly sized shared secret (e.g., 256 bytes instead of 32 bytes), which will lead to a failure or security issues.

Suggested change
CryptoUtil.KEMEncapsulation kemResult = CryptoUtil.encapsulateMLKEM(
storagePubKey,
params.getSkLength());
CryptoUtil.KEMEncapsulation kemResult = CryptoUtil.encapsulateMLKEM(
storagePubKey,
params.getSkLength() / 8);

rec.set(KeyRecord.ATTR_META_INFO, metaInfo);
// key size does not apply to EC
rec.setKeySize(-1);
} else if (keyAlg.toUpperCase().startsWith("ML-KEM")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check keyAlg.toUpperCase().startsWith("ML-KEM") will throw a NullPointerException if keyAlg is null. While keyAlg is unlikely to be null for a valid key, the preceding checks for "RSA" and "EC" use equals(), which safely handles null values. For consistency and safety, a null check should be added.

Suggested change
} else if (keyAlg.toUpperCase().startsWith("ML-KEM")) {
} else if (keyAlg != null && keyAlg.toUpperCase().startsWith("ML-KEM")) {

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
base/kra/src/main/java/com/netscape/kra/EnrollmentService.java (1)

412-417: ⚡ Quick win

Consider enforcing the allowEncDecrypt_archival constraint for ML-KEM storage.

The comment documents that allowEncDecrypt_archival is not supported for ML-KEM storage, but there's no runtime enforcement. If allowEncDecrypt_archival=true is configured with an ML-KEM storage certificate, mStorageUnit.encryptInternalPrivate() (line 430) would be called, which lacks ML-KEM support and may fail unexpectedly.

Consider adding a check to either:

  1. Detect ML-KEM storage and skip the encrypt path, or
  2. Throw a clear error if the unsupported combination is detected
Example enforcement approach
// Before the encrypt/wrap decision, check storage cert algorithm
String storageAlg = mStorageUnit.getPublicKey().getAlgorithm();
if (allowEncDecrypt_archival && storageAlg != null && 
    storageAlg.toUpperCase().startsWith("ML-KEM")) {
    logger.warn("EnrollmentService: allowEncDecrypt_archival not supported with ML-KEM storage, using wrap instead");
    allowEncDecrypt_archival = false;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/kra/src/main/java/com/netscape/kra/EnrollmentService.java` around lines
412 - 417, The code documents that allowEncDecrypt_archival isn't supported for
ML-KEM storage but doesn't enforce it at runtime; before the branch that calls
mStorageUnit.encryptInternalPrivate() (the encrypt vs wrap decision in
EnrollmentService), query mStorageUnit.getPublicKey().getAlgorithm() and if
allowEncDecrypt_archival is true and the algorithm indicates ML-KEM (e.g.,
startsWith("ML-KEM") case-insensitive) either set allowEncDecrypt_archival=false
and emit a clear logger.warn mentioning EnrollmentService and ML-KEM, or throw a
focused IllegalArgumentException with a message that allowEncDecrypt_archival is
not supported for ML-KEM storage so the unsupported path is never taken.
base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java (1)

1185-1223: API verification confirmed; recovery gap is expected per PR scope.

The CryptoUtil.encapsulateMLKEM(PublicKey, int) API exists and correctly returns KEMEncapsulation with sharedSecret (SymmetricKey) and ciphertext (byte[]) fields. The usage in StorageKeyUnit.wrap() is correct.

The recovery gap is acknowledged: unwrap_session_key() and related unwrap methods lack ML-KEM decapsulation support. Since this is deferred to a follow-up PR per the scope, optionally add a debug log message during wrap to document that recovery is unavailable until the decapsulation methods are implemented.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java` around lines
1185 - 1223, The ML-KEM branch in StorageKeyUnit.wrap() correctly uses
CryptoUtil.encapsulateMLKEM(storagePubKey, params.getSkLength()) to produce
kemResult.sharedSecret and kemResult.ciphertext, but recovery/unwrap support is
not yet implemented; add a clear debug log inside the ML-KEM branch (in
StorageKeyUnit.wrap()) after encapsulation to state that ML-KEM
decapsulation/unwrap_session_key() support is not available yet and recovery is
deferred to a follow-up PR (reference CryptoUtil.encapsulateMLKEM,
StorageKeyUnit.wrap(), and unwrap_session_key for context).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@base/kra/src/main/java/com/netscape/kra/EnrollmentService.java`:
- Around line 412-417: The code documents that allowEncDecrypt_archival isn't
supported for ML-KEM storage but doesn't enforce it at runtime; before the
branch that calls mStorageUnit.encryptInternalPrivate() (the encrypt vs wrap
decision in EnrollmentService), query mStorageUnit.getPublicKey().getAlgorithm()
and if allowEncDecrypt_archival is true and the algorithm indicates ML-KEM
(e.g., startsWith("ML-KEM") case-insensitive) either set
allowEncDecrypt_archival=false and emit a clear logger.warn mentioning
EnrollmentService and ML-KEM, or throw a focused IllegalArgumentException with a
message that allowEncDecrypt_archival is not supported for ML-KEM storage so the
unsupported path is never taken.

In `@base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java`:
- Around line 1185-1223: The ML-KEM branch in StorageKeyUnit.wrap() correctly
uses CryptoUtil.encapsulateMLKEM(storagePubKey, params.getSkLength()) to produce
kemResult.sharedSecret and kemResult.ciphertext, but recovery/unwrap support is
not yet implemented; add a clear debug log inside the ML-KEM branch (in
StorageKeyUnit.wrap()) after encapsulation to state that ML-KEM
decapsulation/unwrap_session_key() support is not available yet and recovery is
deferred to a follow-up PR (reference CryptoUtil.encapsulateMLKEM,
StorageKeyUnit.wrap(), and unwrap_session_key for context).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72978e54-9452-44d2-b6ac-fd450a8d7d27

📥 Commits

Reviewing files that changed from the base of the PR and between cdce94b and 436a053.

📒 Files selected for processing (2)
  • base/kra/src/main/java/com/netscape/kra/EnrollmentService.java
  • base/kra/src/main/java/com/netscape/kra/StorageKeyUnit.java

@ladycfu ladycfu force-pushed the pqc-kra-archival branch 3 times, most recently from f6bfdbc to 6a07d1e Compare May 22, 2026 01:22
This change adds ML-KEM key archival support for storage key wrapping:

**EnrollmentService.java:**
- Add missing imports for CryptoUtil and NoSuchAlgorithmException
- Update privateKeyData comment to clarify RSA/EC vs ML-KEM storage structure
- Add note that allowEncDecrypt_archival is not supported for ML-KEM at this time
- Existing ML-KEM metadata handling uses CryptoUtil.getMLKEMStrength()
- Calls StorageKeyUnit.wrap() at line 431 (no changes needed)

**StorageKeyUnit.java:**
- Detect ML-KEM storage certificate by checking algorithm name
- ML-KEM path: Use CryptoUtil.encapsulateMLKEM() to generate shared secret
  and KEM ciphertext
- RSA/EC path: Existing session key generation flow (unchanged)
- Both paths: Wrap user private key with session key/shared secret using
  AES-KWP
- Return SEQUENCE { session/ciphertext, wrappedPrivateKey } - same structure
  for backward compatibility
- Update comments to clarify dual usage

The privateKeyData structure remains compatible:
- For RSA/EC storage: SEQUENCE { wrappedSessionKey, wrappedPrivateKey }
- For ML-KEM storage: SEQUENCE { kemCiphertext, wrappedPrivateKey }

Recovery (unwrap) support will be added in a separate PR.

IDM-5472
Assisted-by: Claude
@ladycfu ladycfu force-pushed the pqc-kra-archival branch from e4227b1 to cf586a4 Compare May 22, 2026 07:01
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant