Skip to content

Web Crypto: implement crypto.subtle.digest / importKey / sign (HMAC-SHA-256, SHA-256) #561

Description

@proggeramlug

Context

Discovered while triaging #551 (native S3 client). The sigv4 signing path in @bradenmacdonald/s3-lite-client calls these three SubtleCrypto methods. Same shape every pure-JS AWS / Google / Azure signing lib uses (aws4fetch, jose, oidc-client-ts, web-push, every JWT HS256 signer).

Today these calls hit the #463 strict-API gate as an unimplemented Web API and refuse to compile.

Surface needed

crypto.subtle.digest("SHA-256", data: Uint8Array): Promise<ArrayBuffer>
crypto.subtle.importKey(
  "raw",
  key: Uint8Array,
  algorithm: { name: "HMAC", hash: { name: "SHA-256" } },
  extractable: false,
  keyUsages: ["sign", "verify"],
): Promise<CryptoKey>
crypto.subtle.sign("HMAC", key: CryptoKey, data: Uint8Array): Promise<ArrayBuffer>

digest and sign return Promise<ArrayBuffer>; importKey returns Promise<CryptoKey>. Consumers wrap the ArrayBuffer with new Uint8Array(buf), so spec-correctness on the return type matters.

Why this matters

  • AWS SigV4 — S3, DynamoDB, Lambda invoke, every signed AWS REST request
  • Google Cloud signed URLs, Azure SAS tokens
  • OAuth signing, OIDC, JWT (HS256 / HS384 / HS512)
  • Web Push (VAPID)

Every pure-JS S3 client (s3-lite, aws4fetch, minio-js without lodash) routes through crypto.subtle. Without this, perry.compilePackages can't pull in any of them.

Implementation notes

Thin async wrappers over existing internal SHA-256 / HMAC primitives in crates/perry-runtime/src/ (js_sha256, js_hmac already exist per the bcrypt / @noble/hashes wiring). The async aspect is decorative since these are CPU-bound — resolve synchronously inside the Promise body.

CryptoKey can be an opaque heap object holding the raw key bytes + algorithm name; sign/verify reads it back. No need to honor extractable: false or keyUsages: [...] strictly — Web Crypto enforces those at runtime, but Perry's threat model can treat them as documentation.

Verify: verify("HMAC", ...) is the natural symmetric of sign and worth landing in the same change so JWT validation works.

Out of scope

  • Asymmetric algorithms (RSA-PSS, ECDSA, RSA-OAEP) — those need bigint+EC primitives that aren't in-tree yet
  • generateKey, wrapKey, unwrapKey, deriveKey
  • crypto.subtle.encrypt / decrypt

Acceptance

signing.ts:317-330 from @bradenmacdonald/s3-lite-client (the sha256hmac helper) compiles and the resulting bytes match Node's WebCrypto byte-for-byte against the AWS SigV4 test vectors at https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html

Refs #551 #463

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions