Skip to content

feat(platform): add ML-KEM-768 client-side encapsulation to Go SDK (DSPX-2399)#3486

Draft
dmihalcik-virtru wants to merge 1 commit into
mainfrom
DSPX-2399-platform-go-sdk
Draft

feat(platform): add ML-KEM-768 client-side encapsulation to Go SDK (DSPX-2399)#3486
dmihalcik-virtru wants to merge 1 commit into
mainfrom
DSPX-2399-platform-go-sdk

Conversation

@dmihalcik-virtru
Copy link
Copy Markdown
Member

@dmihalcik-virtru dmihalcik-virtru commented May 18, 2026

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.

  • Adds MLKem768Key constant and IsMLKEMKeyType helper to lib/ocrypto
  • Adds lib/ocrypto/mlkem.go with MLKEMPublicKeyFromPEM for parsing PEM-encoded ML-KEM-768 encapsulation keys
  • Adds wrapKeyWithMLKEM in sdk/experimental/tdf/key_access.go dispatched from wrapKeyWithPublicKey when the KAS advertises mlkem:768
  • Wire format: KAO keyType = "wrapped", wrappedKey = base64(ml_kem_ciphertext [1088 bytes] || aes_gcm_wrapped_dek), no ephemeralPublicKey
  • Extends formatAlgorithm and convertAlgEnum2Simple in keysplit/attributes.go for the pending KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 13 proto enum (platform-proto cell)

Dependencies

  • platform-proto: adds KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 13 and Algorithm_ALGORITHM_ML_KEM_768 = 6 to objects.proto; once merged the local integer constants in keysplit/attributes.go should be replaced with generated names
  • platform-service: implements KAS-side ML-KEM-768 decapsulation and key registration

Test plan

  • go test github.com/opentdf/platform/lib/ocrypto – passes
  • go test github.com/opentdf/platform/sdk/experimental/tdf/... – passes including new TestWrapKeyWithMLKEM round-trip test
  • go vet ./... and go build ./... – clean
  • Integration: xtest/sdk/go/cli.sh supports mechanism-mlkem case (lives in opentdf/tests, tracked on branch DSPX-2399-tests)
  • End-to-end: xtest/scenarios/mechanism-mlkem.yaml – requires platform-service cell to land

Related

🤖 Generated with Claude Code

…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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8bb05796-9268-4ddb-a931-3339225966f8

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DSPX-2399-platform-go-sdk

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.

@github-actions github-actions Bot added comp:sdk A software development kit, including library, for client applications and inter-service communicati comp:lib:ocrypto labels May 18, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • ML-KEM-768 Integration: Implemented client-side key encapsulation using ML-KEM-768 (NIST FIPS 203) in the Go SDK, enabling post-quantum secure key wrapping.
  • Crypto Library Updates: Added ML-KEM-768 constants and helper functions to lib/ocrypto, including PEM parsing for encapsulation keys.
  • SDK Logic Extensions: Updated key access logic to support ML-KEM-768 dispatching and extended attribute handling to support the new algorithm enum.
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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • lib/fixtures

See the workflow run for details.

Copy link
Copy Markdown
Contributor

@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-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"
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.

medium

In Go, acronyms like ML-KEM should be consistently cased (e.g., MLKEM). This constant should be renamed to MLKEM768Key to match the casing used in the helper function IsMLKEMKeyType and other parts of the PR.

Suggested change
MLKem768Key KeyType = "mlkem:768"
MLKEM768Key KeyType = "mlkem:768"

}

func IsMLKEMKeyType(kt KeyType) bool {
return kt == MLKem768Key
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.

medium

Update this reference to use the consistently cased constant name.

Suggested change
return kt == MLKem768Key
return kt == MLKEM768Key

Comment thread lib/ocrypto/mlkem.go
Comment on lines +10 to +16
// 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"
)
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.

medium

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"
)

Comment thread lib/ocrypto/mlkem.go
Comment on lines +25 to +26
if block.Type != mlkem768PEMType {
return nil, fmt.Errorf("unexpected PEM type %q, expected %q", block.Type, mlkem768PEMType)
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.

medium

Update these lines to use the renamed and exported PEM type constant.

Suggested change
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}
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.

medium

Use the exported constant from the ocrypto package instead of hardcoding the PEM type string.

Suggested change
block := &pem.Block{Type: "ML-KEM-768 PUBLIC KEY", Bytes: pubKeyBytes}
block := &pem.Block{Type: ocrypto.MLKEM768PEMType, Bytes: pubKeyBytes}

Comment on lines +504 to +507
assert.Greater(t, len(payload), ocrypto.MLKem768CiphertextSize, "payload must include ciphertext + wrapped DEK")

ciphertext := payload[:ocrypto.MLKem768CiphertextSize]
wrappedDEK := payload[ocrypto.MLKem768CiphertextSize:]
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.

medium

Update these references to use the renamed MLKEM768CiphertextSize constant.

Suggested change
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)
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.

medium

Update this reference to use the renamed MLKEM768CiphertextSize constant.

Suggested change
assert.Greater(t, len(payload), ocrypto.MLKem768CiphertextSize)
assert.Greater(t, len(payload), ocrypto.MLKEM768CiphertextSize)

@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 177.611751ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 442.464537ms
Throughput 226.01 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.48228169s
Average Latency 443.316162ms
Throughput 112.40 requests/second

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

Labels

comp:lib:ocrypto comp:sdk A software development kit, including library, for client applications and inter-service communicati size/s

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant