Skip to content

webcrypto: add SHA-3 digests and SubtleCrypto.supports()#29222

Open
robobun wants to merge 14 commits intomainfrom
farm/0e1581e7/webcrypto-sha3-and-supports
Open

webcrypto: add SHA-3 digests and SubtleCrypto.supports()#29222
robobun wants to merge 14 commits intomainfrom
farm/0e1581e7/webcrypto-sha3-and-supports

Conversation

@robobun
Copy link
Copy Markdown
Collaborator

@robobun robobun commented Apr 12, 2026

First slice of the WICG "Modern Algorithms in the Web Cryptography API" specification, as requested in #29218.

What

Adds two things to Web Crypto:

  1. SHA3-256 / SHA3-384 / SHA3-512 digests — available through crypto.subtle.digest. Output matches the FIPS 202 test vectors for the empty string and for "abc":

    await crypto.subtle.digest("SHA3-256", new TextEncoder().encode("abc"));
    // -> 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532
  2. SubtleCrypto.supports(operation, algorithm) — a synchronous feature detection method, defined by the WICG spec as a static on the SubtleCrypto interface object rather than an instance method. Returns a boolean. Never throws for well-formed argument counts; malformed input and unknown algorithms/operations return false.

    SubtleCrypto.supports("digest", "SHA3-256");           // true
    SubtleCrypto.supports("digest", "MD5");                // false
    SubtleCrypto.supports("encapsulateKey", "ML-KEM-768"); // false (coming later)

KEM operations (encapsulateKey/encapsulateBits/decapsulateKey/decapsulateBits) and getPublicKey return false from supports() for now, so callers can start shipping progressive-enhancement paths today. They will flip to true when those algorithms land in a follow-up PR.

Why first

The WICG spec is large — ML-KEM, ML-DSA, ChaCha20-Poly1305, SHA-3, SHAKE, cSHAKE, TurboSHAKE, plus supports() and getPublicKey(). Landing it as one PR would be hard to review. SHA-3 and supports() are a small, self-contained starting point: SHA-3 has no new method surface (it rides on the existing digest operation), and supports() is what lets callers progressively adopt every later algorithm without feature-detecting by try/catch. Later PRs against #29218 will add the rest.

How

  • BoringSSL internal BORINGSSL_keccak_* helpers only expose SHA3-256 and SHA3-512 — SHA3-384 is missing — so I added a small self-contained Keccak-f[1600] permutation and a fixed-output sponge to CryptoDigest.cpp. The same state machine can be extended with the XOF padding byte (0x1f) when SHAKE/cSHAKE/TurboSHAKE land.
  • New SHA3_{256,384,512} entries in CryptoAlgorithmIdentifier, CryptoAlgorithm{SHA3_256,SHA3_384,SHA3_512}.{h,cpp} that mirror the existing SHA-256 algorithm class, and registry wiring in CryptoAlgorithmRegistryOpenSSL.cpp.
  • SubtleCrypto::supports is installed as a static on the JSSubtleCryptoDOMConstructor in initializeProperties() (not on the prototype hashtable), with attribute 0 so its property descriptor is {Writable: true, Enumerable: true, Configurable: true} — matching WebIDL § 3.7.7 "define the operations" step 4. The C++ implementation is a static method that doesn't touch any instance state; the host function dispatches directly without going through IDLOperation<JSSubtleCrypto>::call.
  • The implementation mirrors each method's real dispatch so supports() never reports true for something the underlying method would reject: wrapKey/unwrapKey retry the Encrypt/Decrypt fallback the real methods use; exportKey runs the algorithm through isSupportedExportKey() (not just the ImportKey normalization path); getPublicKey and the KEM ops all return false until their algorithms land.
  • SHA-3 is deliberately gated as a hash sub-algorithm for HMAC / RSA-PSS / RSA-OAEP / ECDSA. toHashIdentifier() returns NotSupportedError for SHA3 identifiers so those paths can't build broken CryptoKey instances that would later crash in OpenSSLUtilities::digestAlgorithm, CryptoKeyHMAC::getKeyLengthFromHash, or the structured-clone tag table. SHA-3 as a top-level digest still works. The gate lifts in a follow-up PR that wires the missing bits.
  • Exhaustive switches on CryptoAlgorithmIdentifier (in AsymmetricKeyValue.cpp and SerializedScriptValue.cpp) get new cases for SHA3. Since no CryptoKey can be built with a SHA3 algorithm today, the serialization path is ASSERT_NOT_REACHED for now.
  • test/regression/issue/29218.test.ts is added to test/no-validate-exceptions.txt under the # normalizeCryptoAlgorithmParameters section, matching every other webcrypto regression test, so the exception-swallowing paths in supports() don't trip BUN_JSC_validateExceptionChecks=1 on ASAN CI.

Tests

test/regression/issue/29218.test.ts covers:

  • FIPS 202 digest vectors (empty string + "abc") for all three SHA-3 variants, plus a multi-block vector cross-checked against node:crypto
  • Dictionary-form and case-insensitive algorithm identifiers
  • SHA3-224 (defined by FIPS but not in the WICG spec) is rejected
  • SHA-3 is rejected as a hash sub-algorithm for HMAC importKey/generateKey, RSA-PSS generateKey, and ECDSA sign() (using a real P-256 key pair so the rejection lives in the sign-path normalization, which is where ECDSA's hash parameter is resolved)
  • SubtleCrypto.supports() covers classic algorithms, SHA-3 digests, dictionary input, unknown algorithms, unknown operations, KEM operations, getPublicKey on symmetric and asymmetric algorithms, wrap/unwrap fallback, exportKey vs. isSupportedExportKey, malformed input, and the required-argument-count TypeError
  • End-to-end sanity check that every supports() == true pair actually succeeds when the underlying method is called, and that every supports() == false pair actually rejects

34 tests / 114 expect() calls, all passing.

Refs #29218. This PR only lands the first slice; ML-KEM / ML-DSA / ChaCha20-Poly1305 / SHAKE / cSHAKE / TurboSHAKE / getPublicKey() will follow in separate PRs against the same issue.

Implements the first slice of the WICG "Modern Algorithms in the Web
Cryptography API" specification
(https://wicg.github.io/webcrypto-modern-algos/):

- SHA3-256, SHA3-384 and SHA3-512 as digest algorithms on
  `crypto.subtle.digest`. Output matches the FIPS 202 test vectors.
  Implementation is a small self-contained Keccak-f[1600] sponge in
  CryptoDigest.cpp, which can later be extended with SHAKE/cSHAKE/
  TurboSHAKE when those land.

- `crypto.subtle.supports(operation, algorithm)`, a synchronous feature
  detection method that reports whether a (operation, algorithm) pair
  is supported. Unknown operations and algorithms, and malformed
  algorithm parameter dictionaries, all return false rather than
  throwing — matching the WICG spec's progressive-enhancement intent.
  KEM operations (encapsulateKey/encapsulateBits/decapsulateKey/
  decapsulateBits) return false for now and will flip to true when
  ML-KEM lands in a follow-up.

Refs #29218
@robobun
Copy link
Copy Markdown
Collaborator Author

robobun commented Apr 12, 2026

Updated 9:40 PM PT - Apr 12th, 2026

@robobun, your commit bcad1c2 has 3 failures in Build #45397 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29222

That installs a local version of the PR into your bun-29222 executable, so you can run:

bun-29222 --bun

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds SHA‑3 (SHA3‑256/384/512) support to Bun's WebCrypto: new algorithm identifiers and digest enum values, a Keccak‑based SHA‑3 digest backend, algorithm classes and registration, adjustments to key/serialization handling for SHA‑3, a SubtleCrypto.supports(operation, algorithm) feature‑detection API, and tests validating digest outputs and supports semantics.

Changes

Cohort / File(s) Summary
Algorithm Identifiers & Digest Enum
src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h, src/bun.js/bindings/webcrypto/CryptoDigest.h
Added SHA3_256, SHA3_384, SHA3_512 enum entries to algorithm identifier and digest algorithm lists.
SHA‑3 Algorithm Headers
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_256.h, src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_384.h, src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_512.h
Declared CryptoAlgorithmSHA3_* classes with static name/identifier, create(), identifier(), and digest() overrides.
SHA‑3 Algorithm Implementations
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_256.cpp, src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_384.cpp, src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_512.cpp
Implemented digest logic for each SHA‑3 variant; synchronous path for small inputs and asynchronous work-queue path for large inputs; posts results to ScriptExecutionContext and invokes callbacks.
Digest Backend (Keccak / SHA‑3)
src/bun.js/bindings/webcrypto/CryptoDigest.cpp
Added Keccak‑f[1600] + SHA‑3 sponge implementation (Sha3Context), adapter functions for SHA3 sizes, and wired CryptoDigest::create() to return SHA‑3 contexts.
Algorithm Registration (OpenSSL registry)
src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp
Included and registered CryptoAlgorithmSHA3_* implementations in platform algorithm registration.
Key & Serialization Handling
src/bun.js/bindings/AsymmetricKeyValue.cpp, src/bun.js/bindings/webcore/SerializedScriptValue.cpp
Treated SHA‑3 identifiers as non‑key‑bearing (key = nullptr) and added asserts to prevent SHA‑3 identifiers from being serialized as CryptoKey algorithm identifiers.
SubtleCrypto.supports API & Normalization
src/bun.js/bindings/webcrypto/SubtleCrypto.h, src/bun.js/bindings/webcrypto/SubtleCrypto.cpp, src/bun.js/bindings/webcrypto/SubtleCrypto.idl, src/bun.js/bindings/webcrypto/JSSubtleCrypto.cpp
Added static SubtleCrypto.supports(operation, algorithm) (IDL and host binding) and implementation; extended algorithm normalization to accept SHA‑3 for digest, reject SHA‑3 for importKey, and handle export/wrap fallback logic.
Tests
test/regression/issue/29218.test.ts
Added regression tests for SHA‑3 digests (NIST vectors, multi-block, lengths, deterministic), algorithm-name parsing and rejection cases, and comprehensive SubtleCrypto.supports() semantics and edge cases.
Bindings Glue / Minor Updates
src/bun.js/bindings/AsymmetricKeyValue.cpp, src/bun.js/bindings/webcore/SerializedScriptValue.cpp, src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp
Minor updates to recognize/register new SHA‑3 identifiers across key handling, serialization, and algorithm registry.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding SHA-3 digest support and a SubtleCrypto.supports() method.
Description check ✅ Passed The pull request description comprehensively covers all required template sections with detailed explanations of changes, implementation rationale, testing, and future work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

The spec author (@panva) explicitly recommended SHA-3 + supports() as the starting slice in #29218

I made no such statement

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 50-51: The comment in the test describing SHA-3 rates is
incorrect: it states "largest SHA-3 rate (72 bytes for SHA3-512)" but SHA3-512
actually has the smallest rate (72 bytes); update the comment text in the
test/regression/issue/29218.test.ts test block (the multi-block absorption
comment near the SHA3-512 test) to say "smallest rate (72 bytes for SHA3-512)"
or otherwise correct the wording so it accurately reflects that SHA3-512 uses
the smallest sponge rate.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c4d20dff-7a29-4887-8eac-3d38f41fa628

📥 Commits

Reviewing files that changed from the base of the PR and between 7c75a87 and 04739cc.

📒 Files selected for processing (17)
  • src/bun.js/bindings/AsymmetricKeyValue.cpp
  • src/bun.js/bindings/webcore/SerializedScriptValue.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_256.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_256.h
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_384.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_384.h
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_512.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_512.h
  • src/bun.js/bindings/webcrypto/CryptoDigest.cpp
  • src/bun.js/bindings/webcrypto/CryptoDigest.h
  • src/bun.js/bindings/webcrypto/JSSubtleCrypto.cpp
  • src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
  • src/bun.js/bindings/webcrypto/SubtleCrypto.h
  • src/bun.js/bindings/webcrypto/SubtleCrypto.idl
  • test/regression/issue/29218.test.ts

@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

@robobun use the available WPTs

robobun and others added 2 commits April 12, 2026 12:39
…cKey

Addresses review feedback on #29222:

- wrapKey/unwrapKey now mirror the two-step normalize-and-fallback
  dispatch the real methods perform. supports("wrapKey", ...) reports
  true for AES-GCM/CBC/CTR/CFB-8 and RSA-OAEP (via the Encrypt
  fallback), matching what a subsequent wrapKey() call would accept.
  Before, only AES-KW was reported.

- exportKey no longer proxies through ImportKey normalization. It
  resolves the algorithm name to an identifier and then runs it
  through isSupportedExportKey(), the same gate the real exportKey()
  uses. HKDF/PBKDF2 and hash-only algorithms now correctly report
  false rather than returning a false positive that the underlying
  method would reject at runtime.

- getPublicKey returns false for every algorithm. The method is not
  implemented yet (it lands with ML-KEM/ML-DSA in a later slice) and
  it is conceptually asymmetric-only, so symmetric algorithms must
  never report true.

Regression test coverage expands to 33 tests / 101 expects and now
cross-checks multi-block SHA-3 output against node:crypto (OpenSSL),
exercises the wrap/unwrap fallback on AES-GCM/CBC/CTR and RSA-OAEP,
the exportKey HKDF/PBKDF2 rejection, the getPublicKey negative cases,
and an end-to-end sanity check that every supports()==true pair
actually succeeds in the underlying method.

Refs #29218
@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

@robobun SubtleCrypto.supports is a static method on the SubtleCrypto global, not a prototype method.

robobun and others added 2 commits April 12, 2026 13:04
Two corrections on top of c1f35b0:

1. Move supports() to the interface object.

   The WICG "Modern Algorithms" spec defines supports() as a static
   method on the SubtleCrypto constructor, not an instance method on the
   prototype. It is now exposed as `SubtleCrypto.supports(op, alg)`
   rather than `crypto.subtle.supports(...)`. Implementation moves the
   hashtable entry from JSSubtleCryptoPrototypeTableValues to a
   JSFunction installed in JSSubtleCryptoDOMConstructor::
   initializeProperties(), rewrites the host function to not require a
   `this` cast, and marks SubtleCrypto::supports() as a C++ static.

2. Reject SHA-3 as a hash sub-algorithm for HMAC / RSA / ECDSA.

   Accepting SHA-3 as a top-level digest operation in normalize made
   toHashIdentifier() start resolving "SHA3-256" etc. successfully,
   which let HMAC / RSA-PSS / RSA-OAEP / ECDSA importKey and
   generateKey build CryptoKey instances with m_hash = SHA3_*. Those
   keys then:
     - crash sign()/verify() with a cryptic OperationError because
       OpenSSLUtilities::digestAlgorithm() has no SHA-3 branch;
     - hit ASSERT_NOT_REACHED() in CryptoKeyHMAC::getKeyLengthFromHash();
     - crash the process via RELEASE_ASSERT_NOT_REACHED() in
       SerializedScriptValue when structured-cloned or postMessage'd.

   toHashIdentifier() now returns NotSupportedError for SHA-3
   identifiers. SHA-3 as a top-level digest still works; only the
   hash-subalgorithm slot is gated. The gate will lift in a follow-up
   PR that wires SHA-3 into digestAlgorithm(), getKeyLengthFromHash(),
   and the structured-clone tag table.

Regression test grows to 34 tests / 113 expects: new test asserts that
HMAC / RSA-PSS / HMAC-generateKey reject with NotSupportedError for
each SHA-3 variant, and all existing supports() cases flip from
`crypto.subtle.supports` to `SubtleCrypto.supports`.

Refs #29218
@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

@robobun start work on adding a WPT suite runner here, if not a generic one then one for WebCrypto WPTs specifically, there's no way we can confirm these behaviours are correct without running the shared WPT test suite.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 119-135: The ECDSA branch currently repeats the same call and
never uses the loop variable `alg`; update the crypto.subtle.generateKey call
for ECDSA (the call that currently uses { name: "ECDSA", namedCurve: "P-256" })
to include the hash property (e.g., { name: "ECDSA", namedCurve: "P-256", hash:
alg }) so the test actually exercises SHA-3 handling, and keep the existing
expectation (resolves.toBeDefined()) so it asserts key generation succeeds while
normalization of sign/verify will be tested elsewhere.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 501035b4-e7a5-47eb-bded-f1409c5f849f

📥 Commits

Reviewing files that changed from the base of the PR and between 97e7217 and 0371039.

📒 Files selected for processing (5)
  • src/bun.js/bindings/webcrypto/JSSubtleCrypto.cpp
  • src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
  • src/bun.js/bindings/webcrypto/SubtleCrypto.h
  • src/bun.js/bindings/webcrypto/SubtleCrypto.idl
  • test/regression/issue/29218.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
test/regression/issue/29218.test.ts (1)

112-135: ⚠️ Potential issue | 🟠 Major

The ECDSA branch still does not exercise the SHA-3 rejection path.

Lines 133-135 rerun the same happy-path generateKey() on every loop iteration; alg only affects HMAC and RSA-PSS here. That means the ECDSA part of the test name is still unverified.

Suggested change
-      await expect(
-        crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]),
-      ).resolves.toBeDefined(); // sanity: real ECDSA still works
+      const keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
+      await expect(
+        crypto.subtle.sign({ name: "ECDSA", hash: alg }, keyPair.privateKey, te.encode("abc")),
+      ).rejects.toMatchObject({ name: "NotSupportedError" });
#!/bin/bash
echo "== current test =="
rg -n -C2 'ECDSA|hash: alg|generateKey\(' test/regression/issue/29218.test.ts
echo
echo "== rejection hook =="
rg -n -C3 'toHashIdentifier|SHA3_256|SHA3_384|SHA3_512|ECDSA' src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/regression/issue/29218.test.ts` around lines 112 - 135, The ECDSA case
in the loop never uses the loop variable alg so it doesn't test SHA‑3 rejection;
update the ECDSA call that currently reads crypto.subtle.generateKey({ name:
"ECDSA", namedCurve: "P-256" }, ...) to include the hash: alg parameter (i.e.
crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256", hash: alg },
...)) and change the expectation to reject with NotSupportedError (like the
HMAC/RSA-PSS cases) so the ECDSA path is actually exercised for "SHA3-256",
"SHA3-384", and "SHA3-512".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 280-289: Add the new test file test/regression/issue/29218.test.ts
to the allowlist file test/no-validate-exceptions.txt under the "#
normalizeCryptoAlgorithmParameters" section so the malformed-algorithm-path
exercised by SubtleCrypto.supports(...) (null, number, array, object cases) is
exempt from validation; specifically, insert the filename as an entry under that
section to prevent ASAN/JSC throw-scope SIGILL when
BUN_JSC_validateExceptionChecks=1 triggers the normalization
exception-swallowing path in
normalizeCryptoAlgorithmParameters/SubtleCrypto.supports.
- Around line 141-144: Add negative assertions to ensure the static-only binding
isn't exposed on instances or the prototype: after the existing checks for
SubtleCrypto.supports, assert that SubtleCrypto.prototype.supports is undefined
(or not present) and that an instance (e.g., new SubtleCrypto()) does not have a
supports property; reference the SubtleCrypto.supports symbol and
SubtleCrypto.prototype and the SubtleCrypto constructor when adding these
assertions so future regressions that leak the method onto the prototype or
instance are caught.

---

Duplicate comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 112-135: The ECDSA case in the loop never uses the loop variable
alg so it doesn't test SHA‑3 rejection; update the ECDSA call that currently
reads crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, ...) to
include the hash: alg parameter (i.e. crypto.subtle.generateKey({ name: "ECDSA",
namedCurve: "P-256", hash: alg }, ...)) and change the expectation to reject
with NotSupportedError (like the HMAC/RSA-PSS cases) so the ECDSA path is
actually exercised for "SHA3-256", "SHA3-384", and "SHA3-512".
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d394f1f0-2db5-4571-9ccf-884663fed075

📥 Commits

Reviewing files that changed from the base of the PR and between 0371039 and d047e92.

📒 Files selected for processing (1)
  • test/regression/issue/29218.test.ts

…on checks

The regression test exercises normalizeCryptoAlgorithmParameters along
two paths that trigger BUN_JSC_validateExceptionChecks=1 in ASAN CI:

- digest("SHA3-224", ...) hits the default NotSupportedError arm in the
  Digest switch.
- Every SubtleCrypto.supports() call routes through the
  normalizesWithoutException() helper, which clears exceptions.

Without an entry in test/no-validate-exceptions.txt the pre-existing
throw-scope bookkeeping issue in normalizeCryptoAlgorithmParameters
trips WTFCrash -> ud2 -> SIGILL on ASAN runs. Match the pattern used
for every other webcrypto test in the same section.

Refs #29218
…nits

Two small follow-ups on review feedback:

- The ECDSA arm of the SHA-3 sub-hash rejection test was a no-op: it
  called generateKey({name:"ECDSA", namedCurve:"P-256"}) three times in
  the loop without referencing the SHA-3 loop variable at all. For ECDSA
  the hash is supplied at sign()/verify() time, not generateKey() time,
  so the real assertion is that sign({name:"ECDSA", hash:alg}, ...)
  rejects with NotSupportedError. Generate one P-256 key pair outside
  the loop, then call sign() inside it.

- Two comments in CryptoDigest.cpp SHA-3 sponge were misleading:
    * The buffer[144] comment referred to SHA3-224 as if it were
      registered. Reword to say it is sized for SHA3-224's rate as
      forward-compatibility for later XOF additions; the three variants
      this file ships only need up to SHA3-256's 136 bytes.
    * The squeeze comment claimed "rate >= 104 bytes", which is the
      SHA3-384 rate; the smallest SHA-3 rate is SHA3-512's 72 bytes.
      The single-squeeze logic is still correct (72 >= 64), but the
      bound was wrong — rewrite to state the real minimum.

(Separately, a reviewer suggested adding DontEnum to the constructor-
level supports property. Per WebIDL § 3.7.7 "define the operations",
static operations get {Enumerable: true}, so the existing attribute-0
putDirect is already correct and is intentionally unchanged.)

34 tests / 114 expects pass.

Refs #29218
@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

@robobun the PR's description still uses prototype method supports rather than the static SubtleCrypto.supports

@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

@robobun since you can't run WPTs at least look at internalizing these WPT tests

robobun and others added 2 commits April 12, 2026 15:26
Ports the full (algorithm × input-size) test matrix from
WebCryptoAPI/digest/sha3.tentative.https.any.js in web-platform-tests
into the regression test. Each of the 12 (SHA3-256/384/512 ×
empty/short/medium/long) SHA-3 digests now has to match the exact bytes
shipped with the WPT, including the 87040-byte long input that exercises
multi-block absorption across 512 sponge blocks.

The bytes are copied from:
https://github.com/web-platform-tests/wpt/blob/master/WebCryptoAPI/digest/sha3.tentative.https.any.js

We do not ship a full WPT harness in this slice — that is tracked
separately under #19673 — but the vectors are the important part for
this PR and are now asserted in every bun bd test run.

35 tests / 126 expects pass.

Refs #29218
…ment

The previous comment on `buffer[144]` said the extra capacity was there
so "future SHA3-224 / SHAKE128 support" could reuse the struct without a
resize. SHAKE128 does not fit: its security level is 128 bits, so its
capacity is 2*128 = 256 bits = 32 bytes, and its rate is 200 - 32 = 168
bytes -- 24 bytes larger than this buffer. A developer naively adding
SHAKE128 by following the comment would overflow the buffer inside
sha3Update.

Reword the comment to call out that SHAKE variants do NOT fit as-is:
SHAKE128's rate overflows the buffer, and both SHAKE variants also need a
different digestLength model because their output is variable-length.
Adding SHAKE means resizing the buffer AND teaching sha3Final to squeeze
across multiple rate-sized blocks.

Refs #29218
…ck squeeze

The current sha3Final only squeezes one rate-sized block, which is
correct for all three registered algorithms (SHA3-256: 32 < 136,
SHA3-384: 48 < 104, SHA3-512: 64 < 72) but silently wrong if the
invariant ever breaks — the squeeze loop would read the capacity
portion of the state and emit those bits verbatim, producing a
cryptographically wrong hash with no diagnostic.

The Sha3Context comment already warns XOF variants need a multi-block
squeeze. Add ASSERT(ctx.digestLength <= ctx.rateBytes) at the top of
sha3Final so that if a follow-up slice wires SHAKE/cSHAKE/TurboSHAKE
through this path without also extending the squeeze, debug and ASAN
builds fail loudly instead of producing silent garbage.

35 tests / 126 expects pass (assert does not fire for any registered
variant).

Refs #29218
@robobun
Copy link
Copy Markdown
Collaborator Author

robobun commented Apr 12, 2026

CI status note (final — approved PR, pre-existing flakes only)

PR is approved by @panva ("ship it"). Every CI run has been red on a different set of pre-existing flakes, never touching webcrypto. Summary so far:

Build Red jobs Category
45340 bundler_defer.test.ts GC + FinalizationRegistry timing flake
45354 2× darwin-aarch64 expired, HTTP backpressure flake agent shortage
45367 bundler_defer.test.ts again same GC flake
45381 2× darwin-aarch64 expired, linux-aarch64-build-cpp timeout, test-cluster-primary-kill.js agent shortage + infra timeout + Node cluster flake
45394 bundler_defer.test.ts again same GC flake
45397 (current) 2× darwin-aarch64 expired, update_interactive_install.test.ts, v8-heap-snapshot.test.ts SIGKILL, broadcast-channel-worker-gc.test.ts ASAN use-after-poison in src/bun.js/web_worker.zig agent shortage + unrelated flakes + pre-existing ASAN bug in web_worker.zig

The only potentially new thing is the ASAN report on build 45397: use-after-poison in bun.js.web_worker.setRefInternal during BroadcastChannel worker teardown. Stack trace routes through drainMicrotasks / JSModuleLoader::evaluate, not webcrypto. The file src/bun.js/web_worker.zig is untouched by this PR.

Both halves of the sha3 sponge now have matching defensive asserts (bcad1c2) and bun bd test test/regression/issue/29218.test.ts locally is 35 pass / 126 expects under ASAN debug.

I have already pushed one empty-commit retry (ca842ba). Further retries are a maintainer call — pushing more no-op commits from my side is a waste of CI budget when the same six pre-existing flakes rotate through on every run. Ready to merge; happy to land any real review feedback as a follow-up.

@panva
Copy link
Copy Markdown
Contributor

panva commented Apr 12, 2026

@robobun @Jarred-Sumner I've verified the functionality locally.

@robobun let's start working on the next slice building on top of this one, i recommend finishing up the digest algorithms first, so - TurboSHAKE (maybe KangarooTwelve, probably not tho) that you've got the bases for in this PR already, and cSHAKE at the minimum without support for the functionName and customization optional parameters (which is what gives you effectively SHAKE128 and SHAKE256). There is no algorithm for SHAKE directly, co through the cSHAKE algorithm but without optional args you get SHAKE.

Build 45381 failed on four unrelated jobs: two darwin-aarch64 runners
expired waiting for an agent, linux-aarch64-build-cpp timed out at 14
minutes (infra), and debian-13-x64-asan hit the pre-existing flake in
test/js/node/test/parallel/test-cluster-primary-kill.js (a Node cluster
primary-kill timing test with no webcrypto / SHA-3 touch points).

None of these are related to the ASSERT I added in 5ddb4f6, which
only fires when digestLength > rateBytes — provably false for every
registered SHA-3 variant (32<136, 48<104, 64<72). Local ASAN debug run
shows 35 tests / 126 expects pass.

Kicking CI onto fresh agents.
Symmetric to the guard added in 5ddb4f6 on the squeeze side. sha3Init
computes rateBytes = 200 - 2*outputBytes and stores it; sha3Update then
memcpys up to rateBytes at a time into buffer[144]. If a future caller
wires SHAKE128 (rateBytes = 168) through this path, absorption would
silently overflow the buffer by 24 bytes on the stack before the
sha3Final digest-length guard would ever fire.

Add ASSERT(ctx.rateBytes <= sizeof(ctx.buffer)) at the end of sha3Init
so both halves of the sponge have matching defensive checks. Still
35 tests / 126 expects pass — the assert is false for none of the
three registered variants (SHA3-256: 136, SHA3-384: 104, SHA3-512: 72;
all <= 144).

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants