feat(kas): Adds FIPS-203 wrap with ML-KEM-768/1024#3652
feat(kas): Adds FIPS-203 wrap with ML-KEM-768/1024#3652dmihalcik-virtru wants to merge 37 commits into
Conversation
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>
📝 WalkthroughWalkthroughAdds 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. ChangesML-KEM direct key-wrap rollout
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
protocol/go/policy/kasregistry/key_access_server_registry.pb.gois excluded by!**/*.pb.goprotocol/go/policy/objects.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (63)
adr/decisions/2026-06-16-mlkem-direct-key-wrap.mddocs/grpc/index.htmldocs/openapi/authorization/authorization.openapi.yamldocs/openapi/authorization/v2/authorization.openapi.yamldocs/openapi/policy/actions/actions.openapi.yamldocs/openapi/policy/attributes/attributes.openapi.yamldocs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yamldocs/openapi/policy/kasregistry/key_access_server_registry.openapi.yamldocs/openapi/policy/namespaces/namespaces.openapi.yamldocs/openapi/policy/objects.openapi.yamldocs/openapi/policy/obligations/obligations.openapi.yamldocs/openapi/policy/registeredresources/registered_resources.openapi.yamldocs/openapi/policy/resourcemapping/resource_mapping.openapi.yamldocs/openapi/policy/subjectmapping/subject_mapping.openapi.yamldocs/openapi/policy/unsafe/unsafe.openapi.yamllib/ocrypto/asym_decryption.golib/ocrypto/asym_encryption.golib/ocrypto/benchmark_test.golib/ocrypto/ec_key_pair.golib/ocrypto/ecc_mode.golib/ocrypto/hybrid_common.golib/ocrypto/hybrid_conformance_test.golib/ocrypto/hybrid_nist.golib/ocrypto/hybrid_nist_test.golib/ocrypto/kem.golib/ocrypto/key_type.golib/ocrypto/mlkem.golib/ocrypto/mlkem_test.golib/ocrypto/xwing.golib/ocrypto/xwing_test.gootdfctl/cmd/policy/kasKeys.gootdfctl/docs/man/policy/kas-registry/key/create.mdotdfctl/docs/man/policy/kas-registry/key/import.mdotdfctl/docs/man/policy/kas-registry/key/rotate.mdotdfctl/pkg/cli/sdkHelpers.gootdfctl/pkg/utils/pemvalidate.gosdk/basekey.gosdk/experimental/tdf/key_access.gosdk/experimental/tdf/keysplit/attributes.gosdk/granter.gosdk/idp_access_token_source.gosdk/kas_client.gosdk/kas_client_test.gosdk/tdf.gosdk/tdf_test.goservice/cmd/keygen/main.goservice/internal/security/basic_manager.goservice/internal/security/crypto_provider.goservice/internal/security/in_process_provider.goservice/internal/security/in_process_provider_test.goservice/internal/security/standard_crypto.goservice/internal/security/test_helpers_test.goservice/kas/access/provider.goservice/kas/access/provider_test.goservice/kas/access/publicKey.goservice/kas/access/rewrap.goservice/kas/kas.goservice/kas/kas_test.goservice/policy/db/grant_mappings.goservice/policy/kasregistry/key_access_server_registry.protoservice/policy/objects.prototest/tdf-roundtrips.batstests-bdd/cukes/utils/utils_genKeys.go
… key create Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
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>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
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-wrappedKAO scheme, including keygen and KAS rewrap gating. - Refactors
lib/ocryptoto unify KEM wrapping via a sharedkemadapter + 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.
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>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
|
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-wrappedKAO type.As part of this work, we also extend the policy service, KAS, and the
otdfctltool to support key generation, import, and public key access.For the configuration, this piggy-backs
mlkem_tdf_enabledon the existinghybrid_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
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