feat(platform): add ML-KEM-768 client-side encapsulation to Go SDK (DSPX-2399)#3486
feat(platform): add ML-KEM-768 client-side encapsulation to Go SDK (DSPX-2399)#3486dmihalcik-virtru wants to merge 1 commit into
Conversation
…SPX-2399) Implement post-quantum key encapsulation for TDF manifests using ML-KEM-768 (NIST FIPS 203). The SDK now emits KAO type "wrapped" with wrappedKey = base64(ml_kem_ciphertext [1088 bytes] || aes_gcm_wrapped_dek) and no ephemeralPublicKey field when the KAS advertises an mlkem:768 key. Changes: - lib/ocrypto: add MLKem768Key constant, IsMLKEMKeyType helper, and MLKEMPublicKeyFromPEM / mlkem.go for parsing PEM-encoded ML-KEM-768 encapsulation keys - sdk/experimental/tdf: add wrapKeyWithMLKEM and dispatch from wrapKeyWithPublicKey; add round-trip test that verifies ciphertext and DEK recovery - sdk/experimental/tdf/keysplit: extend formatAlgorithm and convertAlgEnum2Simple for mlkem:768 (enum value 13, pending platform-proto merge) The xtest/sdk/go/cli.sh `supports mechanism-mlkem` case lives in the opentdf/tests repository and is tracked on branch DSPX-2399-tests. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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 |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces support for ML-KEM-768 key encapsulation within the OpenTDF Go SDK. By implementing the necessary cryptographic primitives and updating the key access workflows, this change allows the SDK to perform client-side key wrapping using post-quantum algorithms, aligning with the requirements specified in DSPX-2399. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Quantum threats loom on the shore, We lock the gates with something more. ML-KEM now guards the key, Safe for all eternity. Footnotes
|
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for ML-KEM-768 key wrapping, including new cryptographic helpers in ocrypto, the implementation of wrapKeyWithMLKEM using AES-GCM for DEK encapsulation, and updated attribute mapping. Reviewer feedback recommends standardizing acronym casing to MLKEM, utilizing constants from the crypto/mlkem package instead of hardcoded magic numbers, and exporting the PEM type constant for better reusability in tests.
| EC256Key KeyType = "ec:secp256r1" | ||
| EC384Key KeyType = "ec:secp384r1" | ||
| EC521Key KeyType = "ec:secp521r1" | ||
| MLKem768Key KeyType = "mlkem:768" |
There was a problem hiding this comment.
| } | ||
|
|
||
| func IsMLKEMKeyType(kt KeyType) bool { | ||
| return kt == MLKem768Key |
| // MLKem768CiphertextSize is the byte length of an ML-KEM-768 ciphertext. | ||
| MLKem768CiphertextSize = 1088 | ||
| // MLKem768PublicKeySize is the byte length of an ML-KEM-768 encapsulation key. | ||
| MLKem768PublicKeySize = 1184 | ||
|
|
||
| mlkem768PEMType = "ML-KEM-768 PUBLIC KEY" | ||
| ) |
There was a problem hiding this comment.
It is better to use the constants provided by the crypto/mlkem package instead of hardcoding magic numbers. Also, the PEM type constant should be exported so it can be used by other packages (like tests) to avoid string duplication, and acronym casing should be consistent.
const (
// MLKEM768CiphertextSize is the byte length of an ML-KEM-768 ciphertext.
MLKEM768CiphertextSize = mlkem.CiphertextSize768
// MLKEM768PublicKeySize is the byte length of an ML-KEM-768 encapsulation key.
MLKEM768PublicKeySize = mlkem.EncapsulationKeySize768
MLKEM768PEMType = "ML-KEM-768 PUBLIC KEY"
)| if block.Type != mlkem768PEMType { | ||
| return nil, fmt.Errorf("unexpected PEM type %q, expected %q", block.Type, mlkem768PEMType) |
There was a problem hiding this comment.
Update these lines to use the renamed and exported PEM type constant.
| if block.Type != mlkem768PEMType { | |
| return nil, fmt.Errorf("unexpected PEM type %q, expected %q", block.Type, mlkem768PEMType) | |
| if block.Type != MLKEM768PEMType { | |
| return nil, fmt.Errorf("unexpected PEM type %q, expected %q", block.Type, MLKEM768PEMType) |
| dk, err := mlkem.GenerateKey768() | ||
| require.NoError(t, err) | ||
| pubKeyBytes := dk.EncapsulationKey().Bytes() | ||
| block := &pem.Block{Type: "ML-KEM-768 PUBLIC KEY", Bytes: pubKeyBytes} |
| assert.Greater(t, len(payload), ocrypto.MLKem768CiphertextSize, "payload must include ciphertext + wrapped DEK") | ||
|
|
||
| ciphertext := payload[:ocrypto.MLKem768CiphertextSize] | ||
| wrappedDEK := payload[ocrypto.MLKem768CiphertextSize:] |
There was a problem hiding this comment.
Update these references to use the renamed MLKEM768CiphertextSize constant.
| assert.Greater(t, len(payload), ocrypto.MLKem768CiphertextSize, "payload must include ciphertext + wrapped DEK") | |
| ciphertext := payload[:ocrypto.MLKem768CiphertextSize] | |
| wrappedDEK := payload[ocrypto.MLKem768CiphertextSize:] | |
| assert.Greater(t, len(payload), ocrypto.MLKEM768CiphertextSize, "payload must include ciphertext + wrapped DEK") | |
| ciphertext := payload[:ocrypto.MLKEM768CiphertextSize] | |
| wrappedDEK := payload[ocrypto.MLKEM768CiphertextSize:] |
|
|
||
| payload, err := ocrypto.Base64Decode([]byte(ka.WrappedKey)) | ||
| require.NoError(t, err) | ||
| assert.Greater(t, len(payload), ocrypto.MLKem768CiphertextSize) |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Summary
Implements ML-KEM-768 (NIST FIPS 203) client-side key encapsulation in the OpenTDF Go SDK experimental TDF package, as specified in DSPX-2399.
MLKem768Keyconstant andIsMLKEMKeyTypehelper tolib/ocryptolib/ocrypto/mlkem.gowithMLKEMPublicKeyFromPEMfor parsing PEM-encoded ML-KEM-768 encapsulation keyswrapKeyWithMLKEMinsdk/experimental/tdf/key_access.godispatched fromwrapKeyWithPublicKeywhen the KAS advertisesmlkem:768keyType = "wrapped",wrappedKey = base64(ml_kem_ciphertext [1088 bytes] || aes_gcm_wrapped_dek), noephemeralPublicKeyformatAlgorithmandconvertAlgEnum2Simpleinkeysplit/attributes.gofor the pendingKAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 13proto enum (platform-proto cell)Dependencies
KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 13andAlgorithm_ALGORITHM_ML_KEM_768 = 6toobjects.proto; once merged the local integer constants inkeysplit/attributes.goshould be replaced with generated namesTest plan
go test github.com/opentdf/platform/lib/ocrypto– passesgo test github.com/opentdf/platform/sdk/experimental/tdf/...– passes including newTestWrapKeyWithMLKEMround-trip testgo vet ./...andgo build ./...– cleanxtest/sdk/go/cli.shsupports mechanism-mlkemcase (lives in opentdf/tests, tracked on branchDSPX-2399-tests)xtest/scenarios/mechanism-mlkem.yaml– requires platform-service cell to landRelated
🤖 Generated with Claude Code