Skip to content

feat(platform): add ML-KEM-768 post-quantum key encapsulation (DSPX-2399)#3491

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

feat(platform): add ML-KEM-768 post-quantum key encapsulation (DSPX-2399)#3491
dmihalcik-virtru wants to merge 1 commit into
mainfrom
DSPX-2399-platform-service

Conversation

@dmihalcik-virtru
Copy link
Copy Markdown
Member

Summary

Implements pure ML-KEM-768 post-quantum key encapsulation mechanism support in the platform service.

  • Adds MLKEMKeyPair to lib/ocrypto using Go 1.24+ crypto/mlkem standard library
  • Wire format: wrappedKey = ciphertext[1088B] || AES-256-GCM(DEK) with HKDF-SHA256(shared_secret, SHA-256("TDF")) key derivation; no ephemeralPublicKey field
  • Adds KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 13 to service/policy/objects.proto and regenerates pb.go stubs
  • Implements ML-KEM decapsulation in BasicManager.Decrypt and gates processing in rewrap.go behind preview.mlkem_enabled
  • Enables mlkem_enabled: true in opentdf-dev.yaml for local development and test harness
  • Wires algorithm mappings in sdk/granter.go, sdk/basekey.go, service/kas/key_indexer.go, and sdk/experimental/tdf/keysplit/attributes.go

Jira

https://virtru.atlassian.net/browse/DSPX-2399

Integration tests

xtest/scenarios/mechanism-mlkem.yaml

Test plan

  • lib/ocrypto unit tests pass (go test ./...)
  • sdk unit tests pass (go test ./...)
  • service/kas and service/internal/security tests pass
  • Integration tests via xtest when platform-go-sdk cell is complete

🤖 Generated with Claude Code

…399)

- Add MLKEMKeyPair to lib/ocrypto using Go 1.24+ crypto/mlkem stdlib
- Wire format: wrappedKey = ciphertext[1088B] || AES-256-GCM(DEK)
- Key derivation via HKDF-SHA256 with SHA-256("TDF") salt
- Add KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 13 to objects.proto; regen pb.go
- Implement decapsulation in BasicManager.Decrypt and rewrap.go
- Gate behind preview.mlkem_enabled flag; enabled in opentdf-dev.yaml
- Add algorithm mappings in sdk/granter.go, sdk/basekey.go, key_indexer.go

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Important

Review skipped

Draft detected.

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

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c8fe807f-884a-4a71-a214-3b543ad18a5b

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-service

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.

@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 post-quantum cryptographic support to the platform by implementing the ML-KEM-768 key encapsulation mechanism. The changes encompass the core cryptographic logic, updates to protocol definitions, and integration across the SDK and KAS services to ensure compatibility with the new algorithm. The implementation is safely gated behind a preview flag to allow for controlled rollout and testing.

Highlights

  • ML-KEM-768 Implementation: Added support for the ML-KEM-768 post-quantum key encapsulation mechanism using the Go 1.24+ standard library.
  • Protocol and Service Updates: Updated service/policy/objects.proto to include the new algorithm and wired the necessary mappings across the SDK and KAS services.
  • Feature Gating: Introduced a preview flag mlkem_enabled to gate the new functionality, with it enabled by default in the local development configuration.
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.

Ignored Files
  • Ignored by pattern: protocol/**/* (1)
    • protocol/go/policy/objects.pb.go
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 are on the rise, ML-KEM provides the prize. Wrapped in GCM, the keys are tight, Securing data through the night.

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 github-actions Bot added 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 comp:kas Key Access Server comp:lib:ocrypto size/m labels May 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • service

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 post-quantum cryptography using the ML-KEM-768 algorithm. Key changes include the implementation of ML-KEM key pair generation, PEM-encoded serialization, and a new key wrapping/unwrapping mechanism utilizing HKDF-SHA256 and AES-256-GCM. The functionality is integrated across the SDK and KAS service, with the rewrap logic gated behind a new configuration flag. Review feedback highlights several improvement opportunities: strengthening cryptographic validation by checking PEM block types and AES-GCM ciphertext lengths, optimizing performance by precomputing static salts, and improving efficiency by caching key lookups during request processing.

Comment on lines +149 to +151
if len(data) < nonceSize {
return nil, errors.New("ciphertext too short for nonce")
}
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.

high

The length check for the ciphertext is insufficient. It should ensure the data is long enough to contain both the nonce and the GCM authentication tag (typically 12 + 16 = 28 bytes).

Suggested change
if len(data) < nonceSize {
return nil, errors.New("ciphertext too short for nonce")
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize+gcm.Overhead() {
return nil, errors.New("ciphertext too short")
}

Comment on lines +108 to +111
block, _ := pem.Decode(privateKeyPEM)
if block == nil {
return nil, errors.New("failed to parse ML-KEM-768 PEM private 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

The PEM decoding logic should validate the Type of the PEM block to ensure it matches the expected ML-KEM-768 private key header. This prevents accidental processing of incorrect key types.

Suggested change
block, _ := pem.Decode(privateKeyPEM)
if block == nil {
return nil, errors.New("failed to parse ML-KEM-768 PEM private key")
}
block, _ := pem.Decode(privateKeyPEM)
if block == nil || block.Type != "ML-KEM-768 PRIVATE KEY" {
return nil, errors.New("failed to parse ML-KEM-768 PEM private key")
}

Comment on lines +131 to +136
// mlkemTDFSalt returns the SHA-256("TDF") salt used for HKDF in ML-KEM key wrapping.
func mlkemTDFSalt() []byte {
h := sha256.New()
h.Write([]byte("TDF"))
return h.Sum(nil)
}
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

Computing the SHA-256 hash of the "TDF" string on every call is inefficient as the result is constant. Consider precomputing this salt as a package-level variable.

Suggested change
// mlkemTDFSalt returns the SHA-256("TDF") salt used for HKDF in ML-KEM key wrapping.
func mlkemTDFSalt() []byte {
h := sha256.New()
h.Write([]byte("TDF"))
return h.Sum(nil)
}
var mlkemTDFSaltValue = sha256.Sum256([]byte("TDF"))
// mlkemTDFSalt returns the SHA-256("TDF") salt used for HKDF in ML-KEM key wrapping.
func mlkemTDFSalt() []byte {
return mlkemTDFSaltValue[:]
}

Comment on lines +163 to +166
block, _ := pem.Decode(publicKeyPEM)
if block == nil {
return nil, errors.New("failed to parse ML-KEM-768 PEM 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

The PEM decoding logic should validate the Type of the PEM block to ensure it matches the expected ML-KEM-768 public key header.

Suggested change
block, _ := pem.Decode(publicKeyPEM)
if block == nil {
return nil, errors.New("failed to parse ML-KEM-768 PEM public key")
}
block, _ := pem.Decode(publicKeyPEM)
if block == nil || block.Type != "ML-KEM-768 PUBLIC KEY" {
return nil, errors.New("failed to parse ML-KEM-768 PEM public key")
}

Comment on lines +755 to +756
keyDetails, lookupErr := p.KeyDelegator.FindKeyByID(ctx, kid)
if lookupErr == nil && keyDetails.Algorithm() == ocrypto.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

Performing a FindKeyByID lookup inside the KAO loop can be inefficient if multiple Key Access Objects refer to the same key ID, as it may trigger redundant database or network calls. Consider caching the algorithm lookup results for the duration of the request processing.

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 436.109854ms
Throughput 229.30 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.546868172s
Average Latency 433.853383ms
Throughput 114.82 requests/second

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

Labels

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 size/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant