Skip to content

feat(kas): support ML-KEM client session keys in rewrap - #3814

Open
dmihalcik-virtru wants to merge 2 commits into
mainfrom
DSPX-4221-pq-sessions
Open

feat(kas): support ML-KEM client session keys in rewrap#3814
dmihalcik-virtru wants to merge 2 commits into
mainfrom
DSPX-4221-pq-sessions

Conversation

@dmihalcik-virtru

@dmihalcik-virtru dmihalcik-virtru commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Part of DSPX-4221 (Session Keys should support ML-KEM). rewrap's clientPublicKey — the client-generated ephemeral "session key" used to wrap the response DEK back to the client — only accepted RSA/EC keys. extractSRTBody parsed it with x509.ParsePKIXPublicKey, which doesn't recognize ML-KEM's SPKI OID and rejected it as a parse failure before the request ever reached the (already KEM-aware) wrap dispatch.

Changes

  • service/kas/access/rewrap.go: extractSRTBody now recognizes pure ML-KEM-768/1024 SPKI client public keys via ocrypto.ParseKEMPublicSPKI, mirroring the dispatch order ocrypto.FromPublicPEMWithSalt already uses internally. tdf3Rewrap gates the ML-KEM session-key path on Preview.MLKEMTDFEnabled, matching the existing EC gate (Preview.ECTDFEnabled). The wrap-to-client-key crypto itself needed no changes — ocrypto.FromPublicPEMWithSalt already dispatches ML-KEM/hybrid keys to the KEM encryptor, since it's shared with the KAS-managed-key (mlkem-wrapped KAO) path.
  • sdk/kas_client.go: unwrap() only branched EC vs RSA; added an ML-KEM branch (handleKEMKeyResponse/processKEMResponse) so the Go SDK can decrypt a rewrap response wrapped to an ML-KEM session key. sdk.WithSessionKeyType(ocrypto.MLKEM768Key / MLKEM1024Key) already existed but previously caused a decrypt-time failure since unwrap() would try (and fail) to type-assert the KEM decryptor as RSA.
  • otdfctl: --session-key-algorithm now accepts mlkem:768/mlkem:1024 (the underlying sdk.WithSessionKeyType plumbing already supported these ocrypto.KeyType values, just not the CLI flag mapping); updated docs/man/decrypt.

New audit field: sessionKeyType

A rewrap succeeding doesn't by itself prove which session-key type KAS actually used — nothing observable previously recorded it, so the companion xtest PR could only infer correctness indirectly from a successful decrypt (which would also pass if a client silently fell back to RSA). To make that test load-bearing:

  • service/logger/audit/rewrap.go: RewrapAuditEventParams gains a SessionKeyType field, surfaced as eventMetaData.sessionKeyType on rewrap audit events.
  • service/kas/access/rewrap.go: populates it from asymEncrypt.KeyType() (already computed for the Preview.MLKEMTDFEnabled gate above) when building each KAO's audit event.
  • Updated the existing service/logger/audit unit tests for the new field.

Scope

Deliberately limited to pure ML-KEM (768/1024), matching the ticket title and the existing mechanism-mlkem precedent — hybrid PQ/T session keys (X-Wing, secp+ML-KEM) are out of scope here.

Test plan

go build ./service/... ./sdk/... ./otdfctl/...
go vet ./service/kas/... ./service/logger/... ./sdk/...
go test ./service/kas/... ./service/logger/... ./sdk/...
gofmt -l service/kas/access/rewrap.go service/logger/audit/rewrap.go service/logger/audit/rewrap_test.go service/logger/audit/logger_test.go sdk/kas_client.go otdfctl/cmd/tdf/decrypt.go

All pass, no regressions. Manually verified otdfctl help decrypt now lists mlkem:768/mlkem:1024 for --session-key-algorithm.

Related work

Companion PRs:

Ref: DSPX-4221

Summary by CodeRabbit

  • New Features

    • Added support for ML-KEM-768 and ML-KEM-1024 session-key algorithms in decryption and key rewrapping.
    • Added ML-KEM session-key options and usage examples to CLI documentation.
    • Added support for processing permitted wrapped keys and preserving obligation metadata.
  • Security & Auditing

    • ML-KEM rewrapping is restricted when preview functionality is disabled.
    • Rewrap audit events now record the session-key type.

@dmihalcik-virtru
dmihalcik-virtru requested review from a team as code owners July 31, 2026 21:35
@github-actions github-actions Bot added comp:sdk A software development kit, including library, for client applications and inter-service communicati comp:kas Key Access Server size/m labels Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d66cab13-bd94-485c-9abe-8581098b1fb5

📥 Commits

Reviewing files that changed from the base of the PR and between e3725bf and 5f9a4de.

📒 Files selected for processing (8)
  • otdfctl/cmd/tdf/decrypt.go
  • otdfctl/docs/man/decrypt/_index.md
  • sdk/kas_client.go
  • service/kas/access/rewrap.go
  • service/logger/audit/logger_test.go
  • service/logger/audit/rewrap.go
  • service/logger/audit/rewrap_test.go
  • spec/DSPX-4221.md

📝 Walkthrough

Walkthrough

Decryption now supports ML-KEM-768 and ML-KEM-1024 session keys. The KAS client processes ML-KEM responses. The rewrap service recognizes ML-KEM keys, enforces preview gating, and records the session key type in audit events. Documentation and a draft specification were added.

Changes

ML-KEM session-key support

Layer / File(s) Summary
Client selection and specification
otdfctl/cmd/tdf/decrypt.go, otdfctl/docs/man/decrypt/_index.md, spec/DSPX-4221.md
The CLI accepts and documents ML-KEM-768 and ML-KEM-1024 session-key algorithms. The DSPX-4221 specification defines session-key contracts, preview gating, scope, and acceptance criteria.
KAS ML-KEM unwrap processing
sdk/kas_client.go
KAS unwrap dispatches ML-KEM responses to a KEM handler. The handler decrypts permitted wrapped keys, preserves obligations, and records decryption or denial errors.
Server rewrap and audit metadata
service/kas/access/rewrap.go, service/logger/audit/rewrap.go, service/logger/audit/*_test.go
The rewrap service recognizes ML-KEM SPKI keys and rejects ML-KEM rewraps when preview support is disabled. Rewrap audit events include sessionKeyType, with updated immediate and deferred event tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant KASClient
  participant RewrapService
  participant AuditLogger
  CLI->>KASClient: Select ML-KEM session key
  KASClient->>RewrapService: Request rewrap
  RewrapService->>RewrapService: Validate ML-KEM preview support
  RewrapService-->>KASClient: Return KAO results
  RewrapService->>AuditLogger: Record sessionKeyType
  KASClient->>KASClient: Decrypt permitted wrapped keys
  KASClient-->>CLI: Return decrypted keys and obligations
Loading

Possibly related PRs

Suggested labels: docs

Suggested reviewers: strantalis

Poem

A rabbit checks each ML-KEM key,
While KAS unwraps what it can see.
Audit fields record the type,
Preview gates keep flows precise.
The CLI documents every leap.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: support for ML-KEM client session keys in KAS rewrap flows.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DSPX-4221-pq-sessions

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.

@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: 3

🤖 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 `@spec/DSPX-4221.md`:
- Line 21: Update the wording in the ML-KEM session encryption requirement to
use the hyphenated compound adjective “client-generated” before “key pair.”
- Line 20: Insert one blank line immediately after each Markdown section heading
in spec/DSPX-4221.md, including the headings at lines 20, 23, 26, 29, 32, 35,
and 38, so the document satisfies MD022.
- Around line 20-40: Complete the DSPX-4221 specification by replacing all
template placeholders with concrete requirements covering mlkem:768 and
mlkem:1024 support, pure versus hybrid scope, preview gating, rewrap and
decryption behavior, contracts, constraints, out-of-scope items, and testable
acceptance criteria.
🪄 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 Plus

Run ID: a027b735-ad03-4660-934b-4c85249d7e80

📥 Commits

Reviewing files that changed from the base of the PR and between 4e140ac and 33e7fce.

📒 Files selected for processing (5)
  • otdfctl/cmd/tdf/decrypt.go
  • otdfctl/docs/man/decrypt/_index.md
  • sdk/kas_client.go
  • service/kas/access/rewrap.go
  • spec/DSPX-4221.md

Comment thread spec/DSPX-4221.md
Comment thread spec/DSPX-4221.md Outdated
Comment thread spec/DSPX-4221.md Outdated
@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 248.368523ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 420.215968ms
Throughput 237.97 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 50.858163359s
Average Latency 506.324883ms
Throughput 98.31 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 218.79516ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 433.252595ms
Throughput 230.81 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 54.130352027s
Average Latency 539.671697ms
Throughput 92.37 requests/second

@github-actions

github-actions Bot commented Aug 1, 2026

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 201.522414ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 440.171793ms
Throughput 227.18 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 52.510271245s
Average Latency 523.636634ms
Throughput 95.22 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: 1

🤖 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 `@spec/DSPX-4221.md`:
- Line 45: Update the Go SDK contract entry for sdk.WithSessionKeyType to remove
the invalid bitwise OR expression on string-based ocrypto.KeyType values. List
separate calls for MLKEM768Key and MLKEM1024Key if either option is supported,
or describe accepting both options in prose outside code formatting.
🪄 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 Plus

Run ID: 0ae928d2-8118-4f28-a031-389390aef2db

📥 Commits

Reviewing files that changed from the base of the PR and between 33e7fce and e3725bf.

📒 Files selected for processing (8)
  • otdfctl/cmd/tdf/decrypt.go
  • otdfctl/docs/man/decrypt/_index.md
  • sdk/kas_client.go
  • service/kas/access/rewrap.go
  • service/logger/audit/logger_test.go
  • service/logger/audit/rewrap.go
  • service/logger/audit/rewrap_test.go
  • spec/DSPX-4221.md

Comment thread spec/DSPX-4221.md Outdated
Address CodeRabbit review comments on spec/DSPX-4221.md:
- Fix invalid Go syntax in the SDK contract example (ocrypto.KeyType is a
  string type; bitwise-OR between two enum values doesn't compile).
- Remove the "and vice versa" independence claim, which overstated test
  coverage (only RSA-wrapped-TDF + ML-KEM-session-key is tested).

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

github-actions Bot commented Aug 1, 2026

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.175262ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 439.876583ms
Throughput 227.34 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 49.209714086s
Average Latency 490.80922ms
Throughput 101.61 requests/second

@github-actions

github-actions Bot commented Aug 1, 2026

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.

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 end-to-end support for pure ML-KEM (768/1024) client session keys in the KAS rewrap flow, so clients can request a PQ session key for wrapping the response DEK and successfully decrypt the rewrap response.

Changes:

  • KAS rewrap request parsing now recognizes ML-KEM SPKI client public keys and gates ML-KEM session-key rewrap behind Preview.MLKEMTDFEnabled; rewrap audit events now record sessionKeyType.
  • Go SDK KASClient.unwrap() adds an ML-KEM branch to decapsulate/decrypt rewrap responses when the session key type is ML-KEM.
  • otdfctl decrypt --session-key-algorithm and docs now accept/document mlkem:768 and mlkem:1024; added spec/ticket write-up.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
spec/DSPX-4221.md Spec/ticket documentation for ML-KEM session-key support and acceptance criteria.
service/logger/audit/rewrap.go Adds sessionKeyType to rewrap audit event metadata.
service/logger/audit/rewrap_test.go Updates audit event unit test expectations to include sessionKeyType.
service/logger/audit/logger_test.go Updates logger JSON assertions to include sessionKeyType in rewrap audit logs.
service/kas/access/rewrap.go Accepts ML-KEM SPKI in extractSRTBody, gates ML-KEM session-key rewrap via preview flag, and populates audit SessionKeyType.
sdk/kas_client.go Adds ML-KEM unwrap/decrypt handling for rewrap responses.
otdfctl/docs/man/decrypt/_index.md Documents mlkem:768/mlkem:1024 for --session-key-algorithm and adds an example.
otdfctl/cmd/tdf/decrypt.go Maps mlkem:768/mlkem:1024 CLI values to ocrypto key types.
Suppressed comments (2)

service/kas/access/rewrap.go:1045

  • The new preview gate for ML-KEM session-key rewrap lacks test coverage. There are tests for other rewrap behavior, but none that assert an ML-KEM clientPublicKey is rejected when Preview.MLKEMTDFEnabled is false and accepted when true.
	if ocrypto.IsMLKEMKeyType(asymEncrypt.KeyType()) && !p.Preview.MLKEMTDFEnabled {
		p.Logger.ErrorContext(ctx, "ml-kem session key rewrap not enabled")
		failAllKaos(requests, results, err400("invalid request"))
		return "", results, nil
	}

sdk/kas_client.go:318

  • Error message is misleading: this failure is from k.sessionKey.PrivateKeyInPemFormat(), not ocrypto.PrivateKeyInPemFormat. Consider aligning wording with the EC path ("failed to get private key").
	clientPrivateKey, err := k.sessionKey.PrivateKeyInPemFormat()
	if err != nil {
		return nil, fmt.Errorf("ocrypto.PrivateKeyInPemFormat failed: %w", err)
	}

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

Comment on lines +450 to +454
// Pure ML-KEM client session keys are SPKI-wrapped under the NIST ML-KEM
// OIDs (FIPS 203), which x509.ParsePKIXPublicKey does not recognize and
// would otherwise reject as a parse failure. Accept them here; whether
// ML-KEM rewrap is actually enabled is checked later in tdf3Rewrap.
if oid, _, kemErr := ocrypto.ParseKEMPublicSPKI(block.Bytes); kemErr == nil &&
Comment thread sdk/kas_client.go
Comment on lines +186 to +190
switch {
case ocrypto.IsECKeyType(k.sessionKey.GetKeyType()):
return k.handleECKeyResponse(response)
case ocrypto.IsMLKEMKeyType(k.sessionKey.GetKeyType()):
return k.handleKEMKeyResponse(response)
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: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.

2 participants