HTTP Message Signatures

RFC 9421 HTTP Message Signatures — sign and verify an HTTP request or response at the message level, so a recipient checks integrity and origin without trusting the transport. Two headers carry it: Signature-Input names the covered components and the signature parameters, and Signature carries the bytes. The signature base is the canonicalized covered-component list plus those parameters (RFC 9421 §2.5), and the algorithm runs over it.

The derived components are @method, @target-uri, @authority, @scheme, @request-target, @path, @query and @query-param (§2.2). The signature parameters are created, expires, nonce, keyid, alg and tag (§2.3).

Two algorithms are offered: ed25519 for a peer that is not PQC-aware, and ml-dsa-65 (FIPS 204) when both peers are. The RSA, ECDSA-P256, ECDSA-P384 and HMAC variants of §3.3 are not offered.

b.crypto.httpSig.contentDigest(body) #

stable0.8.44

Compute the Content-Digest field value for a request or response body, as the RFC 8941 dictionary entry sha3-512=::. Cover content-digest in a signature and the body is bound to it, so a peer that alters the body invalidates the signature without the signer having to cover every byte directly.

Takes a Buffer, or a string read as UTF-8. Any other value throws.

var field = b.crypto.httpSig.contentDigest(Buffer.from("{\"ok\":true}"));
// → "sha3-512=::"

b.crypto.httpSig.sign(msg, opts) #

stable0.8.44soc2pci-dss
{
  keyid:      string,    // the key identifier the verifier resolves (required)
  alg:        string,    // "ed25519" | "ml-dsa-65" (required)
  privateKey: string,    // PEM private key (required)
  covered:    string[],  // covered components; default ["@method", "@target-uri"]
  created:    number,    // signature creation time, epoch seconds
  expires:    number,    // signature expiry, epoch seconds
  nonce:      string,    // replay nonce carried in the signature parameters
  tag:        string,    // application tag per RFC 9421 2.3
  label:      string,    // signature label; default "sig1"
}

Sign an HTTP request or response per RFC 9421 and return the headers to send. msg carries the method, url, headers and optional body; opts names the key and what the signature covers. A body produces a Content-Digest header alongside the signature, so covering content-digest binds the body.

alg is ed25519 or ml-dsa-65. The RSA, ECDSA and HMAC variants of RFC 9421 §3.3 are not accepted.

var sig = b.crypto.httpSig.sign({
  method:  "POST",
  url:     "https://api.example.com/orders",
  headers: { "content-type": "application/json", host: "api.example.com" },
  body:    Buffer.from("{\"id\":1}"),
}, {
  keyid:      "service-a-2026-05",
  alg:        "ed25519",
  privateKey: privateKeyPem,
  covered:    ["@method", "@target-uri", "content-digest"],
});
// → { headers: { "Signature-Input": "...", "Signature": "...", "Content-Digest": "..." } }

b.crypto.httpSig.verify(msg, opts) #

stable0.8.44soc2pci-dss
{
  keyResolver:        function,  // (keyid, alg) → PEM public key (required)
  toleranceMs:        number,    // clock skew allowed on created / expires
  requiredComponents: string[],  // covered-component floor; [] waives it
}

Verify the Signature-Input and Signature headers on a received message. Returns { valid, label, keyid, alg, covered } on success and { valid: false, reason } otherwise, rather than throwing, so a handler decides what a bad signature means for the route.

keyResolver(keyid, alg) returns the PEM public key to check against, or a falsy value to refuse the key. requiredComponents is the floor the covered set must include; omitting it applies @method and @target-uri, plus content-digest when the message has a body. Pass [] to waive the floor.

var res = b.crypto.httpSig.verify({
  method: req.method, url: fullUrl, headers: req.headers, body: bodyBuffer,
}, {
  keyResolver: function (keyid) { return keys[keyid] || null; },
  toleranceMs: b.constants.TIME.minutes(5),
});
// → { valid: true, label: "sig1", keyid: "service-a-2026-05", alg: "ed25519", covered: [...] }

Last updated 2026-10-12T14:21:56.879Z by seeder.