Skip to content

fix: deduplicate and canonicalize CryptoKey.usages#1020

Merged
boorad merged 2 commits into
mainfrom
fix/cryptokey-usage-canonicalization
May 4, 2026
Merged

fix: deduplicate and canonicalize CryptoKey.usages#1020
boorad merged 2 commits into
mainfrom
fix/cryptokey-usage-canonicalization

Conversation

@boorad
Copy link
Copy Markdown
Collaborator

@boorad boorad commented May 4, 2026

Closes #1000 (tracked in #995).

Summary

WebCrypto requires CryptoKey.usages to be deduplicated and returned in a canonical order:

encrypt, decrypt, sign, verify, deriveKey, deriveBits,
wrapKey, unwrapKey, encapsulateKey, encapsulateBits,
decapsulateKey, decapsulateBits

RNQC's CryptoKey constructor previously stored the input array verbatim — no dedup, no reorder. Tests using includes() worked, but tests asserting array equality or comparing JWK key_ops order (and interop with Node-generated keys) would fail.

Reference: Node commit fe7ebccd0ce (crypto: deduplicate and canonicalize CryptoKey usages), lib/internal/crypto/util.js:731-755.

Changes

  • New getSortedUsages helper in utils/validation.ts that dedupes and reorders a KeyUsage[] per the canonical order.
  • Applied in the CryptoKey constructor — the single choke point every subtle.generateKey, subtle.importKey, and KeyObject#toCryptoKey path flows through.
  • JWK exports populate jwk.key_ops from key.usages, so they pick up the canonical order automatically (no separate change needed).

Test plan

New subtle.usage-canonicalization suite (~30 tests):

  • generateKey — HMAC, AES-CTR/CBC/GCM (symmetric); RSA-OAEP, RSA-PSS, ECDSA, ECDH, Ed25519, X25519, ML-DSA-65, ML-KEM-768 (key pairs).

  • importKey raw — AES-GCM, HMAC, HKDF, PBKDF2 (exercises the generic-secret import path).

  • importKey jwk — AES-CBC.

  • KeyObject#toCryptoKey — HMAC, AES-GCM via createSecretKey.

  • JWK export key_ops — AES-CTR, HMAC, RSA-OAEP pair, ML-KEM-768 pair — confirms canonical order propagates from key.usages to jwk.key_ops.

  • Tests pass in the example app on iOS

  • Tests pass in the example app on Android

  • No regression in existing WebCrypto / key suites

🤖 Generated with Claude Code

Closes #1000

WebCrypto requires `CryptoKey.usages` to be deduplicated and returned
in a canonical order:

  encrypt, decrypt, sign, verify, deriveKey, deriveBits,
  wrapKey, unwrapKey, encapsulateKey, encapsulateBits,
  decapsulateKey, decapsulateBits

Tracked in #995. Reference: Node commit fe7ebccd0ce.

## Changes

- New `getSortedUsages` helper in `utils/validation.ts` that dedupes
  and reorders a `KeyUsage[]` per the canonical order.
- Applied in the `CryptoKey` constructor — the single choke point that
  every `subtle.generateKey`, `subtle.importKey`, and
  `KeyObject#toCryptoKey` path flows through. JWK exports populate
  `key_ops` from `key.usages`, so they pick up the canonical order
  automatically.

## Testing

New `subtle.usage-canonicalization` suite covers:

- `generateKey`: HMAC, AES-CTR/CBC/GCM (symmetric); RSA-OAEP, RSA-PSS,
  ECDSA, ECDH, Ed25519, X25519, ML-DSA-65, ML-KEM-768 (key pairs).
- `importKey` raw: AES-GCM, HMAC, HKDF, PBKDF2 — verifies the
  generic-secret import path.
- `importKey` jwk: AES-CBC.
- `KeyObject#toCryptoKey`: HMAC, AES-GCM via `createSecretKey`.
- JWK export `key_ops`: AES-CTR, HMAC, RSA-OAEP pair, ML-KEM-768 pair —
  confirms canonical order propagates from `key.usages` to
  `jwk.key_ops`.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-native-quick-crypto Ready Ready Preview, Comment May 4, 2026 1:58am

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🤖 End-to-End Test Results - iOS

Status: ✅ Passed
Platform: iOS
Run: 25297344398

📸 Final Test Screenshot

Maestro Test Results - ios

Screenshot automatically captured from End-to-End tests and will expire in 30 days


This comment is automatically updated on each test run.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🤖 End-to-End Test Results - Android

Status: ✅ Passed
Platform: Android
Run: 25297344397

📸 Final Test Screenshot

Maestro Test Results - android

Screenshot automatically captured from End-to-End tests and will expire in 30 days


This comment is automatically updated on each test run.

@boorad boorad self-assigned this May 4, 2026
The WebCrypto spec requires `key.usages` to be a frozen array. Apply
`Object.freeze` to the canonicalized usages array in the CryptoKey
constructor so external code can't mutate it. Spread into a fresh
array when exporting JWK `key_ops` to keep that field mutable.

Follow-up cleanups from review of the prior canonicalization commit:

- Drop the `length <= 1` early return in `getSortedUsages` so every
  input flows through the canonical filter (avoids a code path that
  would silently pass through invalid usages if validation upstream
  ever regressed).
- Document `getUsagesUnion`'s contract — dedup and ordering are the
  constructor's job, so future contributors don't re-add ad-hoc
  dedup at call sites.
- Type the canonicalization test vectors with `SubtleAlgorithm` /
  `KeyUsage` / `AnyAlgorithm` instead of `any`, and add regression
  tests asserting `key.usages` is frozen and that `jwk.key_ops`
  mutation does not leak back into the source key.
@boorad boorad merged commit 28be337 into main May 4, 2026
9 checks passed
@boorad boorad deleted the fix/cryptokey-usage-canonicalization branch May 4, 2026 02:18
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.

CryptoKey: deduplicate and canonicalize usages array

1 participant