Skip to content

Improve algorithm steps for Modern Algorithms' supports() method - #558

Open
panva wants to merge 7 commits into
w3c:mainfrom
panva:supports-improvements
Open

Improve algorithm steps for Modern Algorithms' supports() method#558
panva wants to merge 7 commits into
w3c:mainfrom
panva:supports-improvements

Conversation

@panva

@panva panva commented Aug 7, 2026

Copy link
Copy Markdown
Member

The Modern Algorithms specification defines SubtleCrypto.supports() by normalizing an algorithm and executing its operation steps until it encounters unavailable key or data, key generation, a return, or an exception.

This makes the ordering and explicitness of validation in the core Web Cryptography specification observable. Some core operations currently reach one of those stopping points before checking parameters that supports() does have. This can make supports() return true for an interaction that:

  • cannot succeed with any key or input;
  • is not defined for the selected algorithms;
  • is not expected to be supported by underlying cryptographic libraries; or
  • is already deliberately rejected by implementations.

This PR makes those outcomes observable before supports() reaches its stopping point.

Changes

  • Move existing AES-GCM IV, additional-data, and tag-length validation ahead of plaintext or ciphertext access.
  • Check HKDF's 255-block output limit before accessing key material.
  • Reject an explicitly zero-length HMAC import before examining key data.
  • Check necessary RSA modulus-length and public-exponent constraints before key generation.
  • Validate supplied X25519 and ECDH public keys and maximum derivation lengths before accessing the unavailable private key.
  • Derive the ECDH output limit from the curve's EC domain parameters, including curves defined by applicable specifications.
  • Define NamedCurve as an enum so names not defined by the core or an applicable specification fail during normalization.
  • Make the fixed-output-hash boundary explicit for RSA, ECDSA, HMAC, HKDF, and PBKDF2.

The XOF checks establish an intentionally unsupported boundary rather than claiming every such composition is cryptographically impossible/undefined. Selecting an output length for an XOF does not turn it into the fixed-output hash expected by these traditional algorithms. In particular, implementations already reject RSA and ECDSA combinations with XOF digests using NotSupportedError. This just sets it as baseline.

Successful operations are unchanged.

Examples

Each of the following now returns false.

AES-GCM parameters

SubtleCrypto.supports("encrypt", {
  name: "AES-GCM",
  iv: new Uint8Array(12),
  tagLength: 31,
})

A 31-bit AES-GCM tag is never valid, regardless of key or plaintext.

HKDF output length

SubtleCrypto.supports("deriveBits", {
  name: "HKDF",
  hash: "SHA-256",
  salt: new Uint8Array(),
  info: new Uint8Array(),
}, 65288)

With SHA-256 the maximum is 65280 bits, so 65288 bits can never be derived.

XOFs in traditional algorithms

SubtleCrypto.supports("generateKey", {
  name: "HMAC",
  hash: { name: "cSHAKE128", outputLength: 256 },
})

Modern Algorithms implementations deliberately exclude XOF digests from these traditional algorithms. Selecting an output length does not turn cSHAKE into a fixed-output hash.

Undefined named curves

SubtleCrypto.supports("importKey", {
  name: "ECDSA",
  namedCurve: "not-a-curve",
})

No import using an undefined curve could ever succeed.

Zero-length HMAC imports

SubtleCrypto.supports("importKey", {
  name: "HMAC",
  hash: "SHA-256",
  length: 0,
})

No HMAC key data can satisfy an explicitly requested length of zero.

RSA key generation parameters

SubtleCrypto.supports("generateKey", {
  name: "RSA-PSS",
  modulusLength: 2048,
  publicExponent: new Uint8Array([2]),
  hash: "SHA-256",
})

No RSA key can use an even public exponent.

X25519 derivation length

SubtleCrypto.supports("deriveBits", {
  name: "X25519",
  public: publicKey,
}, 257)

X25519 produces at most 256 bits, so 257 bits can never be derived. Refs: WICG/webcrypto-modern-algos#74

ECDH derivation length

SubtleCrypto.supports("deriveBits", {
  name: "ECDH",
  public: publicKey,
}, 257)

With a P-256 public key, the field element encodes to 256 bits. The output limit is derived from the public key’s EC domain parameters, so extended named curves are handled without enumeration. Refs: WICG/webcrypto-modern-algos#74


Preview | Diff

panva added 2 commits August 7, 2026 13:12
Move parameter-only checks ahead of plaintext and ciphertext checks so
support queries can observe them.

For example, this now returns false:

    SubtleCrypto.supports("encrypt", {
      name: "AES-GCM",
      iv: new Uint8Array(12),
      tagLength: 31,
    })

A 31-bit AES-GCM tag is never valid, regardless of key or plaintext.
Check HKDF's 255-block output limit before the operation reaches
unavailable key material.

For example, this now returns false:

    SubtleCrypto.supports("deriveBits", {
      name: "HKDF",
      hash: "SHA-256",
      salt: new Uint8Array(),
      info: new Uint8Array(),
    }, 65288)

With SHA-256 the maximum is 65280 bits, so 65288 bits can never be
derived.
Comment thread spec/Overview.html Outdated
Comment thread spec/Overview.html Outdated
Comment on lines +7199 to +7205
<li>
<p>
If the hash function identified by the {{EcdsaParams/hash}}
member of |normalizedAlgorithm| does not have a fixed output
length, then [= exception/throw =] a {{NotSupportedError}}.
</p>
</li>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm also a bit unsure about this (and the other instances).
I guess there's not much point to supporting it, but do we really need to forbid it everywhere?
If an implementation doesn't want to support it they can simply return false due to the text in https://github.com/WICG/webcrypto-modern-algos/blob/597a4f8cc6bc9ce6eed8ef6c7105d0de86adf92b/index.html#L1118-L1120, no?

@panva panva Aug 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes this is prohibiting an interaction that no implementer has picked up on and implemented. That's a strong enough signal to out right close this explicitly.

@panva panva Aug 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Node 26.7.0 Deno 2.9.5 Ladybird Servo
RSA Key generation and import reject with NotSupportedError; no XOF-bearing RSA key can be created. Generation rejects with NotSupportedError. Import succeeds, but sign, verify, encrypt, and decrypt then throw OperationError. Generation and import succeed, but the later RSA operation throws OperationError. Generation and import succeed, but the later RSA operation throws OperationError.
ECDSA Sign and verify reject with NotSupportedError. Sign and verify reject with NotSupportedError. Sign and verify reject with NotSupportedError. Sign and verify execute using the XOF output as the ECDSA prehash.
HMAC Generation, import, and explicit-length deriveKey reject with NotSupportedError; no XOF-bearing HMAC key can be created. Generation rejects with NotSupportedError. Raw import and explicit-length deriveKey succeed, but sign and verify then throw OperationError. Generation and deriveKey succeed when an explicit positive length is supplied, and raw import succeeds. Sign and verify then throw NotSupportedError. Generation and deriveKey succeed when an explicit positive length is supplied, and raw import succeeds. Sign and verify then throw NotSupportedError.
HKDF Derivation rejects with NotSupportedError. Derivation rejects with NotSupportedError. An ordinary positive-length derivation rejects with NotSupportedError; a zero-length derivation bypasses the hash and succeeds with an empty result. Derivation throws OperationError.
PBKDF2 Derivation rejects with NotSupportedError. Derivation rejects with NotSupportedError. Derivation rejects with NotSupportedError after valid length and iteration checks. Derivation rejects with NotSupportedError.

Small correction from my previous broad statement. ECDSA in Servo is the only implementation that uses the XOF output as the ECDSA prehash.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the overview!

But, I'd still prefer to hold off on adding this text, also because it only really makes sense in the context of XOFs which only exist in the Modern Algorithms draft, not here in this spec (yet).

If there's a concrete danger to supporting this, we could state in the Modern Algorithms draft that implementations shouldn't, but I don't think there is.

Barring that, we could add this text in the main spec once we merge one or more of the XOFs, if there's still no support and we prefer to forbid it at that time.

@panva panva Aug 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You'll call it a breaking change then while it isn't one now as implementations of modern algorithms are underway. Unfortunately the only place this can live in is here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could add a note in the modern algorithms draft that it's not expected to be supported. I'll make a PR for that.

@panva

panva commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@twiss to confirm then

The rest is fine from your POV?

@twiss

twiss commented Aug 7, 2026

Copy link
Copy Markdown
Member

Yeah, I'll do one more pass afterwards but I think the rest looks good 👍 Thanks!

panva added 5 commits August 7, 2026 17:20
Expose the unconditional zero-length failure before import reaches unavailable key data.

For example, this now returns false:

    SubtleCrypto.supports("importKey", {
      name: "HMAC",
      hash: "SHA-256",
      length: 0,
    })

No HMAC key data can satisfy an explicitly requested length of zero.
Expose universally invalid modulus lengths and public exponents before
the operation reaches key generation.

For example, this now returns false:

    SubtleCrypto.supports("generateKey", {
      name: "RSA-PSS",
      modulusLength: 2048,
      publicExponent: new Uint8Array([2]),
      hash: "SHA-256",
    })

No RSA key can use an even public exponent.
Expose impossible public-key inputs and overlong derivations before the
operation reaches unavailable base key material.

For example, this now returns false:

    SubtleCrypto.supports("deriveBits", {
      name: "X25519",
      public: publicKey,
    }, 257)

X25519 produces at most 256 bits, so 257 bits can never be derived.
Expose impossible public-key inputs and overlong derivations before the
operation reaches unavailable base key material.

For example, with a P-256 public key, this now returns false:

    SubtleCrypto.supports("deriveBits", {
      name: "ECDH",
      public: publicKey,
    }, 257)

A P-256 field element encodes to 256 bits. The output limit is derived
from the public key's EC domain parameters, so extended named curves are
handled without enumeration.
Expose unsupported ECDSA and ECDH named curves before import reaches unavailable key data.

For example, this now returns false:

    SubtleCrypto.supports("importKey", {
      name: "ECDSA",
      namedCurve: "not-a-curve",
    })

NamedCurve remains a DOMString so applicable specifications can define additional curves.
@panva
panva force-pushed the supports-improvements branch from 1583eb8 to c635495 Compare August 7, 2026 15:25
@panva

panva commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@twiss done, i'll leave the one review thread unresolved until the PR for modern algorithms is there.

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.

2 participants