Skip to main content

RFC 9421 HTTP Message Signature canonical shape

The shape

ElementPin
Algorithmed25519 (default); ecdsa-p256-sha256 as fallback. Reject rsa-pss-sha512 for new keys. Reject hmac-sha256 always.
Signature base components (mandatory)@method, @target-uri, @authority, content-type, content-digest
Parameters in Signature-Inputcreated (UNIX s), expires (created + max 300s for offers, max 600s for catalogs), nonce (16 random bytes, base64url), keyid (resolves in merchant JWKS), alg matches keyid's alg
Body digestRFC 9530 SHA-256; Content-Digest: sha-256=:<base64>: header set on the response
Verifier rules(a) expires > now; (b) created < now + 60s (skew); (c) keyid ∈ jwks; (d) signature valid against keyid's public key; (e) JWKS cache TTL min(300s, response Cache-Control max-age); (f) on keyid miss, re-fetch JWKS once
CachingServer sends Cache-Control: max-age=60, must-revalidate, public plus ETag; agent honours; on stale, re-verifies signature on each fetch

Where it is used

Worked example: signing an Offer (Node)

import { signOffer } from "@oid4pay/oid4ac-merchant";

const body = {
  "@context": "https://schema.org",
  "@type": "Offer",
  "sku": "test-pinata",
  "name": "Test Piñata",
  "amount_minor": 1299,
  "currency": "EUR",
};

const signed = await signOffer(body, {
  privateJwk,
  keyid: "merchant-2026-05-14",
  targetUri: "https://shop.alpacanica.com/products/test-pinata",
  expiresIn: 300,
});

// signed.headers:
//   Content-Digest: sha-256=:abc123...:
//   Signature-Input: offer-sig=("@method" "@target-uri" "@authority"
//     "content-type" "content-digest");created=1747260100;
//     expires=1747260400;nonce="x9...";keyid="merchant-2026-05-14";alg="ed25519"
//   Signature: offer-sig=:<b64 ed25519 sig>:

Worked example: verifying (Python)

from oid4pay_oid4ac import verify_offer, OfferVerifyError

try:
    v = verify_offer(
        body=offer_body,
        headers={
            "Content-Digest": "sha-256=:abc123...:",
            "Signature-Input": "offer-sig=(...)",
            "Signature": "offer-sig=:...:",
        },
        jwks=merchant_jwks,
        expected_target_uri="https://shop.alpacanica.com/products/test-pinata",
    )
    print(v.body_digest, v.keyid, v.alg)
except OfferVerifyError as exc:
    print("rejected:", exc.code)

Canonicalisation

For /.well-known/oid4ac-catalog bodies that carry the signature inside the JSON (so caches and reverse proxies cannot strip headers), the signature MUST cover the canonical (sorted-keys, compact JSON) form of the body MINUS the signature, signature_input, and content_digest members.