Skip to content

implement a subset of "Modern Algorithms in the Web Cryptography API"#6403

Open
panva wants to merge 1 commit intocloudflare:mainfrom
panva:webcrypto
Open

implement a subset of "Modern Algorithms in the Web Cryptography API"#6403
panva wants to merge 1 commit intocloudflare:mainfrom
panva:webcrypto

Conversation

@panva
Copy link
Copy Markdown
Contributor

@panva panva commented Mar 24, 2026

Refs: #3642

From https://wicg.github.io/webcrypto-modern-algos/ this implements:

  • crypto.subtle.getPublicKey()
  • ML-KEM (768, 1024)
  • ML-DSA (44, 65, 87)
  • SHA3 (256, 384, 512)
  • ChaCha20-Poly1305
  • cSHAKE (128, 256)
  • TurboSHAKE (128, 256)
  • raw-* format aliases for old algorithms

Tested using existing WPTs as well as using the https://github.com/panva/hpke and https://github.com/panva/jose test suites.

For SHA3, TurboSHAKE, and cSHAKE I added the sha3 crate, everything else goes through BoringSSL.

With the exception of TurboSHAKE (which has a pending PR open) these are available in Node.js too.

Co-authored-by: GitHub Copilot copilot@github.com

@panva panva requested review from a team as code owners March 24, 2026 20:51
@anonrig anonrig self-assigned this Mar 24, 2026
@anonrig
Copy link
Copy Markdown
Member

anonrig commented Mar 24, 2026

/bigbonk review this pull-request. make sure everything aligns with Node.js. roast this pr as much as you can.

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Substantial PR implementing a subset of the "Modern Algorithms in the Web Cryptography API" spec. The implementation is well-structured with good use of C++ templates for the ML-KEM/ML-DSA parameter dispatch. Key findings:

  • Compat flag gating: These new algorithms and APIs (ML-KEM, ML-DSA, ChaCha20-Poly1305, SHA-3, encapsulate/decapsulate/getPublicKey) are unconditionally available with no compat flag or autogate. Given the project's strong backward compatibility commitment and the fact that these APIs add new methods to SubtleCrypto plus new key usage strings, this should be gated behind a compat flag.
  • Dead code allocation in mldsa.c++:importSpki that unnecessarily allocates a buffer that is immediately shadowed.
  • raw-private format is accepted in importKeySync but never handled by any algorithm — potential developer confusion.
  • DigestStream gap: SHA-3/TurboSHAKE/cSHAKE are not supported in the streaming DigestStream — only subtle.digest(). May be acceptable as a known limitation.
  • outputLength for cSHAKE allows 0: The cSHAKE path accepts outputLength >= 0 (allowing 0) while TurboSHAKE requires > 0. Deliberate or inconsistency?
  • Minor style nits on variable naming and an unnecessary _kjc suffix.

This review was generated by an AI assistant and may contain inaccuracies.

@anonrig anonrig requested a review from jasnell March 24, 2026 20:57
@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 24, 2026

Review posted successfully. Here's a summary of what I flagged:

Key findings posted as inline comments:

  1. Compat flag gating (crypto.c++ L131-137): All new algorithms and SubtleCrypto methods are unconditionally available. This should be gated behind a compat flag per workerd conventions.

  2. Dead code (mldsa.c++ L419-420): publicKeyBytes is allocated then immediately shadowed by rawPkBytes. Suggested removal with a code suggestion.

  3. raw-private format (crypto.c++ L704): Accepted in importKeySync but never handled by any algorithm — will cause confusing late errors.

  4. cSHAKE outputLength=0 (crypto.c++ L441): cSHAKE allows outputLength >= 0 while TurboSHAKE requires > 0 — potential inconsistency.

  5. ChaCha20-Poly1305 format asymmetry (chacha20.c++ L51): Only supports raw-secret, not raw, unlike AES/HMAC.

  6. getPublicKey fragile dispatch (crypto.c++ L843-855): Algorithm-name if/else chain for allowed usages should be a virtual method on Impl.

  7. ML-KEM/ML-DSA key memory footprint (mlkem.c++ L96-100): PQC key structs stored inline in kj::Maybe are several KB each.

  8. JWK pub/priv fields (crypto.h L547): New fields on JsonWebKey affect all algorithms, with no documentation comment.

  9. Rust defense-in-depth (lib.rs L57): TurboSHAKE domain separation should have debug assertion on Rust side.

  10. Secret key material double-copy (crypto.c++ L791): Shared secret is copied during encapsulateKey — consider zeroing the original.

github run

@panva
Copy link
Copy Markdown
Contributor Author

panva commented Mar 24, 2026

@anonrig will update the WPTs separately and then i'll rebase this on top to just get the diff in WPTs related to these changes.

@anonrig
Copy link
Copy Markdown
Member

anonrig commented Mar 24, 2026

@anonrig will update the WPTs separately and then i'll rebase this on top to just get the diff in WPTs related to these changes.

Opened #6405

@panva panva marked this pull request as draft March 24, 2026 22:09
@codspeed-hq

This comment has been minimized.

@panva panva marked this pull request as ready for review March 25, 2026 21:32
@anonrig
Copy link
Copy Markdown
Member

anonrig commented Mar 26, 2026

/bigbonk roast this pr

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

  • ML-KEM/ML-DSA seed not wiped on destroy: Private key seeds stored as plain kj::Array<kj::byte> instead of ZeroOnFree — secret key material left in memory after key destruction.
  • getPublicKey spec ordering: Algorithm-support check should precede the key-type check per the spec.
  • Missing jsgGetMemoryInfo in ML-KEM/ML-DSA: These key classes track sizeof(Self) but don't report heap-allocated seed/publicKeyBytes.
  • XOF outputLength has no upper bound: User-controlled outputLength on TurboSHAKE/cSHAKE could allocate unbounded memory.
  • Nit: _kjc suffix on string literal in ChaCha20 code.
  • TS_OVERRIDE gap: New methods not included in the JSG_TS_OVERRIDE block.
  • Nit: Dead spki2/algId2/cbb2 variable naming in ML-DSA exportSpki.

This review was generated by an AI assistant and may contain inaccuracies.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 26, 2026

Review posted successfully. Here's a summary of what I flagged across 8 inline comments:

  1. ML-KEM/ML-DSA seed not wiped on destroy (mlkem.c++:442): Private key seeds stored as plain kj::Array<kj::byte> instead of ZeroOnFree. Every other secret key class uses ZeroOnFree to wipe key material on destruction. This applies to both ML-KEM and ML-DSA.

  2. getPublicKey spec ordering (crypto.c++:838): Per spec §3.2.5, the algorithm-support check should come before the key-type check. Current code checks type first, which produces wrong error types for some edge cases.

  3. Missing jsgGetMemoryInfo (mlkem.c++:209): ML-KEM and ML-DSA key classes don't track heap-allocated publicKeyBytes and seed in memory info, so V8 heap snapshots underreport memory.

  4. _kjc suffix (chacha20.c++:139): Non-standard string literal suffix, inconsistent with other key classes. Suggested removal.

  5. Dead copy-paste variable names (mldsa.c++:601): cbb2/spki2/algId2 — remnants from copy-paste, ML-KEM version uses clean names.

  6. TS_OVERRIDE gap (crypto.h:716): New methods (encapsulateKey, getPublicKey, etc.) not included in the explicit TypeScript override block, which may produce suboptimal auto-generated types.

  7. ML-DSA getContext semantics (mldsa.c++:156): Suggested adding a comment clarifying the nullptr/empty-context equivalence.

  8. XOF output length unbounded (crypto.c++:415): User-controlled outputLength for TurboSHAKE/cSHAKE has no upper bound, allowing allocation of hundreds of MB.

github run

Co-authored-by: GitHub Copilot <copilot@github.com>
@panva panva requested a review from a team as a code owner March 26, 2026 21:44
@panva panva requested a review from dario-piotrowicz March 26, 2026 21:44
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 1.89643% with 1345 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.00%. Comparing base (9039529) to head (6a5af36).
⚠️ Report is 22 commits behind head on main.

Files with missing lines Patch % Lines
src/workerd/api/crypto/mldsa.c++ 0.00% 517 Missing ⚠️
src/workerd/api/crypto/mlkem.c++ 0.00% 398 Missing ⚠️
src/workerd/api/crypto/crypto.c++ 8.18% 147 Missing and 10 partials ⚠️
src/workerd/api/crypto/chacha20.c++ 0.00% 156 Missing ⚠️
src/rust/sha3/lib.rs 0.00% 57 Missing ⚠️
src/workerd/api/crypto/keys.c++ 0.00% 22 Missing ⚠️
src/workerd/api/crypto/rsa.c++ 0.00% 12 Missing ⚠️
src/workerd/api/crypto/ec.c++ 0.00% 7 Missing and 2 partials ⚠️
src/workerd/api/crypto/impl.h 0.00% 9 Missing ⚠️
src/workerd/api/crypto/aes.c++ 0.00% 2 Missing and 2 partials ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6403      +/-   ##
==========================================
- Coverage   70.71%   70.00%   -0.72%     
==========================================
  Files         427      431       +4     
  Lines      117262   119074    +1812     
  Branches    18903    19193     +290     
==========================================
+ Hits        82918    83353     +435     
- Misses      23093    24455    +1362     
- Partials    11251    11266      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

}
static constexpr CryptoKeyUsageSet decapsulateBits() {
return 1 << 11;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit... to address later... this pattern of how these are defined is a bit brittle.

@@ -0,0 +1,796 @@
#include "impl.h"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: All new files need to include the copyright header. We're not always consistent at remembering this ourselves but we're trying to get better at it ;-)

namespace {

// Traits structs for each ML-DSA parameter set.
struct MlDsa44Params {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: for later, it probably makes sense to get these into ncrypto.

Copy link
Copy Markdown
Collaborator

@jasnell jasnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm going to ask someone from our crypto team to take a look as well.

@jasnell
Copy link
Copy Markdown
Collaborator

jasnell commented Mar 27, 2026

@bwesterb ... any chance we can get a crypto/security review on this?

@bwesterb
Copy link
Copy Markdown
Member

bwesterb commented Mar 27, 2026

Happy to. This is a lot at once: better to split into separate PRs for each addition. Let's start with ML-DSA & ML-KEM.

@panva
Copy link
Copy Markdown
Contributor Author

panva commented Mar 27, 2026

Happy to. This is a lot at once: better to split into separate PRs for each addition. Let's start with ML-DSA & ML-KEM.

In that case I'll hand it over to you folk. You're better suited to push this across the line for workerd than I am. I don't think this is a lot to take in given it's mostly wiring. And I would hate if by the virtue of splitting this things get missed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants