w3c / w3c/webcrypto

ECDH deriveBits: step 5's OperationError precedes the curve-mismatch check, but no browser does that

Open Beginner friendly
#560 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
HTML
Stars
294
Forks
78
PR merge metrics
No merged PRs in 30d

Description

The prose

ECDH "Derive Bits" orders
its checks like this:

  1. Let publicKey be the public member of normalizedAlgorithm.
  2. If the [[type]] of publicKey is not "public"InvalidAccessError.
  3. If the name of publicKey's [[algorithm]]normalizedAlgorithm's nameInvalidAccessError.
  4. Let maximumLength be the length in bits of the output of the field element to octet
    string conversion … for the EC domain parameters associated with publicKey.
  5. If length is not null and is greater than maximumLengthOperationError.
  6. If the [[type]] of key is not "private"InvalidAccessError.
  7. If the name of publicKey's [[algorithm]] ≠ the name of key's [[algorithm]]InvalidAccessError.
  8. If the namedCurve of publicKey's [[algorithm]] ≠ the namedCurve of key's [[algorithm]]InvalidAccessError.
    9.–11. the ECDH primitive, and the truncation of secret.

Steps 4 and 5 measure the ceiling off the public key alone and raise before steps 7 and
8 have established that the two keys are a pair at all. So for a mismatched pair where the
public key is on the narrower curve, the request is refused for its length rather than
for the mismatch — a P-521 base key, a P-256 public key and length: 528 is an
OperationError under a literal reading, not an InvalidAccessError.

The browsers

All of them answer InvalidAccessError. Chromium
(components/webcrypto/algorithms/ecdh.cc), Gecko (dom/crypto/WebCryptoTask.cpp), WebKit
(CryptoAlgorithmECDH::deriveBits) and Node (lib/internal/crypto/diffiehellman.js) all
validate the key roles and the curve equality first and only look at length once secret
exists — several of them have no maximumLength step at all and rely on step 11's
"if the length in bits of secret is less than length".

The WPT rows that pin it

WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js builds its mismatched-curve cases in
derive_bits_keys/ecdh.js, pairing each curve with P-256 (or, for P-256 itself, with
P-384), and derive.js's derive() defaults length to 8 * size where size is the
base key's field width. So:

row base key public key length step 5 says asserted
P-384 mismatched curves P-384 P-256 384 OperationError (384 > 256) InvalidAccessError
P-521 mismatched curves P-521 P-256 528 OperationError (528 > 256) InvalidAccessError
P-256 mismatched curves P-256 P-384 256 passes (256 ≤ 384) InvalidAccessError

The first two are unsatisfiable by an implementation that runs step 5 where it is written.
The third is green either way, which is why this is easy to miss. ecdh_bits.https.any.html
is 40/40 in Chrome 152, Firefox 154 and Safari 26.6 on wpt.fyi (master/stable,
2026-08-21), so the tests are not aspirational — the spec is simply describing a different
algorithm from the one that shipped.

Suggested fix

Move steps 4 and 5 to after step 8, so the order becomes 1, 2, 3, 6, 7, 8, then the
domain-parameter/length steps, then the primitive. Nothing else needs to change:

  • Every one of steps 2, 3, 6, 7 and 8 is a comparison of already-validated key metadata, so
    moving the ceiling past them costs nothing and cannot make a previously-succeeding call fail.
  • Once step 8 has run, "the EC domain parameters associated with publicKey" and those
    associated with key are the same parameters, so the wording of step 4 need not change at
    all — only its position. (If you would rather make that explicit, step 4 could equally read
    "associated with key" after the move.)
  • The only observable difference is exactly the one the WPT rows assert: a mismatched pair
    now reports the mismatch whatever length asked for, instead of reporting a length that
    was never going to be derivable.

A smaller alternative — leaving the steps where they are but deriving maximumLength from
key rather than publicKey — would make the two rows above pass, but it leaves an
OperationError reachable for a pair that step 8 is about to reject anyway (e.g. a P-256
base key, a P-384 public key and length: 264), which is still not what browsers do.
Reordering is the change that matches shipped behaviour in every case.

Found while running the WebCryptoAPI corpus against
Jint's Web Crypto implementation
(sebastienros/jint#3180); Jint has taken the browsers' order and documented the divergence
rather than keep two red WPT rows.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the ECDH “Derive Bits” algorithm linked in the issue, then inspect WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js, derive_bits_keys/ecdh.js, and derive.js. Move the domain-parameter and length checks after the key-role and curve-equality checks, and verify that the P-384 and P-521 mismatched-curve rows report InvalidAccessError while the WPT test remains green.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
cryptography
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.