npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dwk/http-signatures

v0.1.0-beta.2

Published

HTTP Message Signatures (RFC 9421) and legacy draft-cavage sign/verify. Cross-standard reusable; no Workers runtime dependency.

Readme

@dwk/http-signatures

HTTP Message Signatures (RFC 9421) sign/verify, with the legacy draft-cavage-http-signatures profile for fediverse interop. Cross-standard reusable.

Part of the @dwk IndieWeb + Solid cohort. See the package specification for the full requirements.

This package is cross-standard reusable: it takes plain-data inputs only (method, URL, headers) plus a resolved CryptoKey, has no Workers-runtime dependency (only Web Crypto), and unit-tests in isolation (Node, no workerd). It is protocol-agnostic — it knows nothing about ActivityPub, IndieWeb, or Solid. Key resolution (fetching an actor's public key, caching it) is the caller's responsibility, supplied as a KeyResolver.

It mirrors the @dwk/dpop hardening posture: only asymmetric algorithms from an explicit allow-list are accepted — never none or HMAC — and a resolved key is validated against the claimed algorithm (RSA modulus floor of 2048 bits, EC curve) before any signature is checked.

API

Signing (RFC 9421)

import { signMessage, createContentDigest } from "@dwk/http-signatures";

const body = JSON.stringify(activity);

// Cover the body by digesting it and listing the digest among the components.
const headers = {
  host: "remote.example",
  date: new Date().toUTCString(),
  "content-type": "application/activity+json",
  "content-digest": await createContentDigest(body),
};

const sig = await signMessage(
  { method: "POST", url: "https://remote.example/inbox", headers },
  {
    key: privateKey, // a private CryptoKey you imported for `alg`
    keyId: "https://me.example/actor#main-key",
    alg: "ed25519",
    components: ["@method", "@target-uri", "@authority", "date", "content-digest"],
    // created defaults to now; expires, nonce, tag, label are optional
  },
);

// Merge sig (Signature-Input + Signature) into the outbound request headers.
await fetch("https://remote.example/inbox", {
  method: "POST",
  headers: { ...headers, ...sig },
  body,
});

Verifying

import { verifyMessage } from "@dwk/http-signatures";

const result = await verifyMessage(
  { method: request.method, url: request.url, headers },
  {
    // Resolve the public key for the claimed keyid (fetch the actor, cache it).
    resolveKey: async ({ keyId, alg }) => fetchActorKey(keyId, alg),
    requiredComponents: ["@method", "@target-uri", "date", "content-digest"],
    body, // optional: also recompute & check any covered content-digest/digest
  },
);

if (!result.valid) {
  // result.reason is a stable code, e.g. "signature_invalid", "key_unresolved"
  return new Response("bad signature", { status: 401 });
}
// result.keyId is the verified principal; result.coveredComponents the audit trail.

The profile is auto-detected from the headers (a Signature-Input header means RFC 9421, otherwise the legacy single Signature header is parsed as draft-cavage); pass profile to force one.

draft-cavage (fediverse)

import { signMessage, createDigest } from "@dwk/http-signatures";

const headers = {
  host: "remote.example",
  date: new Date().toUTCString(),
  digest: await createDigest(body), // legacy "SHA-256=<base64>" header
};

const sig = await signMessage(
  { method: "POST", url: "https://remote.example/inbox", headers },
  {
    profile: "cavage",
    key: privateKey,
    keyId: "https://me.example/actor#main-key",
    alg: "rsa-v1_5-sha256", // serialized as algorithm="rsa-sha256"
    components: ["(request-target)", "host", "date", "digest"],
  },
);

What is verified

  • ProfileSignature-Input / Signature (RFC 9421) or the single Signature header (draft-cavage), auto-detected unless forced.
  • Algorithm — from the allow-list (rsa-pss-sha512, rsa-v1_5-sha256, ecdsa-p256-sha256, ecdsa-p384-sha384, ed25519); never none or HMAC.
  • Key — the resolved CryptoKey's algorithm and EC curve must match the claimed algorithm, and RSA moduli must be at least 2048 bits.
  • Signature — over the reconstructed signature base / signing string.
  • Windowcreated not in the future and expires not in the past, within toleranceSeconds (default 300).
  • Coverage — every component named in the signature is reconstructed from the message; requiredComponents lets the caller insist specific components were covered.
  • Body integrity (optional) — when a body is supplied and a content-digest (RFC 9530) or legacy Digest component is covered, the header is recomputed over the body and must match.

verifyMessage never throws — failures return { valid: false, reason } with a stable SignatureFailureReason code. On success it returns { valid: true, profile, keyId, coveredComponents, created?, expires? }.

Out of scope

  • Key resolution and caching — the caller supplies a KeyResolver and owns fetching/importing the public key (PEM/JWK → CryptoKey) and any caching.
  • Replay/nonce storage — the caller owns nonce bookkeeping.
  • Component parameters;sf, ;key, ;bs, @query-param, and response components (@status) are not derived; a signature covering them is reported as components_malformed rather than guessed at.

License

ISC