Skip to content

feat(kas): Adds FIPS-203 wrap with ML-KEM-768/1024#3652

Open
dmihalcik-virtru wants to merge 37 commits into
mainfrom
DSPX-3383-merge-main
Open

feat(kas): Adds FIPS-203 wrap with ML-KEM-768/1024#3652
dmihalcik-virtru wants to merge 37 commits into
mainfrom
DSPX-3383-merge-main

Conversation

@dmihalcik-virtru

@dmihalcik-virtru dmihalcik-virtru commented Jun 22, 2026

Copy link
Copy Markdown
Member

ML-KEM 768/1024

The primary motivation for this work is ML-KEM 768 and 1024 support for key wrapping and KAS/policy algorithm selection. These provide FIPS 3 compliant NIST 203 implementations, which will provide post-quantum resistance for TDFs created with this algorithm, just as the hybrids do, but in a way that should be compliant with FIPS 3 approved software and hardware encryption modules.

Since this is a new feature for the file, we place it within the new mlkem-wrapped KAO type.

As part of this work, we also extend the policy service, KAS, and the otdfctl tool to support key generation, import, and public key access.

For the configuration, this piggy-backs mlkem_tdf_enabled on the existing hybrid_tdf_enabled, so if the latter is set then pure mlkem is also enabled. You can enable just pure mlkem without hybrid, though. I mostly did this as a convenience for testing, so I can uncouple these later if desired. but to me it makes sense - hybrid implies mlkem, but the use of mlkem does not imply hybrid. As part of this, I've made a normalization pass on the config to set the values, removing the need to check deprecated config names later when making decisions.

Notes for the reviewer

  • refactored the hybrid KEM to share more code with the pure mlkem-wrapped kaos
  • Still duplicating logic between the asym enc/dec .go and the key_pair.go. The former are mostly used by service, and the latter by the sdk, so this is compound interest on the technical debt from not unifying these
  • removes a couple of deprecated constructors for the asym library, which would produce non-conformant mechanisms anyway. I probalby should put an ! in the CC because of this, lmk if you want me to. I couldn't find any live use in a github search
  • some other small move refactors

dmihalcik-virtru and others added 26 commits June 16, 2026 13:55
Add pure ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism)
support alongside existing hybrid post-quantum algorithms.

Changes:
- Add ALGORITHM_MLKEM_768 and ALGORITHM_MLKEM_1024 to proto enums
- Add ML-KEM key types (mlkem:768, mlkem:1024) to ocrypto library
- Implement MLKEMKeyPair and MLKEM1024KeyPair with key generation
- Update proto validation to accept ML-KEM algorithms
- Regenerate all protocol buffers and OpenAPI docs

This is a partial implementation. Remaining work includes:
- Add ML-KEM encryption/decryption to asym_encryption.go
- Add ML-KEM support to SDK and service layers
- Add corresponding tests

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement complete ML-KEM 768/1024 encryption and decryption support
in the ocrypto library.

Encryption changes (asym_encryption.go):
- Add MLKEM SchemeType constant
- Add MLKEMEncryptor768 and MLKEMEncryptor1024 types
- Implement newMLKEM768/newMLKEM1024 constructors
- Handle mlkem.EncapsulationKey in FromPublicPEMWithSalt
- Implement all PublicKeyEncryptor interface methods
- Use AES-GCM with shared secret from KEM encapsulation

Decryption changes (asym_decryption.go):
- Add DecryptWithEphemeralKey method to interface
- Add MLKEMDecryptor768 and MLKEMDecryptor1024 types
- Handle "MLKEM DECAPSULATION KEY" PEM blocks
- Implement Decrypt and DecryptWithEphemeralKey methods
- Use AES-GCM with shared secret from KEM decapsulation

Also updated existing decryptors:
- Add DecryptWithEphemeralKey to AsymDecryption (RSA)
- Add DecryptWithEphemeralKey to ECDecryptor (ECDH)
- Add DecryptWithEphemeralKey to XWingDecryptor (hybrid)
- Add DecryptWithEphemeralKey to HybridNISTDecryptor (hybrid)

All lib/ocrypto tests pass.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add ML-KEM-768 and ML-KEM-1024 support to the KAS service layer:
- Add AlgorithmMLKEM768 and AlgorithmMLKEM1024 constants
- Add ML-KEM decryption support to BasicManager
- Add StandardMLKEMCrypto type for ML-KEM key management
- Add MLKEMPublicKey method for public key export
- Add ML-KEM case to StandardCrypto.Decrypt method

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Complete ML-KEM integration across the service layer:
- Add ML-KEM algorithms to InProcessProvider key listing and decryption
- Add ML-KEM support to public key export in KAS access layer
- Add ML-KEM algorithm mappings in policy grant_mappings

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add ML-KEM-768 and ML-KEM-1024 algorithm support to SDK:
- Add ML-KEM cases to KeyTypeToPolicyAlgorithm conversion
- Add ML-KEM cases to PolicyAlgorithmToKeyType conversion
- Add ML-KEM enum mappings in convertAlgEnum2Simple
- Add ML-KEM string mappings in algProto2String

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add ML-KEM-768 and ML-KEM-1024 support to otdfctl and experimental SDK:
- Add ML-KEM key generation in kasKeys command
- Add "mlkem:768" and "mlkem:1024" string mappings in CLI helpers
- Add ML-KEM validation in PEM validator
- Add ML-KEM enum conversions in experimental keysplit SDK

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add ML-KEM-768 and ML-KEM-1024 key generation:
- Add ML-KEM key generation to service keygen command
- Add ML-KEM key generation to BDD test utilities
- Generate kas-mlkem768-private.pem and kas-mlkem768-public.pem
- Generate kas-mlkem1024-private.pem and kas-mlkem1024-public.pem

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add pure post-quantum encryption tests for ML-KEM-768 and ML-KEM-1024 algorithms.
Tests validate KAO type (mlkem-wrapped), KID assignment (m1, m2), and successful
encrypt/decrypt roundtrips. Also updates KAS config to include ML-KEM keys.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Refactors plain ML-KEM (768/1024) to use ASN.1 DER encoding and HKDF key
derivation, matching the X-Wing and Hybrid NIST patterns. Stores ML-KEM
ciphertext concatenated with wrapped DEK in ASN.1 structure instead of
overloading ephemeralKey metadata.

- Add lib/ocrypto/mlkem.go with X-Wing-style wrap/unwrap functions
- Add comprehensive test coverage in mlkem_test.go
- Preserve backwards compatibility by renaming old types to Legacy variants
- Update otdfctl documentation to include mlkem:768 and mlkem:1024 algorithms

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…Legacy ML-KEM types

Remove DecryptWithEphemeralKey method from PrivateKeyDecryptor interface,
making it EC-specific. Only ECDecryptor needs ephemeral keys for ECDH.

Remove Legacy ML-KEM types and implementations:
- MLKEMEncryptor768Legacy, MLKEMEncryptor1024Legacy
- MLKEMDecryptor768Legacy, MLKEMDecryptor1024Legacy
- Old "MLKEM DECAPSULATION KEY" PEM handling
- Helper functions newMLKEM768(), newMLKEM1024()

Add PEM block constants for new ML-KEM implementation:
- PEMBlockMLKEM768PublicKey, PEMBlockMLKEM768PrivateKey
- PEMBlockMLKEM1024PublicKey, PEMBlockMLKEM1024PrivateKey

Remove DecryptWithEphemeralKey from all non-EC decryptors:
- MLKEMDecryptor768, MLKEMDecryptor1024
- XWingDecryptor, HybridNISTDecryptor
- AsymDecryption (RSA)

Update service layer to use Decrypt() for ML-KEM instead of
DecryptWithEphemeralKey. EC decryption continues to use the
EC-specific DecryptWithEphemeralKey method.

Breaking changes:
- Old ML-KEM PEM format ("MLKEM DECAPSULATION KEY") no longer supported
- Callers must use concrete ECDecryptor type for DecryptWithEphemeralKey

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
ML-KEM keys loaded through InProcessProvider failed FindKeyByID and
Decrypt with "could not determine key type" because the type switch in
determineKeyType was missing the StandardMLKEMCrypto case. Add the
case and a regression test exercising both mlkem:768 and mlkem:1024
through determineKeyType and FindKeyByID.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Cache the ML-KEM PrivateKeyDecryptor on StandardMLKEMCrypto during
loadKey instead of re-parsing the PEM on every Decrypt call. Mirrors
the existing RSA pattern.

Also fixes a latent bug in ocrypto.MLKEMKeyPair PEM writers: they
emitted "MLKEM DECAPSULATION KEY" / "MLKEM ENCAPSULATOR" block types,
but commit 40d10ce removed parser support for those headers. Updated
the writers to use the PEMBlockMLKEM{768,1024}{Private,Public}Key
constants the parser now recognizes.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
The key_algorithm CEL validation on ListKeysRequest only accepted
algorithm values 0-8, which excluded the post-quantum ML-KEM-768 (20)
and ML-KEM-1024 (21) values. Filtering by these algorithms returned a
validation error even though the server could store and serve them.

RotateKeyRequest.NewKey already includes 20 and 21 in its allowed set.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
ExportPublicKey fell through RSA/Hybrid/XWing to ECPublicKey, so pure
ML-KEM keys returned ErrCertNotFound and KAS PublicKey requests for
mlkem:768/mlkem:1024 failed with not_found.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
InProcessProvider.Decrypt rejected empty ephemeralPublicKey for ML-KEM,
but StandardCrypto.Decrypt and BasicManager.Decrypt both reject
non-empty values. The KEM ciphertext is the encapsulation; there is no
separate ephemeral key. Invert the check to match the HPQT case above
and pass nil downstream.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
MLKEMEncryptor768/1024.PublicKeyInPemFormat emitted "MLKEM
ENCAPSULATOR", but FromPublicPEMWithSalt dispatches on
PEMBlockMLKEM768PublicKey / PEMBlockMLKEM1024PublicKey, breaking PEM
round-trip. Use the canonical constants to match the X-Wing pattern
and the existing MLKEMKeyPair serialization in ec_key_pair.go.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Mirrors the negative assertion already in
TestInProcessProviderDetermineKeyType.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Replace the custom "MLKEM768 PUBLIC KEY" / "MLKEM1024 PRIVATE KEY" PEM
labels (raw key bytes with no ASN.1 envelope) with standard "PUBLIC KEY"
and "PRIVATE KEY" labels carrying RFC 5280 SubjectPublicKeyInfo and
RFC 5958 OneAsymmetricKey, with the algorithm conveyed by NIST OIDs
2.16.840.1.101.3.4.4.2 (ML-KEM-768) and 2.16.840.1.101.3.4.4.3
(ML-KEM-1024). The private-key PKCS#8 inner CHOICE uses [0] IMPLICIT
OCTET STRING (seed form, 64 bytes) per draft-ietf-lamps-kyber-certificates.

The encoding is hand-rolled rather than via crypto/x509 because stdlib
ML-KEM support in MarshalPKIXPublicKey / MarshalPKCS8PrivateKey landed in
Go 1.26 and this module pins go 1.25.

FromPublicPEMWithSalt / FromPrivatePEMWithSalt now peek at the OID after
PEM decode and route ML-KEM blobs to the existing encryptor/decryptor
constructors. Non-ML-KEM blobs fall through to the existing RSA/EC
parsers unchanged. The hybrid SECP256R1-MLKEM768, SECP384R1-MLKEM1024,
and X-Wing schemes keep their custom PEM labels for now; conformance to
IETF composite-KEM and X-Wing drafts is tracked under DSPX-3396 and will
land in a separate PR off main.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add support for generating KAOs with type=mlkem-wrapped when using
pure ML-KEM wrapping keys (MLKEM768, MLKEM1024), while maintaining
backwards compatibility for reading type=wrapped KAOs.

Changes:
- Add IsMLKEMKeyType() helper in lib/ocrypto/ec_key_pair.go
- Add kMLKEMWrapped constant and ML-KEM case to createKeyAccess() in sdk/tdf.go
- Implement generateWrapKeyWithMLKEM() in sdk/tdf.go
- Add ML-KEM handling to wrapKeyWithPublicKey() in sdk/experimental/tdf/key_access.go
- Implement wrapKeyWithMLKEM() in sdk/experimental/tdf/key_access.go

This ensures integration tests pass which expect mlkem-wrapped type
for pure ML-KEM keys, while type=wrapped continues for RSA keys.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Make MLKEM768WrapDEK and MLKEM1024WrapDEK accept multiple input formats:
- Raw key bytes (1184/1568 bytes) - fast path
- SPKI DER (1206/1590 bytes)
- PEM-wrapped SPKI (~1686/~2206 bytes)

This fixes the issue introduced in 6a7480d where KAS started returning
SPKI-encoded PEM keys but callers expected raw bytes. Instead of requiring
all callers to decode manually, the crypto library now handles format
detection transparently.

Changes:
- Add normalizeMLKEMPublicKey() helper for format detection
- Export ParseMLKEMPublicSPKI() and OidMLKEM768/OidMLKEM1024 constants
- Update all internal references to use exported names
- Add comprehensive tests for format handling

Benefits:
- Backward compatible (raw keys still work)
- Simpler callers (no manual PEM decoding needed)
- Better encapsulation (format logic in crypto library)
- Future-proof (handles new formats automatically)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds support for pure ML-KEM key agreement objects in the KAS rewrap
handler. The SDK now emits type="mlkem-wrapped" for MLKEM768/MLKEM1024
KAOs, and this change adds the corresponding case to handle decryption.

Follows the hybrid-wrapped pattern: uses HybridTDFEnabled flag, no
ephemeral key processing, and generic error messages per security
guidelines.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Collapse the three near-identical KEM wrap/unwrap implementations behind
one OID-keyed registry and a single internal kem contract. ML-KEM-768,
ML-KEM-1024, X-Wing, P-256+ML-KEM-768, and P-384+ML-KEM-1024 now share
one envelope type, one wrap function, one unwrap function, and one
encryptor/decryptor pair routed through FromPublicPEM / FromPrivatePEM.

Service and SDK callers shrink accordingly: StandardXWingCrypto,
StandardHybridCrypto, and StandardMLKEMCrypto fold into a single
StandardKEMCrypto; the per-algorithm wrap dispatch in sdk/tdf.go and
sdk/experimental/tdf/key_access.go collapses to one IsKEMKeyType branch
calling ocrypto.WrapDEK.

Wire formats are preserved byte-for-byte (hybrid-wrapped, mlkem-wrapped).
The OID registry leaves the planned hybrid-PEM-to-SPKI follow-up as a
near-zero change: three OID constants plus three registry entries.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…DF references

The HKDF-removal for mlkem-wrapped landed in 455f280; flip the ADR to accepted and update two comments that still described the old HKDF-everywhere behaviour for pure ML-KEM (kemWrapKeySize and defaultTDFSalt docstring).

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Reconcile the branch's unified KEM architecture with main's IETF-draft PQ
key-format conformance (#3563 / dc18568) and PQ CLI disable (#3625).

Resolution:
- ocrypto: adopt main's IETF-conformant hybrid types (composite-KEM OIDs,
  SPKI/PKCS#8 PEM, SHA3-256 draft-14 combiner) wholesale; keep the branch's
  unified kem.go + pure ML-KEM (NIST OIDs) as additive, trimming the unified
  adapters for hybrids. Dispatchers try ML-KEM SPKI first, then main's
  hybrid OID routing. Re-added unified WrapDEK; kemDecryptor exposes KeyType().
- service/security: keep the branch's unified StandardKEMCrypto container and
  port main's assertDecryptorAlgorithm load-time guard (now covers ML-KEM).
- protocol: regenerated objects.pb.go/enums.gen.go/connect wrappers from the
  cleanly-merged proto (ML-KEM enums + DynamicValueMapping).
- otdfctl: keep HPQT hybrids disabled (#3625); keep pure ML-KEM CLI support.

Verified: ocrypto/service-security/sdk/otdfctl/kas build, vet, and tests pass
(incl. main's hybrid_conformance_test.go and SDK end-to-end); proto regen is
deterministic; gofumpt clean; ocrypto golangci-lint 0 issues.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds ML-KEM 768/1024 support for KEM wrapping, expands policy and KAS enums, and routes the new algorithms through security, SDK, CLI, and integration tests.

Changes

ML-KEM direct key-wrap rollout

Layer / File(s) Summary
Decision, schemas, and docs
adr/decisions/2026-06-16-mlkem-direct-key-wrap.md, service/policy/objects.proto, service/policy/kasregistry/key_access_server_registry.proto, docs/grpc/index.html, docs/openapi/**/*.yaml, service/policy/db/grant_mappings.go
Adds ML-KEM 768/1024 enum values and request validation to policy contracts and docs, and records the direct-wrap ADR decision.
Core KEM types and adapters
lib/ocrypto/key_type.go, lib/ocrypto/ecc_mode.go, lib/ocrypto/mlkem.go, lib/ocrypto/kem.go, lib/ocrypto/ec_key_pair.go, lib/ocrypto/hybrid_nist.go, lib/ocrypto/xwing.go, lib/ocrypto/asym_encryption.go, lib/ocrypto/asym_decryption.go, lib/ocrypto/hybrid_common.go
Defines shared key types, ML-KEM parsing/marshaling, the unified KEM envelope flow, ML-KEM key-pair support, and the updated PEM dispatch path for KEM keys.
Security loading and decryption
service/internal/security/standard_crypto.go, service/internal/security/basic_manager.go, service/internal/security/in_process_provider.go, service/internal/security/crypto_provider.go, service/internal/security/test_helpers_test.go, service/internal/security/in_process_provider_test.go, service/kas/access/provider.go, service/kas/access/rewrap.go, service/kas/access/publicKey.go, service/kas/kas.go, service/kas/access/provider_test.go, service/kas/kas_test.go
Introduces ML-KEM-aware loading, decryption, preview gating, public-key export, and KAS mechanism filtering and rewrap handling.
SDK, CLI, and key generation plumbing
sdk/basekey.go, sdk/granter.go, sdk/experimental/tdf/key_access.go, sdk/experimental/tdf/keysplit/attributes.go, sdk/idp_access_token_source.go, sdk/kas_client.go, sdk/kas_client_test.go, sdk/tdf.go, sdk/tdf_test.go, otdfctl/cmd/policy/kasKeys.go, otdfctl/pkg/cli/sdkHelpers.go, otdfctl/pkg/utils/pemvalidate.go, service/cmd/keygen/main.go, tests-bdd/cukes/utils/utils_genKeys.go, otdfctl/docs/man/policy/kas-registry/key/*.md
Adds ML-KEM wrapping and enum translation in the SDK, CLI algorithm mapping and validation, key generation outputs, and supporting docs.
Integration and end-to-end coverage
lib/ocrypto/mlkem_test.go, lib/ocrypto/hybrid_nist_test.go, lib/ocrypto/xwing_test.go, lib/ocrypto/benchmark_test.go, test/tdf-roundtrips.bats, otdfctl/e2e/kas-keys.bats
Adds ML-KEM unit and integration coverage and updates existing hybrid/X-Wing tests to use the shared KEM path.

Sequence Diagram(s)

sequenceDiagram
  participant SDK as SDK TDF
  participant WrapDEK as ocrypto.WrapDEK
  participant KEM as kem adapter
  participant KAS as KAS rewrap
  participant KeyDelegator as KeyDelegator

  SDK->>WrapDEK: WrapDEK(MLKEM768Key, kasPublicPEM, dek)
  WrapDEK->>KEM: encapsulate public key
  KEM-->>WrapDEK: sharedSecret and ciphertext
  WrapDEK->>KEM: wrapKey(sharedSecret, nil, nil)
  KEM-->>WrapDEK: sharedSecret
  WrapDEK-->>SDK: kemEnvelope DER
  SDK->>KAS: rewrap request with mlkem-wrapped KAO
  KAS->>KeyDelegator: Decrypt(kemEnvelope, mlkem:768, nil)
  KeyDelegator->>KEM: decapsulate private key and ciphertext
  KEM-->>KeyDelegator: sharedSecret
  KeyDelegator-->>KAS: plaintext DEK
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • opentdf/platform#3276: Refactors the earlier hybrid NIST EC+ML-KEM wrapping API into the shared KEM flow used here.
  • opentdf/platform#3563: Changes the same hybrid NIST wire-format and adapter path that this PR updates to the shared KEM envelope.
  • opentdf/platform#3564: Updates the same mechanism-advertisement and supported-algorithm plumbing extended here for ML-KEM.

Suggested labels

pqc

Suggested reviewers

  • strantalis
  • elizabethhealy

🐇 ML-KEM keys go hop and hum,
Through kemEnvelope they now come.
No HKDF needed, secret bright,
Wrapped and unwrapped in one clean flight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is clear and relevant, summarizing the new FIPS-203 ML-KEM-768/1024 wrapping support.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DSPX-3383-merge-main

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.

@github-actions github-actions Bot added comp:db DB component comp:policy Policy Configuration ( attributes, subject mappings, resource mappings, kas registry) comp:sdk A software development kit, including library, for client applications and inter-service communicati labels Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 287.40848ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 94.917514ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 531.302798ms
Throughput 188.22 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.626214367s
Average Latency 434.243894ms
Throughput 114.61 requests/second

@dmihalcik-virtru dmihalcik-virtru marked this pull request as ready for review June 23, 2026 18:00
@dmihalcik-virtru dmihalcik-virtru requested review from a team as code owners June 23, 2026 18:00
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 184.851173ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 104.079237ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 413.616338ms
Throughput 241.77 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.898972241s
Average Latency 447.477652ms
Throughput 111.36 requests/second

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 188.737429ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 104.892638ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 415.982353ms
Throughput 240.39 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.662302714s
Average Latency 444.502926ms
Throughput 111.95 requests/second

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@sdk/kas_client_test.go`:
- Around line 45-46: In the DPoP decryptor setup, both the error from
ocrypto.FromPrivatePEM(dpopPEM) and the boolean result from the type assertion
to ocrypto.AsymDecryption are being discarded. Instead, follow the pattern used
in Test_processRSAResponse() at lines 462-465 by checking the error returned
from FromPrivatePEM and the ok value from the type assertion, then call
t.Fatalf() to fail fast if either check fails. This ensures setup errors are
caught immediately rather than causing confusing failures later in the test.

In `@service/kas/kas.go`:
- Around line 210-212: The condition at line 210 in the hybrid TDF disabled case
is incorrectly filtering both hybrid key types and ML-KEM key types together,
which prevents pure ML-KEM from being processed when MLKEMTDFEnabled is true but
HybridTDFEnabled is false. Remove the ocrypto.IsMLKEMKeyType(a) check from the
case condition at line 210, leaving only the hybrid key type check, so that pure
ML-KEM filtering is handled exclusively by the separate case at line 212 which
gates it only on the MLKEMTDFEnabled flag.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ae02586f-8ff1-4806-98c6-a3b03e35f94b

📥 Commits

Reviewing files that changed from the base of the PR and between 005f9de and dfbe29e.

⛔ Files ignored due to path filters (2)
  • protocol/go/policy/kasregistry/key_access_server_registry.pb.go is excluded by !**/*.pb.go
  • protocol/go/policy/objects.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (63)
  • adr/decisions/2026-06-16-mlkem-direct-key-wrap.md
  • docs/grpc/index.html
  • docs/openapi/authorization/authorization.openapi.yaml
  • docs/openapi/authorization/v2/authorization.openapi.yaml
  • docs/openapi/policy/actions/actions.openapi.yaml
  • docs/openapi/policy/attributes/attributes.openapi.yaml
  • docs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yaml
  • docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml
  • docs/openapi/policy/namespaces/namespaces.openapi.yaml
  • docs/openapi/policy/objects.openapi.yaml
  • docs/openapi/policy/obligations/obligations.openapi.yaml
  • docs/openapi/policy/registeredresources/registered_resources.openapi.yaml
  • docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml
  • docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml
  • docs/openapi/policy/unsafe/unsafe.openapi.yaml
  • lib/ocrypto/asym_decryption.go
  • lib/ocrypto/asym_encryption.go
  • lib/ocrypto/benchmark_test.go
  • lib/ocrypto/ec_key_pair.go
  • lib/ocrypto/ecc_mode.go
  • lib/ocrypto/hybrid_common.go
  • lib/ocrypto/hybrid_conformance_test.go
  • lib/ocrypto/hybrid_nist.go
  • lib/ocrypto/hybrid_nist_test.go
  • lib/ocrypto/kem.go
  • lib/ocrypto/key_type.go
  • lib/ocrypto/mlkem.go
  • lib/ocrypto/mlkem_test.go
  • lib/ocrypto/xwing.go
  • lib/ocrypto/xwing_test.go
  • otdfctl/cmd/policy/kasKeys.go
  • otdfctl/docs/man/policy/kas-registry/key/create.md
  • otdfctl/docs/man/policy/kas-registry/key/import.md
  • otdfctl/docs/man/policy/kas-registry/key/rotate.md
  • otdfctl/pkg/cli/sdkHelpers.go
  • otdfctl/pkg/utils/pemvalidate.go
  • sdk/basekey.go
  • sdk/experimental/tdf/key_access.go
  • sdk/experimental/tdf/keysplit/attributes.go
  • sdk/granter.go
  • sdk/idp_access_token_source.go
  • sdk/kas_client.go
  • sdk/kas_client_test.go
  • sdk/tdf.go
  • sdk/tdf_test.go
  • service/cmd/keygen/main.go
  • service/internal/security/basic_manager.go
  • service/internal/security/crypto_provider.go
  • service/internal/security/in_process_provider.go
  • service/internal/security/in_process_provider_test.go
  • service/internal/security/standard_crypto.go
  • service/internal/security/test_helpers_test.go
  • service/kas/access/provider.go
  • service/kas/access/provider_test.go
  • service/kas/access/publicKey.go
  • service/kas/access/rewrap.go
  • service/kas/kas.go
  • service/kas/kas_test.go
  • service/policy/db/grant_mappings.go
  • service/policy/kasregistry/key_access_server_registry.proto
  • service/policy/objects.proto
  • test/tdf-roundtrips.bats
  • tests-bdd/cukes/utils/utils_genKeys.go

Comment thread sdk/kas_client_test.go Outdated
Comment thread service/kas/kas.go Outdated
… key create

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 191.977145ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 101.967936ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 426.009246ms
Throughput 234.74 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 46.818279055s
Average Latency 466.374165ms
Throughput 106.80 requests/second

filterMechanismsByPreview dropped pure ML-KEM whenever hybrid TDF was
disabled, so a config with MLKEMTDFEnabled=true and HybridTDFEnabled=false
would process mlkem-wrapped KAOs but report no ML-KEM mechanisms. Align
with rewrap.go, where mlkem-wrapped is gated solely on MLKEMTDFEnabled,
and add a regression test.

Also fail fast in getTokenSource test setup by checking FromPrivatePEM
and the AsymDecryption type assertion.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 149.406489ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 78.408624ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 353.568694ms
Throughput 282.83 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 36.092311655s
Average Latency 359.372171ms
Throughput 138.53 requests/second

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds pure FIPS-203 ML-KEM-768/1024 support to the platform’s KAS key-wrapping flows, integrating it alongside existing hybrid PQ/T mechanisms by introducing a unified KEM wrapping/unwrap path and propagating the new algorithms through policy/proto, service, SDK, CLI, docs, and tests.

Changes:

  • Introduces pure ML-KEM-768/1024 key types and the mlkem-wrapped KAO scheme, including keygen and KAS rewrap gating.
  • Refactors lib/ocrypto to unify KEM wrapping via a shared kem adapter + common ASN.1 envelope and SPKI/PKCS#8 dispatch for ML-KEM.
  • Updates policy/proto enums + OpenAPI/GRPC docs and expands unit/e2e coverage (Go tests + bats roundtrips) for ML-KEM.

Reviewed changes

Copilot reviewed 65 out of 66 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests-bdd/cukes/utils/utils_genKeys.go Extend BDD key generation to include ML-KEM-768/1024 PEM keypairs.
test/tdf-roundtrips.bats Add roundtrip coverage for mlkem-wrapped KAOs and configure ML-KEM KIDs in test stack.
service/policy/objects.proto Add ML-KEM enum values to policy algorithm enums.
service/policy/kasregistry/key_access_server_registry.proto Allow ML-KEM enum values in request validation CEL lists.
service/policy/db/grant_mappings.go Map new policy algorithms to KAS public key alg enums.
service/kas/kas.go Log supported mechanisms using normalized config and preview gating including ML-KEM.
service/kas/kas_test.go Update preview-gating tests for hybrid/ML-KEM visibility.
service/kas/access/rewrap.go Add mlkem-wrapped KAO rewrap handling behind preview flag.
service/kas/access/publicKey.go Permit ML-KEM algorithms in public key endpoints and format handling.
service/kas/access/provider.go Add ML-KEM preview flag and normalize preview settings.
service/kas/access/provider_test.go Test config normalization behavior (hybrid implying ML-KEM).
service/internal/security/test_helpers_test.go Add helpers for loading ML-KEM keys in StandardCrypto tests.
service/internal/security/standard_crypto.go Unify KEM key loading into StandardKEMCrypto and switch RSA parsing to FromPrivatePEM.
service/internal/security/in_process_provider.go Add ML-KEM algorithms to supported list and unify KEM public key export.
service/internal/security/in_process_provider_test.go Add determineKeyType coverage for ML-KEM keys.
service/internal/security/crypto_provider.go Define ML-KEM algorithm string constants.
service/internal/security/basic_manager.go Route all KEM algorithms through IsKEMKeyType and tighten mismatch errors.
service/cmd/keygen/main.go Generate ML-KEM-768/1024 KAS keypairs in keygen tool.
sdk/tdf.go Treat all KEM schemes uniformly for wrapping; emit mlkem-wrapped vs hybrid-wrapped based on key type.
sdk/tdf_test.go Switch RSA unwrap helpers to FromPrivatePEM in tests.
sdk/kas_client.go Replace RSA decryptor construction with FromPrivatePEM + type assertion.
sdk/kas_client_test.go Update tests to use FromPrivatePEM and improve formatting.
sdk/idp_access_token_source.go Use FromPrivatePEM for DPoP RSA decryptor with explicit type check.
sdk/granter.go Map ML-KEM kas public key enums to policy algorithm + string forms.
sdk/experimental/tdf/keysplit/attributes.go Add ML-KEM mappings in experimental keysplit attribute conversions.
sdk/experimental/tdf/key_access.go Use unified WrapDEK for KEM wrapping and select manifest scheme.
sdk/basekey.go Add conversions between ML-KEM KeyType and policy.Algorithm.
protocol/go/policy/objects.pb.go Regenerated Go protos with ML-KEM enum values.
otdfctl/pkg/utils/pemvalidate.go Validate ML-KEM public key PEMs against expected algorithms.
otdfctl/pkg/cli/sdkHelpers.go Add CLI string↔enum conversions for mlkem:768 / mlkem:1024.
otdfctl/e2e/kas-keys.bats Add e2e coverage for creating ML-KEM keys in local mode.
otdfctl/docs/man/policy/kas-registry/key/rotate.md Document ML-KEM algorithms for rotate command.
otdfctl/docs/man/policy/kas-registry/key/import.md Document ML-KEM algorithms for import command.
otdfctl/docs/man/policy/kas-registry/key/create.md Document ML-KEM algorithms for create command.
otdfctl/cmd/policy/kasKeys.go Generate ML-KEM keypairs via CLI key creation.
lib/ocrypto/xwing.go Adapt X-Wing into shared kem interface and remove legacy envelope/types.
lib/ocrypto/xwing_test.go Update X-Wing tests to use shared KEM wrap/unwrap helpers and dispatcher types.
lib/ocrypto/mlkem.go Add ML-KEM SPKI/PKCS#8 ASN.1 helpers and KEM key parsing routines.
lib/ocrypto/mlkem_test.go Add extensive ML-KEM tests including “shared secret is AES key” and salt/info ignored contract.
lib/ocrypto/key_type.go Introduce centralized KeyType definitions including ML-KEM and parsing helpers.
lib/ocrypto/kem.go Add unified KEM wrapping implementation, registry, and KEM encryptor/decryptor types.
lib/ocrypto/hybrid_nist.go Adapt NIST composite KEM hybrids into the shared kem interface and reuse common envelope.
lib/ocrypto/hybrid_nist_test.go Update hybrid NIST tests to use shared KEM wrap/unwrap helpers and dispatcher types.
lib/ocrypto/hybrid_conformance_test.go Update conformance test to assert ciphertext layout via shared envelope and PEM dispatcher.
lib/ocrypto/hybrid_common.go Add unified WrapDEK helper for KEM schemes (hybrid + pure ML-KEM).
lib/ocrypto/ecc_mode.go Extract ECCMode helpers into a dedicated file (used by EC code paths).
lib/ocrypto/ec_key_pair.go Add ML-KEM keypair generation and PEM encoding using ML-KEM SPKI/PKCS#8.
lib/ocrypto/benchmark_test.go Update benchmarks to use unified KEM helpers and PEM dispatchers.
lib/ocrypto/asym_encryption.go Extend dispatcher to route ML-KEM SPKI first, then hybrid OIDs, then x509 parsing.
lib/ocrypto/asym_decryption.go Extend dispatcher to route ML-KEM PKCS#8 first, then hybrid OIDs, then x509 parsing.
docs/openapi/policy/unsafe/unsafe.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/registeredresources/registered_resources.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/obligations/obligations.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/objects.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/namespaces/namespaces.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml Add ML-KEM algorithms and updated CEL snippets to OpenAPI.
docs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/attributes/attributes.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/policy/actions/actions.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/authorization/v2/authorization.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/openapi/authorization/authorization.openapi.yaml Add ML-KEM algorithms to OpenAPI enums.
docs/grpc/index.html Regenerated GRPC docs to include ML-KEM enum values.
adr/decisions/2026-06-16-mlkem-direct-key-wrap.md ADR documenting “direct shared secret as AES key” decision for ML-KEM wrapped KAOs.
Comments suppressed due to low confidence (1)

service/kas/kas_test.go:67

  • There are two identical hybrid-flag test cases, and one is misnamed (“top-level hybrid flag”) even though the top-level flag no longer exists and the config uses Preview.HybridTDFEnabled. This makes the table harder to read and maintain.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread service/kas/access/provider.go Outdated
Comment thread lib/ocrypto/kem.go Outdated
Comment thread service/kas/kas_test.go
Address Copilot review feedback on #3652 (docs/tests only, no behavior change):

- provider.go: HybridTDFEnabled doc said ML-KEM was enabled "by default";
  normalizePreview always forces it, so reword to reflect that it cannot be
  disabled while hybrid is on.
- ocrypto/kem.go: header comment claimed hybrids use their own per-scheme
  encryptor/decryptor types; they now adapt onto the kem interface and share
  the unified kemEncryptor/kemDecryptor. Update to match.
- kas_test.go: drop the duplicate/misnamed "top-level hybrid flag" case and
  replace the unreachable (hybrid on, mlkem off) case with a reachable all-off
  case that still exercises negative ML-KEM gating.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 146.505954ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 74.431212ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 346.812079ms
Throughput 288.34 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 34.159542979s
Average Latency 340.129849ms
Throughput 146.37 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

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

Labels

comp:db DB component comp:kas Key Access Server comp:lib:ocrypto comp:policy Policy Configuration ( attributes, subject mappings, resource mappings, kas registry) comp:sdk A software development kit, including library, for client applications and inter-service communicati docs Documentation size/xl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants