Skip to content

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

@proggeramlug

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions