Internet-Draft Signature-Keys August 2026
Hardt & Meunier Expires 6 February 2027 [Page]
Workgroup:
HTTP
Internet-Draft:
draft-hardt-httpbis-signature-key-08
Published:
Intended Status:
Standards Track
Expires:
Authors:
D. Hardt
Hellō
T. Meunier
Cloudflare

HTTP Signature Keys

Abstract

This document defines five HTTP header fields for use with HTTP Message Signatures as defined in RFC 9421. The Signature-Key request header distributes public keys used to verify signatures, with eight initial key distribution schemes: pseudonymous inline keys (hwk), self-issued key delegation via JWK Thumbprint JWTs (jkt-jwt), identified signers with JWKS URI discovery (jwks_uri), direct JWKS fetch (jwks), JWT-based delegation (jwt), self-issued JWTs (self-jwt), X.509 certificate chains (x509), and references to previously cached assertions (cached). The Accept-Signature-Scheme and Accept-Signature-Alg response headers state the schemes and algorithms a server accepts, so a client can select both before it signs. The Signature-Error response header provides structured error information when signature verification fails, and the Signature-Key-Cache response header issues a cache identifier by which a caller can reference a previously presented assertion instead of resending it. Together, these mechanisms enable flexible trust models ranging from privacy-preserving pseudonymous verification to horizontally-scalable delegated authentication and PKI-based identity chains.

Discussion Venues

Note: This section is to be removed before publishing as an RFC.

Source for this draft and an issue tracker can be found at https://github.com/dickhardt/signature-key.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 6 February 2027.

Table of Contents

1. Conventions and Definitions

{::boilerplate bcp14-tagged}

2. Introduction

HTTP Message Signatures [RFC9421] provides a powerful mechanism for creating and verifying digital signatures over HTTP messages. To verify a signature, the verifier needs the signer's public key. While RFC 9421 defines signature creation and verification procedures, it intentionally leaves key distribution to application protocols, recognizing that different deployments have different trust requirements.

Where the signer and verifier have no prior relationship, that gap is usually filled by out-of-band pre-registration or by an application-specific token. This document addresses the cases those two options do not cover.

A verifier may have no prior relationship with the signer. Pre-registration assumes the signer is known before the request. Agents, first-contact clients, and cross-domain callers frequently are not. When the first request is also the first contact, there is no registration step in which to have exchanged a key. The key, or a means to obtain it, has to travel with the request.

The key material and its trust model are separate questions. "Which key signed this" and "why should the verifier trust that key" are distinct. A raw inline key answers the first and defers the second to the verifier's policy. A key discovered from an origin ties the key to that origin. A delegated key carries an assertion from a third party. A certificate chain carries a PKI trust path. These are different trust models over the same signature primitive, and a mechanism that hard-codes one of them cannot serve the others. This document treats the trust model as a scheme dimension rather than a fixed choice.

Key conveyance must be covered by the signature it introduces. If the keying material or its identifier travels alongside the signature but is not itself signed over, an intermediary can substitute a different key or identifier and the signature still verifies against the substituted key. Conveying the key in a covered component closes this. A design that carries the key outside the signature's covered components reopens it. Section 7.8 describes the scheme-substitution and identity-substitution attacks this prevents.

The verifier must be able to state what it will accept. A signer that guesses the wrong key distribution scheme, or the wrong algorithm, learns nothing useful from a bare verification failure. Without a way for the verifier to say what it requires and what it supports, the extension point cannot be exercised or negotiated, and it ossifies.

This document defines:

Additional schemes may be defined through the IANA registry established by this document.

Three properties follow from the gaps above and are held as invariants throughout this document:

  1. Keying material or its identifier is conveyed in the Signature-Key header, which is a covered component (Section 7.8). The signature protects the key or identifier that introduces it.

  2. The trust model is a scheme, not a fixed choice. A single header (Section 3) carries any of an inline key, an origin-discovered key, a delegated key, or a certificate chain, distinguished by a scheme token. The header is one namespace for key conveyance; the trust model varies within it.

  3. Unknown schemes and algorithms have defined, mandatory feedback. A verifier that does not implement a presented scheme returns unsupported_scheme with the set it supports (Section 5.4.2). A verifier requires fully-specified algorithms and rejects underspecified ones (Section 3.3). The extension point is exercised on ordinary traffic rather than only at the moment a new value is first deployed, per the guidance of [RFC9170].

The Signature-Key header works in conjunction with the Signature-Input and Signature headers defined in RFC 9421, using matching labels to correlate signature metadata with keying material.

The mechanisms in this document were designed as general-purpose building blocks and are used by other specifications. In the AAuth protocol [I-D.hardt-oauth-aauth-protocol], all parties communicate using Signature-Key to distribute the keys that verify their signed requests. Email Verification [I-D.hardt-email-verification] uses the hwk scheme to convey the browser's public key so the issuer can bind it into the verification token it issues. Additional protocols can adopt these mechanisms without further coordination.

3. Signature-Key HTTP Request Header

The Signature-Key header provides the public key or key reference needed to verify an HTTP Message Signature. It is a Structured Field Dictionary [RFC8941] keyed by signature label, where each member describes how to obtain the verification key for the corresponding signature.

Format:

Signature-Key: <label>=<scheme>;<parameters>...

Where: - <label> (dictionary key) matches the label in Signature-Input and Signature headers - <scheme> (token) identifies the key distribution scheme - <parameters> are semicolon-separated key-value pairs whose values are structured field strings or byte sequences, varying by scheme

Multiple keys are comma-separated per the dictionary format. See [RFC8941] for definitions of dictionary, token, string, and byte sequence.

Unknown schemes:

A verifier that selects a member whose scheme token it does not implement, including any unregistered value, MUST reject the request with a Signature-Error of error=unsupported_scheme (Section 5.4.2) and MUST NOT fail in a scheme-specific or undefined manner. Verifiers SHOULD dispatch on the scheme token through a lookup over the HTTP Signature-Key Scheme registry (Section 9.2) rather than a fixed set of branches, so that unknown schemes take this defined path.

This rule governs the member the verifier selected, and that member alone. A verifier MUST NOT reject a request because a member it did not select names a scheme the verifier does not implement; such members are ignored under Section 3.1. Without this, a signer could not offer a signature under a new scheme without breaking every verifier that does not implement it, so no signer would offer one and the scheme registry would have no path into deployment.

Example:

Signature-Input: sig=("@method" "@authority" "@path" "signature-key"); created=1732210000
Signature: sig=:MEQCIA5...
Signature-Key: sig=hwk;kty="OKP";crv="Ed25519";x="JrQLj..."

Label Correlation:

Labels are correlated by equality of label names across Signature-Input, Signature, and Signature-Key. Signature-Key is a dictionary keyed by label; Signature-Input and Signature are the sources of what signatures are present; Signature-Key provides keying material for those labels.

Verifiers MUST:

  1. Parse Signature-Input and Signature per RFC 9421 and obtain the set of signature labels present. The verifier determines which labels it is attempting to verify based on application context and RFC 9421 processing.

  2. Parse Signature-Key as a Structured Fields Dictionary

  3. For each label being verified, select the Signature-Key dictionary member with the same name

  4. If the Signature-Key header is present and the verifier is attempting to verify a label using it, but the corresponding dictionary member is missing, verification for that signature MUST fail

Note: A verifier might choose to verify only a subset of labels present (e.g., the application-required signature); labels not verified can be ignored.

Signatures whose keys are distributed through mechanisms outside this specification (e.g., pre-configured keys, out-of-band key exchange) are out of scope. A Signature-Key header is not required for such signatures, and verifiers MAY use application-specific means to obtain the verification key.

3.1. Label Consistency

If a label appears in Signature or Signature-Input, and the verifier attempts to verify it using Signature-Key, the corresponding member MUST exist in Signature-Key. If Signature-Key contains members for labels not being verified, verifiers MAY ignore them.

3.2. Multiple Signatures

The dictionary format supports multiple signatures per message. Each signature has its own dictionary member keyed by its unique label:

Signature-Input: sig1=(... "signature-key"), sig2=(... "signature-key")
Signature: sig1=:...:, sig2=:...:
Signature-Key: sig1=jwt;jwt="eyJ...", sig2=jwks_uri;id="https://example.com";dwk="eg-config";kid="k1"

Most deployments use a single signature. When multiple signatures are required, the complete Signature-Key header (containing all keys) MUST be populated before any signature is created, and each signature MUST cover signature-key. This ensures all signatures protect the integrity of all key material. See Section 7.8 in Security Considerations. Alternative key distribution mechanisms outside this specification may be used for scenarios requiring independent signature addition.

3.3. Algorithm Determination

Several schemes in this document convey or reference a JSON Web Key [RFC7517]. For any such JWK, the signature algorithm MUST be fully determined by the key, meaning the JWK carries an alg member whose value is a fully-specified algorithm identifier: one that determines the signature operation completely, including curve and hash where applicable. A verifier MUST reject a JWK whose alg member is absent or whose alg is a polymorphic identifier, and MUST NOT select an algorithm for it by inspecting other key parameters. This requirement applies identically regardless of how the JWK was obtained.

Algorithm identifiers in this document are values from the IANA "JSON Web Signature and Encryption Algorithms" registry [IANA.JOSE.Algorithms], established by [RFC7518] and extended since. This document uses the JOSE signing algorithms of [RFC9421], Section 3.3.7: the signature base is the JWS Signing Input, no JOSE header is used, and the key signals the algorithm rather than the wire. That section states that JWA values are not registered in the HTTP Signature Algorithms registry ([RFC9421], Section 6.2), and that the alg signature parameter is not used at all with JOSE signing algorithms. This document therefore does not use that registry; see Section 7.4.

In particular:

  • The none algorithm MUST NOT be used, nor any algorithm whose JOSE Implementation Requirement is Prohibited. [RFC9421], Section 3.3.7 requires this of any JWS algorithm used for an HTTP Message Signature. A server MUST NOT list such an algorithm in Accept-Signature-Alg (Section 4.2); listing none would advertise that the server takes an unsigned request for a signed one.

  • The polymorphic EdDSA identifier MUST NOT be used. Use the fully-specified Ed25519 or Ed448 identifiers registered by [RFC9864] instead.

  • For RSA keys, the alg MUST name both the padding scheme and the hash, for example PS256 (RSASSA-PSS with SHA-256) or RS256 (RSASSA-PKCS1-v1_5 with SHA-256). A key type of RSA alone is insufficient, since it determines neither the padding nor the hash.

  • The JOSE ECDSA identifiers ES256, ES384, and ES512 are already fully specified and are used as-is.

  • Symmetric algorithms MUST NOT be used. The oct key type and the JOSE MAC identifiers HS256, HS384, and HS512 name a shared secret rather than a public key. A verifier MUST reject a Signature-Key scheme that conveys or references such a key, and a server MUST NOT list a symmetric algorithm in Accept-Signature-Alg (Section 4.2). See Section 7.5.

[RFC9864] states the rule this rests on — that a key be used with only a single algorithm, unless using one key with several is proven secure — and from it RECOMMENDS that the alg member of a JWK be present, unless some other mechanism ensures the key is used as intended. It also deprecates the polymorphic identifiers in the JOSE registry, which is what the first bullet above applies.

This document raises that RECOMMENDED to a requirement, and does so uniformly rather than conditionally on key type.

For OKP and EC keys, kty and crv do determine the algorithm between them, and so are an instance of the "other mechanism" [RFC9864] permits. No registered JOSE signing algorithm pairs the Ed25519 curve with anything but Ed25519, or P-256 with anything but ES256. They do not determine it for an RSA key, which has no crv and whose padding scheme and hash are both free, nor for the AKP key type of [RFC9964], which covers several ML-DSA parameter sets. Requiring alg of every conveyed key, including those it would be possible to derive, is a deliberate choice; Appendix A.11 gives the reasons. [I-D.richer-oauth-httpsig] arrives at the same requirement independently for the keys it binds as JWKs.

A verifier MUST reject a key whose alg names an algorithm it does not support, reporting unsupported_algorithm (Section 5.4.1). Accept-Signature-Alg (Section 4.2) states exactly that set — the algorithms the verifier accepts, neither a subset nor a superset — so a client that selects an algorithm from that list, and presents a key carrying it, is assured of clearing this check.

Where the alg member comes from depends on the scheme. The hwk scheme carries it as a header parameter, the key itself being in the header. The jwt and jkt-jwt schemes carry it inside cnf.jwk, in an assertion the issuer mints. The jwks_uri, jwks, and self-jwt schemes carry no algorithm in the Signature-Key header at all: that header conveys id, kid, and dwk, which identify a key rather than describe it, so the alg member of the resolved JWKS entry is the only channel. A deployment adopting one of those schemes MUST publish a key that carries alg. Pointing at an existing key that omits it does not satisfy this document, even though such a key is valid under [RFC7517], where alg is OPTIONAL. Only the key the kid selects is subject to this requirement; other members of the same JWKS are never resolved, so an existing metadata document can be reused by adding a conforming key to it.

A JWK also carries key-structure members: kty, which [RFC7517] requires, and crv where the key type has one. Because a fully-specified alg determines the key type and the curve, these members are redundant with it. They remain present, since the schemes in this document convey ordinary JWKs, and the redundancy is used as a check rather than ignored: a verifier MUST verify that kty and, where present, crv are consistent with alg, and MUST reject the key if they are not. A JWK with an alg of ES256 and a kty of RSA is inconsistent and MUST be rejected, as is one with an alg of ES256 and a crv of P-384. Rejecting on disagreement prevents a key from being used under either of two conflicting interpretations.

Post-quantum signature algorithms are accommodated by this rule without special treatment. For example, the ML-DSA identifiers ML-DSA-44, ML-DSA-65, and ML-DSA-87 registered by [RFC9964] are fully specified and are used directly as the JWK alg value. The requirement is algorithm-agnostic and accommodates additional post-quantum and hybrid algorithms as they are registered.

A verifier that encounters a JWK whose kty it does not implement, including the AKP key type defined by [RFC9964] for post-quantum keys, MUST reject the key with defined error feedback and MUST NOT fail in an undefined manner. Unrecognized key material is handled on the same defined path as an unsupported algorithm, via unsupported_algorithm (Section 5.4.1). Absence of support for a key type is a reason to decline, not a parsing failure.

The rules above apply to the key a scheme resolves to and to that key alone. A JWKS may hold keys a verifier cannot use. A verifier MUST select the member matching kid without requiring any other member to be usable, and MUST NOT fail because an unselected member names a kty or alg it does not implement.

Without this rule no signer could introduce a new algorithm: an issuer adding a post-quantum key alongside a classical one would break every verifier that does not implement the new type, including those that were only ever going to use the classical key. The accommodation above would never be reached in deployment, because no issuer could afford to publish such a key.

Within a single JWK, a member a verifier does not understand is ignored, as [RFC7517], Section 4 requires. A member this document forbids, such as kid in the hwk scheme (Section 3.4), is different: it is understood and rejected, not unknown and ignored.

3.4. Header Web Key (hwk)

The hwk scheme provides a self-contained public key inline in the header, enabling pseudonymous verification without key discovery. The parameter names and values correspond directly to the JWK parameters defined in [RFC7517].

Parameters by key type:

OKP (Octet Key Pair):

  • kty (REQUIRED, String) - "OKP"

  • crv (REQUIRED, String) - Curve name (e.g., "Ed25519")

  • x (REQUIRED, String) - Public key value

  • alg (REQUIRED, String) - Fully-specified algorithm identifier (e.g., "Ed25519")

Signature-Key: sig=hwk;kty="OKP";crv="Ed25519";x="JrQLj5P...";alg="Ed25519"

EC (Elliptic Curve):

  • kty (REQUIRED, String) - "EC"

  • crv (REQUIRED, String) - Curve name (e.g., "P-256", "P-384")

  • x (REQUIRED, String) - X coordinate

  • y (REQUIRED, String) - Y coordinate

  • alg (REQUIRED, String) - Fully-specified algorithm identifier (e.g., "ES256")

Signature-Key: sig=hwk;kty="EC";crv="P-256";x="f83OJ3D...";y="x_FEzRu...";alg="ES256"

RSA:

  • kty (REQUIRED, String) - "RSA"

  • n (REQUIRED, String) - Modulus

  • e (REQUIRED, String) - Exponent

  • alg (REQUIRED, String) - Fully-specified algorithm identifier naming padding and hash (e.g., "PS256")

Signature-Key: sig=hwk;kty="RSA";n="0vx7agoebGcQ...";e="AQAB";alg="PS256"

Constraints:

  • The alg parameter MUST be present and fully specified. The inline JWK is subject to Algorithm Determination (Section 3.3).

  • The kid parameter MUST NOT be used. The key is carried inline, so there is nothing for an identifier to select, and a kid that disagrees with the inline key has no defined resolution.

Use cases:

  • Privacy-preserving agents that avoid identity disclosure

  • Experimental or temporary access without registration

  • Rate limiting and reputation building on a per-key basis

3.5. JKT JWT Self-Issued Key Delegation (jkt-jwt)

The jkt-jwt scheme (pronounced "jacket jot") provides self-issued key delegation using a JWT whose signing key is embedded in the JWT header. This enables devices with hardware-backed secure enclaves to delegate signing authority to ephemeral keys, avoiding the performance cost of repeated enclave operations while maintaining a cryptographic chain of trust rooted in the enclave key.

Many devices — mobile phones, laptops, IoT hardware — include secure enclaves or trusted execution environments (e.g., Apple Secure Enclave, Android StrongBox, TPM) that can generate and store private keys with strong protection guarantees. However, signing operations using these enclaves are comparatively slow and may require user interaction (biometric confirmation, PIN entry).

For HTTP Message Signatures, where every request requires a signature, this creates a tension between security and performance. The jkt-jwt scheme resolves this by allowing the enclave key to sign a JWT that delegates authority to a faster ephemeral key:

  1. The enclave generates a long-lived key pair (the identity key)
  2. The device generates an ephemeral key pair in software (the signing key)
  3. The enclave signs a JWT binding the ephemeral key via the cnf claim
  4. HTTP requests are signed with the fast ephemeral key
  5. The JWT proves the ephemeral key was authorized by the enclave key

The enclave key's JWK Thumbprint URI (urn:jkt:<hash-algorithm>:<thumbprint>) serves as a stable, pseudonymous device identity. Verifiers build trust in this identity over time (TOFU — Trust On First Use [RFC7435]).

Parameters:

  • jwt (REQUIRED, String) - Compact-serialized JWT

  • cache (OPTIONAL, Boolean) - As for the jwt scheme (Section 3.8): the caller indicates it can present a cache identifier on subsequent requests, using the cached scheme (Section 3.11), and requests that the verifier issue one. A JWT presented with cache MUST contain a jti claim. See Section 6.

JWT requirements:

Header:

  • typ (REQUIRED) - Identifies the thumbprint hash algorithm. Defined values: jkt-s256+jwt (SHA-256), jkt-s512+jwt (SHA-512). Implementations MUST support jkt-s256+jwt and MAY support additional algorithms.

  • alg (REQUIRED) - Signature algorithm used by the enclave key

  • jwk (REQUIRED) - JWK public key of the enclave/identity key (the key that signed this JWT)

Payload:

  • iss (REQUIRED) - JWK Thumbprint URI of the signing key, in the format urn:jkt:<hash-algorithm>:<thumbprint> where the thumbprint is computed per [RFC7638]. The hash algorithm in the URN MUST match the algorithm indicated by the JWT typ. The verifier knows the hash algorithm from the typ it accepted, computes the thumbprint of the header jwk, prepends the known urn:jkt:<hash-algorithm>: prefix, and compares to iss by string equality.

  • iat (REQUIRED) - Issued-at timestamp

  • exp (REQUIRED) - Expiration timestamp

  • cnf (REQUIRED) - Confirmation claim [RFC7800] containing jwk: the ephemeral public key delegated for HTTP message signing

  • jti (OPTIONAL) - Unique identifier for this delegation. REQUIRED when the JWT is presented with the cache parameter, since a cacheable assertion must be identifiable (Section 6). The iss thumbprint does not serve: it names the enclave key, so successive delegations from one enclave share it.

The sub claim is not used. The identity is the enclave key itself, fully represented by the iss thumbprint.

The header jwk and the delegated key in cnf.jwk are each subject to Algorithm Determination (Section 3.3).

JWT Type Values:

The typ value encodes both the purpose and the thumbprint hash algorithm:

Table 1
typ Hash Algorithm iss prefix
jkt-s256+jwt SHA-256 urn:jkt:sha-256:
jkt-s512+jwt SHA-512 urn:jkt:sha-512:

The jkt- prefix indicates a self-issued delegation JWT: the signing key is embedded in the JWT header as a JWK, the issuer is identified by the key's thumbprint, and the JWT delegates signing authority to the key in the cnf claim. The suffix (s256, s512) identifies the hash algorithm used for the thumbprint. The typ and iss prefix MUST be consistent.

These types are independent of the Signature-Key header and MAY be used in other contexts where self-issued key delegation is needed. Additional hash algorithms can be supported by registering new typ values following the jkt-<alg>+jwt pattern.

Example:

Signature-Key: sig=jkt-jwt;jwt="eyJ..."

JWT header:

{
  "typ": "jkt-s256+jwt",
  "alg": "ES256",
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
    "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
  }
}

JWT payload:

{
  "iss": "urn:jkt:sha-256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
  "iat": 1732210000,
  "exp": 1732296400,
  "cnf": {
    "jwk": {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"
    }
  }
}

In this example, the enclave holds a P-256 key (signed via hardware) and delegates to an Ed25519 ephemeral key (signed in software). The identity is urn:jkt:sha-256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs.

The stable (enclave) key algorithm in the JWT alg header is determined by what the enclave hardware supports. This document's example uses ES256 with a P-256 stable key delegating to an Ed25519 request key; deployments whose enclaves support Ed25519 (or other) stable-key algorithms should document this explicitly. The cnf.jwk request key algorithm is likewise enclave-determined.

Verification procedure:

  1. Parse the JWT without verifying the signature

  2. Check the typ header (e.g., jkt-s256+jwt). Reject if the type is not supported.

  3. Determine the hash algorithm and iss prefix from the typ (e.g., jkt-s256+jwt → SHA-256, urn:jkt:sha-256:)

  4. Extract the jwk from the JWT header

  5. Compute the JWK Thumbprint ([RFC7638]) of the header jwk using the determined hash algorithm

  6. Construct the expected iss value by prepending the known prefix to the computed thumbprint

  7. Verify the iss claim matches the constructed value by string equality

  8. Verify the JWT signature using the header jwk

  9. Validate exp and iat claims per policy

  10. Extract the ephemeral public key from cnf.jwk

  11. Verify the HTTP Message Signature using the ephemeral key

Caching:

Caching — the cached scheme (Section 3.11) together with the Signature-Key-Cache response header (Section 6) — keeps an assertion off the wire in the steady state, and the saving grows with the size of the keys and signatures the assertion carries (Section 7.7). This applies to any cacheable assertion, not only to jkt-jwt. What is specific to jkt-jwt is that the assertion carries a public key in its header and is signed by that key, so caching it also removes the thumbprint computation and the verification of that signature, which the jwt scheme does not perform. That part of the saving is small, since post-quantum verification is comparable to classical; the bytes are the reason to cache. The per-request signature continues to use the ephemeral cnf.jwk key.

Use cases:

  • Devices with hardware-backed secure enclaves delegating to fast ephemeral keys

  • Persistent pseudonymous identity without requiring registration or authority

  • Mobile apps, laptops, and IoT devices with enclave-backed identity

3.6. JWKS URI Discovery (jwks_uri)

The jwks_uri scheme identifies the signer and enables key discovery via a metadata document containing a jwks_uri property.

Parameters:

  • id (REQUIRED, String) - Signer identifier (HTTPS URL)

  • dwk (REQUIRED, String) - Dot well-known metadata document name under /.well-known/

  • kid (REQUIRED, String) - Key identifier

Discovery procedure:

  1. Fetch {id}/.well-known/{dwk}

  2. Parse as JSON metadata. The document MUST contain issuer and jwks_uri members. Reject with issuer_missing (Section 5.4.9) if issuer is absent.

  3. Verify issuer equals the id parameter, by byte equality as presented. Reject with issuer_mismatch (Section 5.4.10) if they differ.

  4. Extract jwks_uri property

  5. Fetch JWKS from jwks_uri

  6. Find key with matching kid

The issuer check binds the metadata document to the identity it was fetched under. Without it, a document served at {id}/.well-known/{dwk} — through misconfigured shared hosting, a subdomain takeover, or any other means — could point jwks_uri at keys that do not belong to id, and the verifier would attribute the request accordingly. This is the same check [RFC8414], Section 3.3 requires of authorization server metadata, and a document conforming to [RFC8414] or OpenID Connect Discovery already carries issuer.

The JWK selected from the retrieved JWKS is subject to Algorithm Determination (Section 3.3).

Example:

Signature-Key: sig=jwks_uri;id="https://client.example";dwk="example-configuration";kid="key-1"

Use cases:

  • Identified services with stable HTTPS identity

  • Search engine crawlers and monitoring services

  • Services requiring explicit entity identification

3.7. Direct JWKS (jwks)

The jwks scheme identifies the signer by an HTTPS URL that returns a JWKS directly. Unlike jwks_uri, there is no metadata document and no discovery hop: the URL is both the signer's identifier and the location of its keys.

Parameters:

  • url (REQUIRED, String) - HTTPS URL of the signer's JWKS

  • kid (REQUIRED, String) - Key identifier

Discovery procedure:

  1. Apply egress admission (Section 7.3) to url

  2. Fetch url

  3. Parse as a JWKS ([RFC7517])

  4. Find the key with matching kid

The JWK selected from the fetched JWKS is subject to Algorithm Determination (Section 3.3).

Example:

Signature-Key: sig=jwks;url="https://client.example/keys.jwks";kid="key-1"

Identifier semantics:

Under the jwks scheme the signer's identity is the JWKS URL itself. A verifier that allowlists or policies by identity is doing so against url. Because identity and key location are the same string, moving the JWKS to a different URL changes the signer's identity. Signers that need identity to remain stable while key location changes independently should use the jwks_uri scheme (Section 3.6), whose indirection exists for that purpose (see Appendix A.1). The jwks scheme trades that decoupling for a single fetch and zero configuration.

The url is compared by byte equality, as presented: a verifier MUST NOT canonicalize or normalize it, and values that differ in any byte name different identities. A signer MUST present the same bytes wherever it intends the same identity.

Use cases:

  • Signers that want a self-describing identifier with no metadata to host

  • Deployments where the JWKS URL is an acceptable stable identity

3.8. JWT Confirmation Key (jwt)

The jwt scheme embeds a public key inside a signed JWT using the cnf (confirmation) claim [RFC7800], enabling delegation and horizontal scale.

Parameters:

  • jwt (REQUIRED, String) - Compact-serialized JWT

  • cache (OPTIONAL, Boolean) - When true, the caller indicates it can present a cache identifier on subsequent requests, using the cached scheme (Section 3.11), and requests that the verifier issue one. Absent means the caller does not want one. Boolean true is indicated by omitting the value ([RFC8941], Section 4.1.1.2), so the parameter is serialized as cache rather than cache=?1. Because it is carried in the Signature-Key header, this signal is covered by the per-request signature. A JWT presented with cache MUST contain a jti claim ([RFC7519], Section 4.1.7); a verifier MUST NOT issue a cache identifier for a JWT without one. See Section 6.

Signature-Key: sig1=jwt;jwt="eyJhbGciOiJFZERTQSJ9...";cache

JWT requirements:

  • MUST contain cnf.jwk claim with embedded JWK. The key conveyed in the assertion is subject to Algorithm Determination (Section 3.3).

  • SHOULD contain iss claim (HTTPS URL of the issuer) — using SHOULD rather than MUST allows existing JWT infrastructure to be used without modification

  • SHOULD contain dwk claim (dot well-known metadata document name) — the verifier constructs {iss}/.well-known/{dwk} to discover the issuer's jwks_uri. Using SHOULD allows deployments where the verifier already knows the issuer's keys.

  • MUST contain exp claim. The assertion carries a confirmation key, and exp is what bounds how long that key is accepted; without it the key remains acceptable indefinitely. See Appendix A.7.

  • SHOULD contain standard claims: sub, iat

  • Verifiers SHOULD verify the JWT typ header parameter has an expected value per deployment policy, following the explicit-typing guidance of [RFC8725], Section 3.11. The check is a defence against token confusion — an assertion minted for one context being accepted in another — and also rejects a wrong token before any cryptographic work.

Note: The mechanism by which the JWT is obtained is out of scope of this specification.

Verification procedure:

  1. Parse the JWT parameter value per [RFC7519] Section 7.2. Reject if the value is not a well-formed JWT. This and subsequent pre-signature checks allow the verifier to fail early without expensive cryptographic operations or network fetches.

  2. Verify the JWT typ header parameter has an expected value per policy. Reject if unexpected.

  3. Validate exp claim if present. Reject if the token has expired.

  4. Verify required claims are present (cnf.jwk, plus any claims required by deployment policy). Reject if a required claim is missing.

  5. If iss and dwk claims are present, fetch {iss}/.well-known/{dwk}, parse as JSON metadata, and verify the document's issuer member equals the iss claim as for the jwks_uri scheme (Section 3.6), rejecting with issuer_missing or issuer_mismatch. Extract jwks_uri, fetch the JWKS from it, and find the key matching kid in the JWT header. If iss or dwk is absent, the verifier MUST obtain the issuer's key through an application-specific mechanism.

  6. Verify JWT signature using the discovered key

  7. Validate remaining JWT claims per policy (iss, sub, etc.)

  8. Extract JWK from cnf.jwk

  9. Verify HTTP Message Signature using extracted key

Example:

Signature-Key: sig=jwt;jwt="eyJhbGciOiJFUzI1NiI..."

JWT payload example:

{
  "iss": "https://issuer.example",
  "dwk": "example-configuration",
  "sub": "instance-123",
  "exp": 1732210000,
  "cnf": {
    "jwk": {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"
    }
  }
}

Use cases:

  • Distributed services with ephemeral instance keys

  • Delegation scenarios where instances act on behalf of an authority

  • Short-lived credentials for horizontal scaling

3.9. Self-Issued JWT (self-jwt)

The self-jwt scheme carries a signed JWT where the JWT issuer and the HTTP request signer are the same party. The signing key is discoverable from the issuer's JWKS, and that same key verifies both the JWT and the HTTP Message Signature. Unlike the jwt scheme, no cnf claim is present — the signing key is the confirmation key.

Parameters:

  • jwt (REQUIRED, String) - Compact-serialized JWT

JWT requirements:

  • MUST contain iss claim (HTTPS URL of the issuer)

  • MUST contain dwk claim (dot well-known metadata document name) — the verifier constructs {iss}/.well-known/{dwk} to discover the issuer's jwks_uri

  • MUST have kid in the JWT header identifying the signing key in the issuer's JWKS. The JWK selected from the issuer's JWKS is subject to Algorithm Determination (Section 3.3).

  • MUST NOT contain cnf claim

  • MUST contain exp claim, bounding how long the assertion is accepted

  • SHOULD contain standard claims: sub, aud, iat

The self-jwt scheme does not support the cache parameter, and a verifier MUST NOT issue a cache identifier for a self-jwt. A self-jwt embeds no key: its signing key is the confirmation key and is discovered from the issuer's JWKS by iss and kid. That key is already cacheable on those two values (Section 7.2), so caching the assertion in addition would save only the assertion's own bytes, of which there are few. A self-jwt also typically carries claims specific to the request it accompanies, so a cached copy would be stale for the next request rather than reusable.

  • Verifiers SHOULD verify the JWT typ header parameter has an expected value per deployment policy, following the explicit-typing guidance of [RFC8725], Section 3.11. The check is a defence against token confusion — an assertion minted for one context being accepted in another — and also rejects a wrong token before any cryptographic work.

Note: The mechanism by which the JWT is obtained is out of scope of this specification.

Verification procedure:

  1. Parse the JWT parameter value per [RFC7519] Section 7.2. Verifiers MUST reject if the value is not a well-formed JWT. Performing this and the subsequent pre-signature checks first lets the verifier fail early, without expensive cryptographic operations or network fetches.

  2. Verify the JWT typ header parameter has an expected value per policy. Reject if unexpected.

  3. Validate exp claim if present. Reject if the token has expired.

  4. Verify iss, dwk claims and kid JWT header parameter are present. Reject if any is absent.

  5. Verify cnf claim is absent. Reject if present.

  6. Construct {iss}/.well-known/{dwk}, parse as JSON metadata, and verify the document's issuer member equals the iss claim as for the jwks_uri scheme (Section 3.6), rejecting with issuer_missing or issuer_mismatch. Extract jwks_uri, fetch the JWKS from it, and find the key matching kid from the JWT header. Reject if the key is not found (error: unknown_key).

  7. Verify JWT signature using the discovered key.

  8. Validate remaining JWT claims per policy (sub, aud, etc.)

  9. Verify HTTP Message Signature using the same key from step 6.

Example:

Signature-Key: sig=self-jwt;jwt="eyJhbGciOiJFUzI1NiIsImtpZCI6InIxIn0..."

JWT header:

{
  "alg": "ES256",
  "kid": "r1",
  "typ": "aauth-resource+jwt"
}

JWT payload:

{
  "iss": "https://resource.example",
  "dwk": "aauth-resource",
  "aud": "https://agent.example",
  "eid": "evt-abc123",
  "exp": 1732210000
}

The verifier fetches https://resource.example/.well-known/aauth-resource, retrieves the JWKS, finds the key with kid="r1", verifies the JWT signature with it, then uses that same key to verify the HTTP Message Signature.

Use cases:

  • Resources delivering events with application-layer claims that the verifier needs alongside key verification

  • Clients presenting themselves directly without delegating to a separate authority

3.10. X.509 Certificates (x509)

The x509 scheme provides certificate-based verification using PKI trust chains.

Parameters:

  • x5u (REQUIRED, String) - URL to X.509 certificate chain (PEM format, [RFC7517] Section 4.6)

  • x5t (REQUIRED, Byte Sequence) - Certificate thumbprint: SHA-256 hash of DER-encoded end-entity certificate

Verification procedure:

  1. Check cache for certificate with matching x5t

  2. If not cached or expired, fetch PEM from x5u

  3. Validate certificate chain to trusted root CA

  4. Check certificate validity and revocation status

  5. Verify x5t matches end-entity certificate

  6. Extract public key from end-entity certificate

  7. Verify signature using extracted key

  8. Cache certificate indexed by x5t

For x509, the verifier determines the signature algorithm from the certificate's SubjectPublicKeyInfo together with the signing algorithm it will apply for HTTP Message Signature verification. The verifier MUST select a fully-specified algorithm and MUST NOT make a polymorphic selection.

Example:

Signature-Key: sig=x509;x5u="https://client.example/.well-known/cert.pem";x5t=:bWcoon4QTVn8Q6xiY0ekMD6L8bNLMkuDV2KtvsFc1nM=:

Use cases:

  • Enterprise environments with PKI infrastructure

  • Integration with existing certificate management systems

  • Scenarios requiring certificate revocation checking

  • Regulated industries requiring certificate-based authentication

3.11. Cached Assertion (cached)

Editor's Note: Assertion caching, comprising this scheme, the cache signal on the jwt and jkt-jwt schemes, the Signature-Key-Cache response header (Section 6), and the cache_miss error (Section 5.4.3), is a straw man offered as a starting point for discussion, not a settled design. Caching is hard, and this may not be the right layer for it. Mechanisms that already exist lower down may fit better: HPACK ([RFC7541]) and QPACK ([RFC9204]) header compression already avoid retransmitting a repeated header field value, entity tags ([RFC9110], Section 8.8.3) already express "I hold this, do you" at the HTTP layer, and HTTP/2 and HTTP/3 session resumption already carry state across connections. The problem is real and grows with post-quantum assertion sizes (Section 7.7); the shape of the answer is open. Feedback on whether this belongs in this document, and at this layer, is specifically sought.

The cached scheme references an assertion the verifier has previously cached and issued a cache identifier for (Section 6), in place of presenting the assertion in full.

Parameters:

  • cid (REQUIRED, String) - The cache identifier previously issued by the verifier for this assertion. It is opaque to the caller, which MUST present it exactly as received and MUST NOT parse or construct it.

Example:

Signature-Key: sig1=cached;cid="2f9c8a1e-a7b3"

A request using the cached scheme MUST carry the per-request HTTP Message Signature as usual. The cache identifier stands in for the assertion, not for the signature. The verifier resolves it to the cached assertion (Section 6.1), obtains the assertion's confirmation key, and verifies the per-request signature against that key. The signature-key component is covered by the signature (Section 7.8), so the cache identifier is signed over and cannot be substituted by an intermediary.

A caller MUST NOT present a cache identifier unless a verifier has issued one for that assertion via Signature-Key-Cache (Section 6). A verifier that does not implement assertion caching treats cached as an unimplemented scheme and returns unsupported_scheme (Section 5.4.2); the caller then retries with the full assertion.

4. Accept-Signature-Scheme and Accept-Signature-Alg Response Headers

[RFC9421] Section 5 defines the Accept-Signature response header for requesting HTTP Message Signatures. Its signature metadata parameters are Item parameters, whose values are bare Items ([RFC8941], Section 3.1.2) and cannot be lists. A server therefore cannot use Accept-Signature to state that it accepts any of several Signature-Key schemes, nor any of several algorithms: its alg parameter names one algorithm.

This document defines two response header fields that carry those sets. Both are List Structured Fields ([RFC8941], Section 3.1) of Tokens, so a server states everything it accepts in one response, and a client selects a scheme and an algorithm before it signs rather than discovering them through a rejection.

Both headers are advisory capability statements, not directives. A server that omits them is not asserting that it accepts everything; a client that cannot satisfy them learns the outcome from Signature-Error (Section 5.4) as before.

4.1. Accept-Signature-Scheme

Accept-Signature-Scheme is a List ([RFC8941], Section 3.1) of Tokens, each naming a scheme registered in the HTTP Signature-Key Scheme registry (Section 9.2). It states the Signature-Key schemes the server accepts.

Accept-Signature-Scheme: hwk, jwks_uri, jwt

Order carries the server's preference: a server SHOULD list schemes in descending order of preference. A client MAY choose any listed scheme it can satisfy, and the order does not bind it. The preference is an operational convenience for the server, while the choice of scheme decides whether the signer stays pseudonymous or is identified (Section 8.1). A client that would be identified under the server's first preference and pseudonymous under its second is entitled to take the second.

A client MUST ignore tokens it does not recognize, so that a server may list schemes registered after the client was written without breaking it. A client that recognizes no listed scheme SHOULD NOT sign the request, since no scheme it can produce will be accepted.

Listing the cached scheme (Section 3.11) states that the server implements assertion caching. A client that sees it can set the cache signal (Section 3.8, Section 3.5) on its first request rather than probing. cached is a capability announcement rather than a scheme a client can choose to present: a client MUST NOT present it until a verifier has issued it a cache identifier, whatever the list order. A server that lists cached first is stating a preference for the steady state, not for the first request, and the client selects any scheme it can satisfy from the remainder.

4.2. Accept-Signature-Alg

Accept-Signature-Alg is a List ([RFC8941], Section 3.1) of Tokens, each a fully-specified identifier from the IANA "JSON Web Signature and Encryption Algorithms" registry [IANA.JOSE.Algorithms] — the same identifiers a conveyed key carries in its alg member (Section 3.3), and not those of the HTTP Signature Algorithms registry, which this document does not use (Section 7.4). It states the signature algorithms the server accepts. Using the registry the key uses is what lets a client compare what a server accepts against the keys it holds.

Accept-Signature-Alg: Ed25519, ES256

Each Token is the registered identifier verbatim, including its case: ES256, not es256. Structured Field parsing preserves the case of a Token ([RFC8941], Section 4.2.6), and the comparison a client performs is against the alg member of a JWK, a case-sensitive JSON string. A case-folded token names no registered algorithm and matches no key.

Order, unknown-token handling, and the no-recognized-value case are as for Accept-Signature-Scheme.

Because a fully-specified algorithm identifier determines the key type and curve (Section 3.3), this list also tells the client which keys are usable, and so which key to generate or select when it holds more than one.

Accept-Signature-Alg states what the server accepts. The alg parameter of Accept-Signature ([RFC9421], Section 5.1) requests one specific algorithm for a specific signature label, naming it in the HTTP Signature Algorithms registry, which this document does not use (Section 7.4): under the JOSE signing algorithms the algorithm is signaled by the key, not requested on the wire. A server that sends Accept-Signature-Alg SHOULD NOT send the alg parameter, and a client MAY ignore an alg received alongside Accept-Signature-Alg; the algorithm the client uses is the one its key carries (Section 3.3).

4.3. Relationship to Accept-Signature

Accept-Signature continues to carry what is to be signed: the covered components, and the per-label parameters of [RFC9421] Section 5.1. The two headers defined here carry what the server will accept in the Signature-Key header and in the signature itself. They are independent fields; a response MAY include any combination.

Neither header is keyed by signature label. Both state a server-wide capability, which does not vary per signature. A deployment that genuinely requires different schemes for different labels in one multi-signature message is outside what these headers express, and states the requirement in its own protocol.

Accept-Signature also defines a keyid parameter ([RFC9421], Section 5.1), which asks the signer to use key material the two parties already hold. Where the client is to identify its key through Signature-Key, keyid has nothing left to name: a server SHOULD NOT send it, and a client MAY ignore it. If a signer includes keyid in Signature-Input for a label it also lists in Signature-Key, the two MUST identify the same key, and a verifier verifying that label MUST take the key from Signature-Key.

HTTP/1.1 401 Unauthorized
Accept-Signature: sig1=("@method" "@path" "@authority");created
Accept-Signature-Scheme: jwks_uri, jwt
Accept-Signature-Alg: Ed25519

The client responds with matching labels:

Signature-Key: sig1=jwks_uri;id="https://client.example";dwk="example-configuration";kid="key-1"
Signature-Input: sig1=("@method" "@path" "@authority" "signature-key");
    created=1732210000
Signature: sig1=:MEQCIA5...:

The signature-key covered component is added by the client per this specification's requirement that signature-key appear in covered components. The server does not need to list it in Accept-Signature.

4.4. Sending on Errors and on Challenges

Both headers MAY be sent on any response. They are useful on two occasions in particular.

On a challenge, before the client has signed anything, they let the client choose correctly the first time.

On a Signature-Error response, they say what would have worked. A server returning unsupported_scheme (Section 5.4.2) SHOULD include Accept-Signature-Scheme, and a server returning unsupported_algorithm (Section 5.4.1) SHOULD include Accept-Signature-Alg. The error names what went wrong; the header names what would succeed. A server MAY omit the header where enumerating its accepted schemes or algorithms to an unauthenticated caller is judged a disclosure risk, accepting that a client then has to discover them by trial or out of band; the same consideration applies to Signature-Error itself.

HTTP/1.1 401 Unauthorized
Signature-Error: error=unsupported_scheme
Accept-Signature-Scheme: jwks_uri, jwt

4.5. Response Status Codes

These headers can be set on any response. Below is a list of what they MAY mean on responses with the following status codes:

Table 2
Status Meaning Legacy client behavior Signature-aware client behavior
401 Authentication required Falls back to WWW-Authenticate Signs request with an accepted Signature-Key scheme
402 Payment + authentication required Processes payment mechanism Signs request AND processes payment
429 Rate limited Respects Retry-After, slows down Signs request, gets higher per-key rate limit

The 429 case is particularly important for incremental adoption: a server can add these headers to its existing 429 responses with zero risk. Legacy clients ignore the unknown header fields and respect Retry-After. Signature-aware clients sign with a pseudonymous key, giving the server a stable key thumbprint for per-client rate limiting, and the client gets a higher rate limit in return.

4.6. Incremental Adoption

These headers are designed for zero-coordination deployment. They are unknown to legacy clients, and an unknown header field is ignored, so servers can add them to existing responses without breaking anything.

Stage 1 - Rate limiting (429): A server adds Accept-Signature-Scheme: hwk to its 429 responses. Legacy clients slow down as before. Signature-aware clients sign requests and get higher per-key rate limits. The server gains per-client rate limiting without requiring registration or API keys.

Stage 2 - Authentication (401): The server starts requiring signatures on some paths, returning 401 with Accept-Signature-Scheme: hwk. It can include WWW-Authenticate alongside for legacy clients that have other auth mechanisms. Signature-aware clients sign; legacy clients fall back to bearer tokens or other schemes.

Stage 3 - Identity (401): The server advertises Accept-Signature-Scheme: jwks_uri, jwt, x509 on sensitive paths, requiring verifiable client identity. The server can now make identity-based policy decisions without pre-registration.

Each stage is independently deployable. A server can use stage 1 on all endpoints while using stage 3 on admin endpoints. No bilateral agreements or client coordination required.

4.7. Coexistence with WWW-Authenticate

These headers and WWW-Authenticate ([RFC9110], Section 11.6.1) are independent header fields; a response MAY include both. A client that understands Signature-Key processes the Accept-Signature-* headers; a legacy client processes WWW-Authenticate. Neither header's presence invalidates the other.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Accept-Signature-Scheme: jwks_uri, jwt
Accept-Signature-Alg: ES256

What a client that understands both mechanisms does depends on what the WWW-Authenticate auth-scheme does. Nothing on the wire says which, and this document defines no signal for it, but a client only faces the choice for a scheme it already understands.

  • Where the challenge is an authentication or authorization challenge, such as Basic or Bearer, the two are alternatives. The client SHOULD sign the request rather than present the credential: a signature demonstrates possession of a private key over this request, whereas a bearer credential authenticates whoever holds it, and signing puts no credential on the wire that the exchange did not require. A response carrying both does not assert that the two grant the same access, so a client that needs what only the credential grants MAY present it instead, and a server that is not satisfied by the choice challenges again.

  • Where the challenge is not an authentication or authorization challenge, such as the payment challenge defined by the Micropayment Protocol ([I-D.ryan-httpauth-payment]), the two are complements: satisfying one does not satisfy the other, and a client that wants the resource satisfies both.

A 402 response MAY include a payment mechanism such as x402 [x402] or the Micropayment Protocol ([I-D.ryan-httpauth-payment]) alongside a signature challenge. Payment is not authentication, so this is the complementary case and a client satisfies both:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="x7Tg2pLq", method="example",
    request="eyJhbW91bnQiOiIxMDAw..."
Accept-Signature-Scheme: hwk

4.8. Examples

Pseudonymous access:

HTTP/1.1 401 Unauthorized
Accept-Signature-Scheme: hwk

Identity with algorithm restriction:

HTTP/1.1 401 Unauthorized
Accept-Signature-Scheme: jwks_uri, jwt
Accept-Signature-Alg: ES256

Rate limiting with pseudonymous upgrade:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Accept-Signature-Scheme: hwk

Payment with pseudonymous authentication:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="x7Tg2pLq", method="example",
    request="eyJhbW91bnQiOiIxMDAw..."
Accept-Signature-Scheme: hwk

4.9. Client Processing

When a client receives a response containing Accept-Signature-Scheme (Section 4.1), it MAY retry the request with an HTTP Message Signature using any listed Signature-Key scheme it can satisfy.

[RFC9421] Section 5.2 defines the processing of Accept-Signature by the client. A client MAY ignore Accept-Signature-Scheme and Accept-Signature-Alg, and MUST ignore tokens within them that it does not recognize.

If a client already knows which schemes and algorithms the server accepts (from a previous interaction or metadata), it MAY sign the initial request directly without waiting for a challenge response.

A conforming verifier, when presented with a well-formed request bearing an unknown or unregistered scheme, returns unsupported_scheme and an Accept-Signature-Scheme header naming what it accepts. This exercises the unknown-scheme path as a matter of defined behavior.

When a 429 response includes both Retry-After and Accept-Signature-Scheme, the client MAY retry one time with a signed request without waiting for the Retry-After interval. Signing the request provides a key thumbprint that enables per-client rate limiting, which may result in a higher rate limit for the client.

A server MAY return a 429 response without Accept-Signature-Scheme to a signed request when it wants to rate-limit the client regardless of signing. In this case, the client MUST respect Retry-After as usual.

Open Issue: Should this specification define a baseline HTTP Message Signatures profile (minimum covered components, timestamp requirements, verification steps), or is that always the responsibility of the protocol using these headers? See GitHub issue #7.

5. Signature-Error HTTP Response Header

When a server rejects a signed request due to a signature-related error, the response SHOULD include the Signature-Error header. A server MAY omit it where returning diagnostic detail to an unauthenticated caller is itself judged a disclosure risk, accepting that clients then cannot self-diagnose. The response status code is typically 400 Bad Request, since the signature or keying material is malformed or invalid. A server MAY use 401 Unauthorized for recoverable errors (e.g., unsupported_algorithm, unsupported_scheme, invalid_input) where the client can retry with corrected parameters.

5.1. Header Structure

The Signature-Error header is a Dictionary ([RFC8941], Section 3.2) with the following member:

  • error (REQUIRED): A Token ([RFC8941], Section 3.3.4) indicating the error code.

Additional members are defined per error code. Recipients MUST ignore unknown members.

Signature-Error: error=unsupported_algorithm

The Signature-Error header is the authoritative source for machine-readable error information. The client MUST NOT depend on the response body for error handling.

5.2. Response Body

Servers SHOULD use Problem Details [RFC9457] (application/problem+json) for the response body when returning Signature-Error, and MAY use another representation where content negotiation or an existing error format requires it. The header, not the body, is the interoperable carrier. Where a Problem Details body is returned, its type member MUST be a URN of the form urn:ietf:params:sig-error:<error-code>, where <error-code> matches the error value in the header; a type of any other form cannot be interpreted against this document's registry.

{
  "type": "urn:ietf:params:sig-error:unsupported_algorithm",
  "title": "Unsupported signature algorithm",
  "status": 400,
  "detail": "The server does not support RS256"
}

Extension members in the Problem Details object MAY duplicate information from the Signature-Error header for convenience. When the header and body conflict, the header takes precedence.

5.3. Access Denied

When the server successfully verifies the client's signature and identity but denies access based on policy (e.g., the client is not authorized for this resource), the server returns 403 Forbidden. This is not a signature error — the authentication succeeded but authorization was denied. The response MUST NOT include an Accept-Signature-Scheme header, an Accept-Signature-Alg header, or a Signature-Error header.

5.4. Error Codes

5.4.1. unsupported_algorithm

The signing algorithm used by the client is not supported by the server.

The response SHOULD include an Accept-Signature-Alg header (Section 4.2) naming the algorithms the server accepts.

Signature-Error: error=unsupported_algorithm
Accept-Signature-Alg: Ed25519, ES256

This error also covers a JWK whose key type the server does not implement (Section 3.3). Because a fully-specified algorithm identifier determines the key type, the accompanying Accept-Signature-Alg tells the client which key types are usable without a separate list: a client offered Ed25519 learns that an OKP key on the Ed25519 curve is accepted.

5.4.2. unsupported_scheme

The Signature-Key scheme presented by the client is not supported by the server.

The response SHOULD include an Accept-Signature-Scheme header (Section 4.1) naming the schemes the server accepts.

Signature-Error: error=unsupported_scheme
Accept-Signature-Scheme: jwks_uri, jwt

This error is recoverable. A server MAY return it with 401 Unauthorized so the client can retry with an accepted scheme.

5.4.3. cache_miss

A cache identifier presented with the cached scheme (Section 3.11) could not be resolved to a cached assertion. It is unknown, has been evicted, or failed integrity or decryption.

Signature-Error: error=cache_miss

This error is recoverable and carries no additional members. The caller retries the request presenting the assertion in full (for example via the jwt scheme). A verifier MAY return it with 401 Unauthorized. A verifier MUST NOT treat an unresolved cache identifier as an authorization failure, and MUST NOT return cache_miss for an assertion that resolved but failed validation.

5.4.4. invalid_signature

The HTTP Message Signature is missing, malformed, or cryptographic verification failed. This includes missing Signature, Signature-Input, or Signature-Key headers, an expired created timestamp, or a signature that does not verify.

Signature-Error: error=invalid_signature

5.4.5. invalid_input

The Signature-Input is missing required covered components.

  • required_input (RECOMMENDED): An Inner List of String ([RFC8941], Section 3.1.1) listing the covered components the server requires. A server SHOULD include this member, and MAY omit it where enumerating its requirements to an unauthenticated caller is judged a disclosure risk; a client then has to discover the required components by other means.
Signature-Error: error=invalid_input,
    required_input=("@method" "@authority" "@path"
    "signature-key" "content-digest")

5.4.6. invalid_request

The request is malformed or missing required information unrelated to signature verification — such as missing query parameters or an unsupported content type.

Signature-Error: error=invalid_request

5.4.7. invalid_key

The public key in Signature-Key could not be parsed, is expired, or does not meet the server's trust requirements.

Signature-Error: error=invalid_key

5.4.8. unknown_key

The public key from Signature-Key does not match any key at the client's jwks_uri (applicable when the client uses scheme=jwks_uri). The server SHOULD re-fetch the JWKS once before returning this error, to handle key rotation.

Signature-Error: error=unknown_key

5.4.9. issuer_missing

The metadata document fetched during discovery does not contain an issuer member. Applicable to the jwks_uri scheme (Section 3.6), and to the jwt and self-jwt schemes when they discover the issuer's keys through a metadata document.

Signature-Error: error=issuer_missing

5.4.10. issuer_mismatch

The issuer member of the metadata document fetched during discovery does not match the identity the document was fetched under: the id parameter for the jwks_uri scheme, or the iss claim for the jwt and self-jwt schemes.

Signature-Error: error=issuer_mismatch

5.4.11. invalid_jwt

The JWT in the Signature-Key header (when using scheme=jwt or scheme=jkt-jwt) is malformed or its signature verification failed.

Signature-Error: error=invalid_jwt

5.4.12. expired_jwt

The JWT in the Signature-Key header (when using scheme=jwt or scheme=jkt-jwt) has expired (exp claim is in the past).

Signature-Error: error=expired_jwt

6. Signature-Key-Cache Response Header

A verifier that has cached an assertion presented in a signed request, and that was asked to do so by the cache signal on the presented scheme (Section 3.8, Section 3.5), MAY return the Signature-Key-Cache response header to issue the caller a cache identifier for later reference.

Signature-Key-Cache is a Dictionary ([RFC8941], Section 3.2) keyed by the signature label whose assertion was cached.

The member value is the cache identifier itself, a String. It is not a named parameter. The caller presents this same String back to the verifier as the cid parameter of the cached scheme (Section 3.11).

The cache identifier is opaque to the caller. A verifier chooses its own form: a random value indexed in a cache, or a self-contained, integrity-protected, encrypted value that any node in a verifier fleet can resolve without shared cache state. A verifier using the self-contained form SHOULD choose a compact textual encoding, since the identifier is carried on every request that uses it. See Appendix A.8.

The member's parameters describe the cached assertion:

Example:

Signature-Key-Cache: sig1="2f9c8a1e-a7b3";jti="2f9c8a1e";expires=1730000000

Round trip:

The caller presents the assertion in full and asks for a cache identifier. The JWT carries a jti, without which it is not cacheable:

Signature-Key: sig1=jwt;jwt="eyJhbGciOiJFZERTQSJ9...";cache

The verifier caches the assertion and issues a cache identifier for it:

Signature-Key-Cache: sig1="2f9c8a1e-a7b3";jti="2f9c8a1e";expires=1730000000

On subsequent requests the caller presents the cache identifier in place of the assertion, as the cid parameter of the cached scheme. The String is the one the verifier issued, unchanged:

Signature-Key: sig1=cached;cid="2f9c8a1e-a7b3"

Each request is signed as usual, and signature-key remains a covered component, so the cache identifier is signed over on every request that carries it.

The cache identifier's validity never exceeds the cached assertion's expiry. Two expiries are therefore in play and MUST NOT be confused. The cache entry's expiry is the verifier's own retention decision; the assertion's expiry is a property of the assertion. A verifier that still holds the entry MUST resolve it and let the assertion fail validation (Section 6.1), exactly as an expired assertion presented in full would, rather than reporting cache_miss. A verifier that has already evicted the entry returns cache_miss, which is correct: it no longer holds the assertion and cannot say why the assertion would have been rejected. The caller resends in full and receives the validation error.

6.1. Presenting and Resolving a Cached Assertion

A receiver processes a request bearing the cached scheme in two stages, which MUST remain distinct:

  1. Resolution. The signature-verification layer resolves the cache identifier to the cached assertion. If it is unknown, has been evicted, including at the cache entry's own expiry, or (for a self-contained identifier) fails integrity or decryption, resolution fails and the verifier returns cache_miss (Section 5.4.3). On success, the verifier obtains the assertion and its confirmation key and verifies the per-request signature against that key. This stage answers cache hit or cache miss.

  2. Validation. The resolved assertion is validated by the consuming authorization layer identically to an assertion presented in full, including expiry and any other claim checks. Resolution by cache identifier does not move, replace, or defer this validation. This stage answers valid or invalid, and an expired assertion here produces the same error as an expired assertion presented in full.

These two outcomes MUST NOT be conflated. A cache miss (stage 1) means the assertion could not be reconstructed and is recovered by resending it in full. An invalid assertion (stage 2) means the assertion was reconstructed and failed validation and is not recovered by resending it. A verifier MUST NOT report a validation failure as a cache miss, nor a cache miss as an authorization failure.

Because assertions are short-lived, expiry at stage 2 is the freshness check and no separate revocation state is required; the short lifetime is the revocation window. A deployment that issues long-lived assertions MUST either perform a revocation check at stage 2 or disallow caching for those assertions.

6.2. Degradation and Interoperability

Implementation of assertion caching is OPTIONAL. The degradation behavior in this subsection is not.

A verifier that does not implement assertion caching MUST NOT emit Signature-Key-Cache, and MUST reject a request using the cached scheme with unsupported_scheme (Section 5.4.2). A caller MUST NOT present the cached scheme unless a verifier has issued a cache identifier for that assertion. A verifier that implements caching MUST implement the cache_miss path (Section 5.4.3). These rules allow a caching caller and a non-caching verifier, and the reverse, to interoperate without prior negotiation: a caller always falls back to presenting the assertion in full.

7. Security Considerations

7.1. Key Validation

Verifiers MUST validate all cryptographic material before use:

  • hwk: Validate JWK structure and key parameters per [RFC7517]

  • jwks_uri: Verify HTTPS transport and validate fetched JWKS per [RFC7517]

  • jwks: Verify HTTPS transport and validate fetched JWKS per [RFC7517]

  • x509: Validate complete certificate chain per [RFC5280], check revocation status

  • jwt: Verify JWT signature per [RFC7519] and validate embedded JWK per [RFC7517]

  • self-jwt: Verify JWT signature per [RFC7519] using the key discovered from {iss}/.well-known/{dwk}; reuse that key as the HTTP signing key

  • jkt-jwt: Verify JWT signature per [RFC7519] using header jwk, validate thumbprint matches iss per [RFC7638], validate embedded ephemeral JWK per [RFC7517]

7.2. Caching and Performance

Verifiers MAY cache keys to improve performance but MUST implement appropriate cache expiration:

  • jwks_uri: Respect cache-control headers, implement reasonable TTLs. Verifiers MUST NOT refetch a given issuer's JWKS more frequently than once per minute to prevent abuse.

  • jwks: Cache by url; the same cache-control handling and once-per-minute refetch floor as jwks_uri apply.

  • x509: Cache by x5t, invalidate on certificate expiry

  • jwt: Cache embedded keys until JWT expiration

  • self-jwt: Cache discovered keys until JWT expiration; cache by iss + kid

  • jkt-jwt: Cache embedded keys until JWT expiration; cache by iss thumbprint URI

Verifiers MUST implement cache limits. Cache entries are created by unauthenticated callers, so an unbounded cache is a resource exhaustion attack with no work factor for the attacker.

When the Signature-Key scheme is jwks_uri and a cached key matching the JWT kid fails signature verification, the verifier SHOULD refresh the issuer's JWKS once and retry verification before returning unknown_key (if the key is then absent) or invalid_jwt (if verification still fails), subject to the once-per-minute fetch floor and egress admission (Section 7.3) that apply to unknown-kid refreshes. This covers silent re-keying where the issuer replaces key material under the same kid without changing the identifier.

7.3. Scheme-Specific Risks

hwk: No identity verification - suitable only for scenarios where pseudonymous access is acceptable.

jkt-jwt: The security of this scheme depends on the enclave key's private key remaining protected in hardware. If the enclave key is compromised, all delegated ephemeral keys are compromised. Verifiers should be aware that the jkt-jwt scheme implies but does not prove hardware protection — there is no attestation mechanism in this scheme. Unlike the jwt scheme where trust is rooted in a discoverable issuer, jkt-jwt trust is rooted in the key itself. Verifiers MUST understand that any party can create a jkt-jwt — the scheme provides pseudonymous identity, not verified identity. The exp claim on the JWT controls how long the ephemeral key is valid. Shorter lifetimes limit the exposure window if an ephemeral key is compromised, and the lifetime should be no longer than the deployment's re-delegation interval allows. The iss value is a JWK Thumbprint URI — a globally unique, collision-resistant identifier. The verifier MUST always compute the expected iss from the header jwk and compare by string equality — never trust the iss value alone.

jwks_uri: Relies on HTTPS security — vulnerable to DNS/CA compromise. Beyond HTTPS validation, nothing prevents an attacker from copying a client's public keys and serving them from a different domain. Verifiers SHOULD verify that the id parameter in the Signature-Key header matches an expected or authorized origin. A general-purpose verifier that accepts signers it has no prior relationship with has no such list to match against, and cannot apply this check; such a verifier obtains an origin-bound pseudonym rather than an authorized identity, and MUST NOT treat a well-formed id as evidence that the origin authorized the request.

Because the JWKS location (and, for jwks_uri, the metadata document that yields it) is controlled by the asserted signer, an unconstrained verifier can be induced to fetch attacker-chosen internal URLs (SSRF). Verifiers MUST apply egress admission before fetching issuer metadata, a jwks_uri, or a jwks url:

  • Require HTTPS for all outbound fetches.
  • Enforce response-size and timeout limits.
  • Refuse or constrain redirects (at minimum, do not follow redirects to a different host).
  • Reject private, loopback, and link-local destination addresses unless explicitly allowed by deployment configuration.
  • Defend against DNS rebinding by pinning the resolved IP address for the duration of the connection.
  • Treat cross-origin jwks_uri URLs (where the JWKS host differs from the metadata host) as requiring explicit deployment admission.

jwks: The jwks scheme carries the same server-side request forgery exposure as jwks_uri, and arguably more directly, since the verifier fetches a client-supplied URL with no metadata step to anchor it. The egress-admission requirements above apply to jwks without exception. Because the signer's identity is the JWKS URL itself, a verifier that policies by identity is policying against a value the presenter chooses; verifiers SHOULD verify that url matches an expected or authorized origin.

jwt: Delegation trust depends on JWT issuer verification. Verifiers MUST validate JWT signatures and claims before trusting embedded keys.

self-jwt: Trust is rooted entirely in the issuer's JWKS. The same SSRF egress admission requirements that apply to jwks_uri and jwt apply here — the iss and dwk values are asserted by the presenter. Verifiers MUST validate that cnf is absent before treating the scheme as self-issued; a JWT containing cnf MUST be rejected. Short JWT lifetimes are especially important because the signing key also authenticates the HTTP request — compromise of the key is immediately exploitable at both layers.

x509: Requires robust certificate validation including revocation checking. Verifiers MUST NOT skip certificate chain validation.

7.4. Algorithm Selection

The signature algorithm is determined by the key material in Signature-Key. For JWK-based schemes (hwk, jkt-jwt, jwks_uri, jwks, jwt, self-jwt), the algorithm is the fully-specified identifier carried in the JWK alg member, per Algorithm Determination (Section 3.3); verifiers MUST NOT derive it from the key type and curve. For the x509 scheme, the algorithm is determined by the certificate's public key type.

[RFC9421], Section 1.4 offers three ways for an application to establish the algorithm: state it in the alg signature parameter, derive it from the key material, or agree it out of band. This document takes the second, which [RFC9421], Section 3.3.7 develops for JOSE signing algorithms: the algorithm is signaled by the key, and "the explicit alg signature parameter is not used at all when using JOSE signing algorithms".

Signers therefore MUST NOT include the alg parameter in Signature-Input ([RFC9421], Section 2.3), and verifiers MUST ignore it if present and MUST NOT use it to select or validate the algorithm. One source of truth is the point. The two identifier spaces do not correspond — [RFC9421], Section 3.3.7 notes that JWA values are not registered in the HTTP Signature Algorithms registry — so a rule requiring the parameter to agree with the key would have no defined meaning to test against. Ignoring the parameter also forecloses the confused-verifier condition in which one verifier takes the algorithm from the parameter and another from the key.

Algorithm agility depends on the verifier selecting exactly one signature algorithm for a given key. A key whose algorithm is not fully determined by its identifier invites downgrade and confused-verifier conditions, where two verifiers disagree on the operation a signature represents. For this reason this document requires a present, fully-specified alg for conveyed JWKs (Section 3.3). This aligns with [RFC9864], which states that a key is to be used with only a single algorithm unless the use of that key with multiple algorithms has been proven secure, and recommends that the algorithm parameter of a JWK be present.

Verifiers MUST:

  • Take the algorithm from the key's alg member, and reject a key that has none (Section 3.3)

  • Reject a key whose kty or crv is inconsistent with its alg

  • Reject an alg naming an algorithm the verifier does not support, or that its policy declines, reporting unsupported_algorithm (Section 5.4.1) and stating what it does accept in Accept-Signature-Alg (Section 4.2)

7.5. Symmetric Algorithms

Every scheme in this document distributes a public key or a reference to one, and every verification it describes is a public-key operation. A symmetric algorithm has no public key: verifying a MAC requires the same secret used to produce it. Distributing that secret in a request header would hand the verifying party the ability to forge the signature it is checking, and any intermediary that saw the header the same ability. The proof of possession this document relies on would then prove nothing, since possession would no longer be exclusive to the signer.

For this reason symmetric algorithms MUST NOT be used with Signature-Key (Section 3.3). A verifier MUST reject a JWK whose kty is oct or whose alg is a MAC identifier such as HS256. A shared-secret MAC remains available to deployments that have a pre-shared key and use keyid as [RFC9421] describes; it is out of scope here precisely because it needs no key distribution.

7.6. Cache Identifiers

Cache identifiers inherit the request's proof of possession. A cache identifier is presented in the Signature-Key header, which is a covered component, and the request is signed by the agent's confirmation key, so a captured identifier is useless without the corresponding private key, the same property as a captured token. It is covered by the per-request signature and cannot be substituted by an intermediary.

A verifier MUST NOT treat successful resolution as authentication. Resolution answers only which assertion was referenced. The caller is authenticated by the per-request signature, which the verifier MUST verify against the confirmation key carried in the resolved assertion, exactly as it would had the assertion been presented in full. A caller presenting a cache identifier it did not receive therefore fails signature verification, because it does not hold the corresponding private key. No separate check that the assertion belongs to the caller is required, or possible: possession of the confirmation key is that proof.

Cache identifiers MUST be unpredictable to any party other than the verifier. A guessable identifier does not by itself permit impersonation, since the request signature must still verify against the resolved assertion's confirmation key. It does allow an unrelated party to distinguish a cache miss from a signature failure and so learn whether a verifier currently holds a given assertion, and it removes the second line of defence against a verifier that resolves without verifying. An identifier is a lookup key supplied by a remote party and is untrusted input to the cache.

A cache identifier is a stable reference to one assertion and is therefore a correlator across the requests that use it, for the life of the assertion, in the same way that a repeated ETag or session ticket is. It is a fingerprinting surface, but not a new one: it replaces an assertion that carries the confirmation key itself, and a repeated key is at least as strong a correlator as a repeated identifier. Any party that can observe the identifier could have observed the assertion it stands in for. The exposure is therefore bounded above by what the assertion already discloses, and it is bounded below only by the identifier's lifetime: a verifier concerned with correlation by intermediaries MAY issue and rotate distinct identifiers for the same assertion, which the caller cannot detect and need not act on, since it presents whatever it was last issued.

Assertion caching lets a caller create verifier-side state at will. Nothing bounds how many distinct assertions it presents with cache, and a self-issued scheme such as jkt-jwt can mint a fresh jti, and so a fresh cache entry, on every request. The cache limits required by Section 7.2 apply to assertion caching, and a verifier SHOULD bound entries per confirmation key rather than only in aggregate, so that one caller cannot evict every other caller's entries. A verifier is never obliged to issue a cache identifier: cache is a request, not an instruction.

A self-contained cache identifier carries verifier state to itself across a fleet. Such an identifier MUST be integrity-protected and encrypted under keys known only to the verifier fleet, so that it cannot be forged or read by any other party, and one that fails integrity or decryption MUST be treated as a cache miss (Section 5.4.3).

7.7. Post-Quantum Key and Signature Sizes

Post-quantum keys and signatures are substantially larger than classical ones. ML-DSA public keys are 1312, 1952, and 2592 octets for the three parameter sets, and signatures are larger still, and other post-quantum schemes are larger again. The cost that matters here is size rather than verification time: ML-DSA verification is comparable to Ed25519, so what a deployment must plan for is bytes on the wire. Two consequences follow. First, an inline key conveyed with the hwk scheme, together with the signature, can approach or exceed HTTP header size limits imposed by servers, proxies, and intermediaries. Deployments conveying large keys SHOULD prefer a discovery scheme (jwks_uri or jwks), which conveys a reference rather than the key itself, so that the key material does not traverse a header. A deployment MAY keep an inline scheme where it controls the whole request path and has confirmed the headers fit, trading the discovery fetch for header size. Second, the HTTP Message Signature itself is carried in a header regardless of scheme and is large for post-quantum algorithms; discovery does not mitigate this, and operators should size header buffers to accommodate post-quantum signatures where such algorithms are in use.

7.8. Signature-Key Integrity

The Signature-Key header MUST be included as a covered component in Signature-Input:

Signature-Input: sig=("@method" "@authority" "@path" "signature-key"); created=1732210000

If signature-key is not covered, an attacker can modify the header without invalidating the signature. Attacks include:

Scheme substitution: An attacker extracts the public key from an hwk scheme and republishes it via jwks_uri under their own identity, causing verifiers to attribute the request to the attacker.

Identity substitution: An attacker modifies the id parameter in a jwks_uri scheme to point to their own metadata endpoint that returns the same public key, impersonating a different signer.

Verifiers MUST reject requests where signature-key is not a covered component. There is no deployment in which accepting an uncovered Signature-Key is safe: both attacks above succeed against any verifier that does so, and neither is detectable after the fact.

8. Privacy Considerations

8.1. Pseudonymity vs. Identity

The hwk and jkt-jwt schemes enable pseudonymous operation where the signer's identity is not disclosed. Verifiers should be aware that:

  • A server can track a client across requests by JWK Thumbprint ([RFC7638]). If a client uses the same key across multiple servers, those servers could correlate the client's activity. Clients MUST use distinct keys for distinct servers to prevent cross-server correlation of pseudonymous identity.

  • The jkt-jwt thumbprint is stable across sessions (tied to the enclave key), enabling long-term tracking even when ephemeral keys rotate.

  • Verifiers should not log or retain pseudonymous keys beyond operational necessity.

The jwks_uri, jwks, x509, jwt, and self-jwt schemes reveal signer identity. When a client presents its identity via these schemes, the server learns the client's HTTPS URL or certificate subject, revealing which software is making the request. Servers SHOULD NOT disclose client identity information to third parties without the client operator's consent.

8.2. Key Discovery Tracking

The jwks_uri, jwks, jwt, self-jwt, and x509 schemes require verifiers to fetch resources from signer-controlled URLs. This creates tracking vectors:

  • Signers can observe when and from where keys are fetched. In particular, when a server fetches a client's JWKS from jwks_uri at verification time, the fetch reveals to the JWKS host that someone is verifying signatures for that client.

  • Verifiers should cache keys to minimize fetches.

  • Verifiers may wish to use shared caching infrastructure to reduce fingerprinting.

8.3. JWT Contents

JWTs in the jwt scheme may contain additional claims beyond cnf. Verifiers should:

  • Only process claims necessary for verification

  • Not log or retain unnecessary JWT claims

  • Be aware that JWT contents are visible to network observers unless using TLS

9. IANA Considerations

9.1. HTTP Field Name Registration

This document registers the following header fields in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" defined in [RFC9110].

Header field name: Signature-Key

Applicable protocol: http

Status: standard

Author/Change controller: IETF

Specification document(s): [this document]

Header field name: Signature-Error

Applicable protocol: http

Status: standard

Author/Change controller: IETF

Specification document(s): [this document]

Header field name: Signature-Key-Cache

Applicable protocol: http

Status: standard

Author/Change controller: IETF

Specification document(s): [this document]

Header field name: Accept-Signature-Scheme

Applicable protocol: http

Status: standard

Author/Change controller: IETF

Specification document(s): [this document]

Header field name: Accept-Signature-Alg

Applicable protocol: http

Status: standard

Author/Change controller: IETF

Specification document(s): [this document]

9.2. Signature-Key Scheme Registry

This document establishes the "HTTP Signature-Key Scheme" registry. This registry allows for the definition of additional key distribution schemes beyond those defined in this document.

9.2.1. Registration Procedure

New scheme registrations follow the Specification Required policy ([RFC8126], Section 4.6). See Section 9.5 for instructions to the designated expert.

9.2.2. Initial Registry Contents

Table 3
Scheme Description Reference
hwk Header Web Key - inline public key [this document]
jkt-jwt JKT JWT Self-Issued Key Delegation - enclave-backed delegation [this document]
jwks_uri JWKS URI Discovery - key discovery via metadata [this document]
jwks Direct JWKS - JWKS fetched directly from an HTTPS URL that is also the signer identity [this document]
jwt JWT Confirmation Key - delegated key in JWT [this document]
self-jwt Self-Issued JWT - signer and issuer are the same party [this document]
x509 X.509 Certificate - PKI certificate chain [this document]
cached Cached Assertion - reference to an assertion the verifier has cached [this document]

9.2.3. Registration Template

Scheme Name:
The token value used in the Signature-Key header
Description:
A brief description of the scheme
Specification:
Reference to the specification defining the scheme
Parameters:
List of parameters defined for this scheme

9.3. URN Sub-namespace Registration

This document registers the following URN sub-namespace in the "IETF URN Sub-namespace for Registered Protocol Parameter Identifiers" registry defined in [RFC3553].

Registry name: sig-error

Specification: [this document]

Repository: [this document], Section on Error Codes

Index value: Values are registered in the "Signature Error Code" registry defined in this document.

The URN pattern is urn:ietf:params:sig-error:<error-code>, where <error-code> corresponds to a value in the Signature Error Code registry. These URNs are used as Problem Details type values ([RFC9457]) in response bodies accompanying Signature-Error headers.

9.4. Signature Error Code Registry

This document establishes the "Signature Error Code" registry. New values may be registered following the Expert Review policy ([RFC8126], Section 4.5). See Section 9.5 for instructions to the designated expert.

9.4.1. Initial Registry Contents

Table 4
Value Description Reference
unsupported_algorithm Signing algorithm not supported [this document]
unsupported_scheme Signature-Key scheme not supported [this document]
cache_miss Cache identifier could not be resolved [this document]
invalid_signature Signature missing, malformed, or verification failed [this document]
invalid_input Missing required covered components [this document]
invalid_request Missing required info unrelated to signature [this document]
invalid_key Key cannot be parsed or doesn't meet trust requirements [this document]
unknown_key Key not found at jwks_uri [this document]
issuer_missing Metadata document lacks an issuer member [this document]
issuer_mismatch Metadata document issuer does not match the discovery identity [this document]
invalid_jwt JWT malformed or signature verification failed [this document]
expired_jwt JWT expired [this document]

9.4.2. Registration Template

Value:
The error code token used in the Signature-Error header and the urn:ietf:params:sig-error: URN
Description:
A brief description of the error condition and when a verifier generates it
Reference:
Reference to the document or specification defining the error code

9.5. Designated Expert Instructions

Registration requests for the registries established by this document are evaluated by a designated expert appointed by the IESG. The HTTP Signature-Key Scheme registry uses the Specification Required policy ([RFC8126], Section 4.6); the Signature Error Code registry uses the Expert Review policy ([RFC8126], Section 4.5).

Registration requests should be sent to IANA, which will forward them to the designated expert. The expert is expected to respond within two weeks. Denials should include an explanation and, if applicable, suggestions for how the request could be revised to be successful.

For all registrations, the designated expert should verify that:

  • The proposed value conforms to the registry's syntax and is not confusingly similar to an existing entry.
  • The registration does not duplicate the semantics of an existing entry without clear justification.

For the HTTP Signature-Key Scheme registry, the expert should additionally verify that the referenced specification is stable and freely available, describes the scheme in sufficient detail that interoperable, independent implementations are possible, and defines:

  • How the verifier obtains the public key and establishes its authenticity.
  • All parameters used by the scheme, including which are required and which are optional.
  • The security and privacy properties of the scheme, including the trust model (e.g., pseudonymous, URI-bound, or PKI-anchored) and any risks introduced by network fetches during verification.

For the Signature Error Code registry, the expert should additionally verify that:

  • The error code is a lowercase token using only lowercase letters and underscore, consistent with the registry's existing entries.
  • The error condition is broadly applicable to signed HTTP message exchanges rather than specific to a single application. The condition need not be a signature verification failure; it may concern any part of the exchange the verifier rejects, as invalid_request does.
  • The description makes clear when a verifier generates the error, and the error does not leak sensitive information to unauthenticated callers.

10. Document History

Note: This section is to be removed before publishing as an RFC.

Not backward compatible with -07. Breaking changes are listed first.

Breaking changes:

Other changes:

11. Acknowledgments

The author would like to thank Joshua Gay and Yaron Sheffer for their feedback on this specification.

12. References

12.1. Normative References

[IANA.JOSE.Algorithms]
IANA, "JSON Web Signature and Encryption Algorithms", <https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms>.
[RFC3553]
Mealling, M., Masinter, L., Hardie, T., and G. Klyne, "An IETF URN Sub-namespace for Registered Protocol Parameters", BCP 73, RFC 3553, DOI 10.17487/RFC3553, , <https://www.rfc-editor.org/info/rfc3553>.
[RFC5280]
Cooper, D., Santesson, S., Farrell, S., Boeyen, S., Housley, R., and W. Polk, "Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, , <https://www.rfc-editor.org/info/rfc5280>.
[RFC7517]
Jones, M., "JSON Web Key (JWK)", RFC 7517, DOI 10.17487/RFC7517, , <https://www.rfc-editor.org/info/rfc7517>.
[RFC7518]
Jones, M., "JSON Web Algorithms (JWA)", RFC 7518, DOI 10.17487/RFC7518, , <https://www.rfc-editor.org/info/rfc7518>.
[RFC7519]
Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, DOI 10.17487/RFC7519, , <https://www.rfc-editor.org/info/rfc7519>.
[RFC7638]
Jones, M. and N. Sakimura, "JSON Web Key (JWK) Thumbprint", RFC 7638, DOI 10.17487/RFC7638, , <https://www.rfc-editor.org/info/rfc7638>.
[RFC7800]
Jones, M., Bradley, J., and H. Tschofenig, "Proof-of-Possession Key Semantics for JSON Web Tokens (JWTs)", RFC 7800, DOI 10.17487/RFC7800, , <https://www.rfc-editor.org/info/rfc7800>.
[RFC8126]
Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, , <https://www.rfc-editor.org/info/rfc8126>.
[RFC8725]
Sheffer, Y., Hardt, D., and M. Jones, "JSON Web Token Best Current Practices", BCP 225, RFC 8725, DOI 10.17487/RFC8725, , <https://www.rfc-editor.org/info/rfc8725>.
[RFC8941]
Nottingham, M. and P. Kamp, "Structured Field Values for HTTP", RFC 8941, DOI 10.17487/RFC8941, , <https://www.rfc-editor.org/info/rfc8941>.
[RFC9110]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, , <https://www.rfc-editor.org/info/rfc9110>.
[RFC9421]
Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP Message Signatures", RFC 9421, DOI 10.17487/RFC9421, , <https://www.rfc-editor.org/info/rfc9421>.
[RFC9457]
Nottingham, M., Wilde, E., and S. Dalal, "Problem Details for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, , <https://www.rfc-editor.org/info/rfc9457>.
[RFC9864]
Jones, M.B. and O. Steele, "Fully-Specified Algorithms for JSON Object Signing and Encryption (JOSE) and CBOR Object Signing and Encryption (COSE)", RFC 9864, DOI 10.17487/RFC9864, , <https://www.rfc-editor.org/info/rfc9864>.
[RFC9964]
Prorock, M. and O. Steele, "ML-DSA for JSON Object Signing and Encryption (JOSE) and CBOR Object Signing and Encryption (COSE)", RFC 9964, DOI 10.17487/RFC9964, , <https://www.rfc-editor.org/info/rfc9964>.

12.2. Informative References

[I-D.hardt-email-verification]
Hardt, D. and S. Goto, "Email Verification Protocol", Work in Progress, Internet-Draft, draft-hardt-email-verification-01, , <https://datatracker.ietf.org/doc/html/draft-hardt-email-verification-01>.
[I-D.hardt-oauth-aauth-protocol]
Hardt, D., "AAuth Protocol", Work in Progress, Internet-Draft, draft-hardt-oauth-aauth-protocol-09, , <https://datatracker.ietf.org/doc/html/draft-hardt-oauth-aauth-protocol-09>.
[I-D.richer-oauth-httpsig]
Richer, J., Parecki, A., Bastian, P., Skokan, F., and C. Bormann, "OAuth Proof of Possession Tokens with HTTP Message Signatures", Work in Progress, Internet-Draft, draft-richer-oauth-httpsig-03, , <https://datatracker.ietf.org/doc/html/draft-richer-oauth-httpsig-03>.
[I-D.ryan-httpauth-payment]
Ryan, B., Moxey, J., Meagher, T., Weinstein, J., and S. Kaliski, "The "Payment" HTTP Authentication Scheme", Work in Progress, Internet-Draft, draft-ryan-httpauth-payment-01, , <https://datatracker.ietf.org/doc/html/draft-ryan-httpauth-payment-01>.
[OpenID.Discovery]
Sakimura, N., Bradley, J., Jones, M., and E. Jay, "OpenID Connect Discovery 1.0", , <https://openid.net/specs/openid-connect-discovery-1_0.html>.
[RFC5077]
Salowey, J., Zhou, H., Eronen, P., and H. Tschofenig, "Transport Layer Security (TLS) Session Resumption without Server-Side State", RFC 5077, DOI 10.17487/RFC5077, , <https://www.rfc-editor.org/info/rfc5077>.
[RFC7435]
Dukhovni, V., "Opportunistic Security: Some Protection Most of the Time", RFC 7435, DOI 10.17487/RFC7435, , <https://www.rfc-editor.org/info/rfc7435>.
[RFC7541]
Peon, R. and H. Ruellan, "HPACK: Header Compression for HTTP/2", RFC 7541, DOI 10.17487/RFC7541, , <https://www.rfc-editor.org/info/rfc7541>.
[RFC7924]
Santesson, S. and H. Tschofenig, "Transport Layer Security (TLS) Cached Information Extension", RFC 7924, DOI 10.17487/RFC7924, , <https://www.rfc-editor.org/info/rfc7924>.
[RFC8414]
Jones, M., Sakimura, N., and J. Bradley, "OAuth 2.0 Authorization Server Metadata", RFC 8414, DOI 10.17487/RFC8414, , <https://www.rfc-editor.org/info/rfc8414>.
[RFC8446]
Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, , <https://www.rfc-editor.org/info/rfc8446>.
[RFC8701]
Benjamin, D., "Applying Generate Random Extensions And Sustain Extensibility (GREASE) to TLS Extensibility", RFC 8701, DOI 10.17487/RFC8701, , <https://www.rfc-editor.org/info/rfc8701>.
[RFC9170]
Thomson, M. and T. Pauly, "Long-Term Viability of Protocol Extension Mechanisms", RFC 9170, DOI 10.17487/RFC9170, , <https://www.rfc-editor.org/info/rfc9170>.
[RFC9204]
Krasic, C., Bishop, M., and A. Frindell, Ed., "QPACK: Field Compression for HTTP/3", RFC 9204, DOI 10.17487/RFC9204, , <https://www.rfc-editor.org/info/rfc9204>.
[x402]
x402 Foundation, "x402: HTTP 402 Payment Protocol", , <https://docs.x402.org>.

Appendix A. Design Rationale

A.1. Why jwks_uri Instead of Inline JWKS?

The jwks_uri and jwt schemes reference a jwks_uri property in the .well-known metadata document rather than embedding the JWKS directly in the metadata. This separation of concerns is deliberate:

  1. Independent key rotation: Keys can be rotated by updating the JWKS endpoint without modifying the .well-known metadata document. This decouples key lifecycle management from configuration management, allowing operations teams to rotate keys on their own schedule without redeploying metadata.

  2. Independent management: The .well-known metadata document and the JWKS can be hosted, managed, and secured by different systems or teams. For example, an identity team may manage keys while a platform team manages service metadata.

  3. Caching semantics: The JWKS endpoint can have its own cache-control headers tuned for key rotation frequency (e.g., short TTLs during a rotation event), independent of the .well-known document's caching policy.

  4. Consistency with existing standards: This approach mirrors the pattern established by OpenID Connect Discovery [OpenID.Discovery] and OAuth Authorization Server Metadata [RFC8414], which both use jwks_uri in metadata documents for the same reasons.

A.2. Why Both jwks and jwks_uri?

The jwks and jwks_uri schemes occupy adjacent points on a simplicity/decoupling axis. jwks_uri fetches a .well-known metadata document and follows its jwks_uri property to the keys, so identity (the id origin) is separate from key location (the discovered JWKS URL); the two can be managed and rotated independently, as Appendix A.1 describes. jwks collapses this to a single HTTPS fetch whose URL is both identity and key location. Neither dominates: jwks removes a hop and all metadata hosting at the cost of tying identity to the JWKS URL, while jwks_uri keeps identity stable across key-location changes at the cost of a discovery step. Offering both lets a signer choose the tradeoff rather than having it imposed.

A.3. Why a Separate Header?

An alternative design would extend Signature-Input with additional parameters to carry key material. This was considered and rejected for several reasons:

  1. Parameter complexity: Each scheme has a different set of parameters (e.g., hwk needs kty, crv, x, y; jwks_uri needs id, dwk, kid; jwt needs a full JWT string). Overloading Signature-Input with all possible key parameters across all schemes would make the Signature-Input grammar unwieldy and harder to parse.

  2. Separation of concerns: Signature-Input describes what is signed and how (covered components, algorithm, timestamps). Signature-Key describes who signed it and where to find the key. These are distinct concerns, and separating them into distinct headers makes each easier to understand and process independently.

  3. Extensibility: A separate header with a scheme registry allows new key distribution mechanisms to be added without modifying the Signature-Input grammar. New schemes can define arbitrary parameters without coordination with RFC 9421.

  4. Multiple signatures: With a dictionary structure keyed by label, each signature can use a different scheme. This is natural in a separate header but would create complex nesting if embedded in Signature-Input.

A.4. Why Schemes Instead of Just a Key and Key ID?

A simpler design would define Signature-Key as carrying only a public key (or key reference) and a key identifier, without the scheme abstraction. This was considered insufficient because:

  1. Trust model varies: A bare key tells the verifier nothing about the trust model. Is this a pseudonymous key to be evaluated on its own merits (hwk)? A key bound to a discoverable identity (jwks_uri)? A delegated key from an authority (jwt)? A certificate-backed key (x509)? The scheme token tells the verifier which verification procedure to follow and what trust properties the key carries.

  2. Verification procedure differs: Each scheme has a fundamentally different verification path. hwk requires no external fetches. jwks_uri requires metadata discovery. x509 requires certificate chain validation. jwt requires JWT signature verification before the HTTP signature can be verified. A key-and-ID-only design would push scheme detection to heuristics or out-of-band agreement.

  3. Security properties differ: Without an explicit scheme, a verifier cannot distinguish between a self-asserted key and a CA-certified key. The scheme makes the trust model explicit, allowing verifiers to enforce policy (e.g., "only accept jwt or x509 schemes").

  4. Interoperability: Explicit schemes create clear interoperability targets. Two implementations that support the jwt scheme know exactly what to expect from each other. Without schemes, the same key material could be interpreted differently by different implementations.

A.5. Why a Scheme Token Instead of a Header per Scheme?

An alternative design would define a distinct header field per key distribution mechanism (for example Signature-Key-Hwk, Signature-Key-Jwt) rather than one Signature-Key header carrying a scheme token. HTTP field names are the most heavily exercised extension point on the web, and [RFC9170] identifies header fields in email and HTTP as the canonical case of an extension point that stays usable because recipients routinely ignore fields they do not understand. A per-mechanism header would ride that established tolerance rather than a scheme registry with little traffic. This document nonetheless uses a single header with a scheme token, for four reasons.

  1. Ignorability is the wrong property for keying material. Header-name extensibility works because an unknown header can be safely ignored. Signature-Key carries mandatory keying material. A verifier that ignores an unknown Signature-Key-X header fails verification exactly as if no key were present, and cannot tell the client why. With a scheme token, the verifier knows a key was offered under a scheme it does not implement and returns unsupported_scheme with an Accept-Signature-Scheme header naming what it accepts. [RFC9170] Section 4.4 notes that effective feedback is what keeps the surrounding extension machinery working.

  2. The covered-component invariant stays fixed. The scheme substitution and identity substitution attacks in Section 7.8 depend on signature-key being a covered component. With one header, signature-key is a single stable identifier in the signature base across every scheme. Per-scheme headers would make the covered component name mechanism-dependent and would require a rule for a request that covers one key header while leaving another uncovered.

  3. Label correlation stays simple. The dictionary is keyed by signature label so that each signature in a multi-signature message carries its own keying material (Section 3.2). Resolving a label's key is one dictionary lookup. Across N per-scheme headers it becomes a scan with a cross-header collision policy.

  4. Fewer namespaces. [RFC9170] Section 4.1 observes that a smaller number of widely applicable extension points is exercised more, and ossifies less, than many specialized ones. The scheme is the single namespace for key distribution mechanisms, referenced from both this header and the Accept-Signature-Scheme response header. Per-scheme header names would add a second namespace for the same axis.

To keep the scheme registry usable despite its narrow traffic, this document relies on defined behavior rather than on greasing [RFC8701]. Unknown and unregistered schemes have a single mandatory outcome (Section 5.4.2), verifiers dispatch through the registry rather than a fixed branch set, and conformance testing exercises the unknown-scheme path directly (Section 4.9). Reserving grease values was considered and not adopted: a mandatory, conformance-tested reject path exercises the same handling that grease would provoke, whereas a reserved grease token tends to become a filterable synonym for "unknown" that implementations special-case, the outcome [RFC9170] Section 3.3 cautions against.

A.6. Why Accept-Signature-Scheme and Accept-Signature-Alg Are Separate Headers

Earlier versions of this document carried the server's scheme requirement in a sigkey parameter on Accept-Signature, and the accepted sets in supported_schemes and supported_algorithms members of Signature-Error. Both were replaced by two response header fields. The reasons are worth recording, because at first reading a new header field looks like the more invasive choice.

  1. A set cannot be expressed in a parameter. A Structured Fields parameter value is a bare Item ([RFC8941], Section 3.1.2) and cannot be an Inner List. A parameter can therefore name one scheme, never a set. This is not a limitation of sigkey: the alg parameter of Accept-Signature ([RFC9421], Section 5.1) is singular for the same structural reason. Any design that puts the accepted set in a parameter slot is constrained to a single value, whatever it is named.

  2. The Accept-Signature member value is already spoken for. The alternative to a parameter is a Dictionary member value, which may be an Inner List. In Accept-Signature that position holds the covered components, so it is unavailable. Carrying the accepted sets there would mean overloading one list with two unrelated kinds of token.

  3. Capability does not vary by signature label. A new Dictionary keyed by label could carry a list per label, but the schemes and algorithms a verifier accepts are a property of the verifier, not of a particular signature in a particular message. Keying by label would invite the question of what a client should do when two labels disagree, and would create a per-label negotiation surface with no deployment behind it. Accept-Signature retains label granularity for what it is for: which components are to be covered, and the per-label parameters of [RFC9421] Section 5.1.

  4. One dimension per field is the established HTTP pattern. HTTP negotiates with Accept, Accept-Encoding, Accept-Language, and Accept-Charset: one field per dimension, each a list, each independently ignorable. A single field carrying both dimensions as Dictionary members would be equally valid Structured Fields, but it would depart from that pattern, and it would couple the two: a server with no algorithm constraint could not simply omit the algorithm field.

  5. The same syntax serves the challenge and the error. This is the property the previous design could not have. supported_schemes and supported_algorithms lived inside Signature-Error, so a client could reach them only by first being rejected. A header field can be sent on an ordinary challenge, before the client has signed anything, and on an error response, and means the same thing in both places. A client selects a scheme and an algorithm before it signs rather than after a failure. [RFC9170] Section 4.4 observes that an extension point stays usable when it is exercised routinely; a set that can be learned only through failure is exercised only when something goes wrong.

  6. Removing the error members avoids two ways to say one thing. Once the headers exist, retaining the members would leave two encodings of the same information, differing only in when they may appear. [RFC9170] Section 4.1 notes that redundant, partially-used mechanisms ossify. Signature-Error now states what went wrong, and the Accept-Signature-* fields state what would succeed.

Adding header fields here does not contradict the argument against per-scheme key headers in Appendix A.5. That argument turns on ignorability being the wrong property for mandatory keying material: a verifier that silently ignores an unknown Signature-Key-X cannot tell the client why verification failed. These fields carry advisory capability rather than keying material, and ignorability is exactly the property wanted. A client that does not understand Accept-Signature-Scheme ignores it and behaves as it did before, which is also what a client that cannot satisfy any listed scheme does. Nothing is lost by ignoring a hint, whereas ignoring a key is a silent failure. The two conclusions differ because the requirements differ.

A.7. Layered Cryptographic Agility

Post-quantum protection is applied to artifacts whose authenticity must survive into the quantum era: durable, consequential, or retained assertions. The per-request HTTP Message Signature is ephemeral, replay-bounded proof of possession that no verifier accepts outside its short window; it MAY continue to use a classical algorithm such as Ed25519 in a post-quantum deployment. Ed25519 is not itself post-quantum, and the claim here concerns the threat model for short-lived authentication signatures rather than the algorithm's quantum resistance. Caching (Section 6) makes the post-quantum assertion, whose signature is large (Section 7.7), affordable to reference on each request without retransmitting it.

What makes the classical hot path acceptable is the lifetime of the key, not the lifetime of the assertion carrying it. A confirmation key's public value travels in the assertion and can be collected by any observer today, so the exposure is bounded by how long a verifier will still accept that key, not by how long ago it was seen. A classical key is safe against an attacker who later recovers private keys from harvested public keys for as long as it is replaced — a freshly generated key bound in a newly issued assertion — faster than that recovery is possible. Expiring the assertion does not achieve this on its own: a signer that rebinds one long-lived key into each successive assertion leaves that key acceptable for as long as it keeps doing so, whatever exp any individual assertion carries. The jkt-jwt scheme (Section 3.5) has the intended shape by construction, the stable enclave key establishing identity while the request key in cnf is generated per delegation. A deployment that instead reuses a confirmation key across assertions gains nothing from short assertion lifetimes, and should choose that key's algorithm against its true acceptance window.

The saving is on both sides, though for a different reason on each. The jkt-jwt scheme exists because signing in a secure enclave is slow, so the enclave key signs once and delegates to a fast ephemeral key: the signer's per-request cost is time. The verifier's is size. Post-quantum verification is not the expensive part — ML-DSA verification is comparable to Ed25519 — and the burden is the assertion (Section 7.7) that would otherwise be retransmitted and reparsed on every request. A verifier that caches the delegation resolves it once, fetching, parsing, computing the thumbprint, and verifying the signature, and references the result thereafter, while the caller stops paying the assertion's bytes on each request. Delegation on the signer's side and caching on the verifier's answer the same shape of problem from opposite ends.

Long-term non-repudiation is out of scope for this layer and is provided above it. The per-request signature is not the durable evidentiary record. Where long-term, tamper-evident proof of what an agent did is required, it is provided by a transparency ledger that records actions and is itself protected for the long term, not by retaining and later trusting individual per-request signatures. Because durable evidence lives in the ledger, the per-request signature has no long-term evidentiary value to protect, and the classical hot path needs no post-quantum sealing at this layer. The ledger, being the durable artifact, is where post-quantum protection is applied for audit. The ledger itself is outside the scope of this document.

A.8. Why the Verifier Issues the Cache Identifier

The cache identifier is issued by the verifier, not chosen by the caller. The caller already has an identifier for the assertion, its jti, so it is worth stating why that one is not used and why the naming is the verifier's.

  1. The cache is the verifier's. The identifier is a lookup key into storage the verifier owns and evicts from. Only the verifier knows its namespace, its eviction policy, and whether an entry survives. A caller-chosen key would name something the caller cannot observe.

  2. A jti is unique per issuer, not per verifier. The jti claim is unique within the scope of its issuer ([RFC7519], Section 4.1.7). A verifier accepting assertions from many issuers can be presented with the same jti by unrelated callers, so jti alone is not a key. A verifier could key on issuer and jti together, but that is a compound the caller would have to reconstruct exactly, and it fixes the key's form for every verifier rather than letting each choose.

  3. A caller-chosen identifier is attacker-chosen input to a lookup. If callers named their own entries, one caller could name an entry another caller had created, and cache lookup would become a probe for whether a given assertion is held. Verifier-issued identifiers keep the namespace under the verifier's control, which is what allows the unpredictability requirement in Security Considerations to mean anything.

  4. Only the verifier can make the identifier self-contained. A verifier that encodes the assertion's state into the identifier, encrypted to itself, can resolve it on any node without shared cache state. That is possible only if the verifier constructs it. TLS session tickets take the same approach for the same reason (Appendix A.9).

The jti is still useful to the caller, and is echoed in Signature-Key-Cache so that a caller with several assertions in flight can associate the identifier it receives with the assertion it sent. It identifies the assertion; the cache identifier identifies the verifier's cached copy of it.

A.9. Precedents for Assertion Caching

The reference-and-fallback shape of the cached scheme follows established practice.

The TLS Cached Information Extension [RFC7924] lets a client tell the server it already holds an object and reference it, with a defined fallback when the server's copy does not match. The cached scheme has the same shape: a reference in place of the object, and a defined miss path back to sending it in full.

TLS session tickets ([RFC5077]; [RFC8446], Section 4.6.1) are server-minted, optionally self-contained encrypted references that any node in a fleet can honor without shared state. This is the precedent for permitting a self-contained cache identifier (Section 6) and for the fleet case in which an identifier minted by one node is presented to another that cannot resolve it (Section 5.4.3).

Entity tags ([RFC9110], Section 8.8.3) are the closest HTTP precedent and the source of three properties adopted here. An entity tag is opaque to the recipient, which echoes it back unmodified; it is issued by the party that holds the thing it names; and a recipient that does not recognize one falls back to the full representation rather than failing. A cache identifier behaves the same way: opaque, verifier-issued, and recoverable by resending the assertion (Section 5.4.3).

One property of entity tags is deliberately not adopted. A strong entity tag is commonly derived from the representation, so two parties holding the same bytes compute the same tag. Applying that to assertions, by using a thumbprint of the assertion as the cache identifier, would make the identifier computable by anyone who has seen the assertion, including any intermediary it passed through. Cache lookup would then become a probe any such party could run to learn whether a verifier currently holds a given assertion, and the identifier would no longer be unpredictable (Section 7.6). A content-derived identifier also fixes one construction for every verifier, foreclosing the self-contained encrypted form that the session-ticket precedent supports. The cache identifier is therefore verifier-minted and unpredictable rather than derived from the assertion, and the assertion's own identity is carried separately by the jti echoed in Signature-Key-Cache (Appendix A.8).

The TLS mechanisms above are cited as context for the design, not as normative dependencies.

A.10. Why Strings Instead of Byte Sequences for hwk?

The hwk parameters use structured field strings rather than byte sequences. JWK key values are base64url-encoded per [RFC7517], while structured field byte sequences use base64 encoding per [RFC8941]. Using strings allows implementations to pass JWK values directly without converting between base64url and base64, avoiding a potential source of encoding bugs.

A.11. Why alg Is Required on Every Conveyed Key

The alternative considered was deriving the algorithm from the key's structure, as JOSE implementations commonly do today.

That derivation works, for two of the four key types this document can convey. No registered JOSE signing algorithm pairs the Ed25519 curve with anything but Ed25519, or P-256 with anything but ES256; for OKP and EC keys, kty and crv between them name exactly one algorithm. Stating otherwise would be wrong, and the requirement here is not justified by an ambiguity that does not exist in those cases.

It fails for the other two. An RSA key has no crv, and kty of RSA determines neither the padding scheme nor the hash, so the same key admits RS256, PS256, RS512, and more. The AKP key type of [RFC9964] covers several ML-DSA parameter sets, none of them recoverable from kty. In both cases the key does not say what it is for, and nothing else in these schemes does either.

The requirement is uniform rather than restricted to those two cases, for four reasons.

The set of underdetermined types grows. A conditional rule needs a table of which key types are self-determining, maintained in this document, revised whenever an algorithm is registered. Post-quantum and hybrid algorithms are arriving now, and AKP is already an entry in that table. A uniform requirement accommodates a new algorithm with no change here at all.

Negotiation must be a total comparison. Accept-Signature-Alg (Section 4.2) advertises algorithm identifiers with no key attached, because the server has no key to attach. A client decides which of its keys to present by comparing them against that list. Were keys permitted to omit alg, every client would have to implement the derivation table purely to perform that comparison, and would have to implement it identically to every server, or the two would disagree about what the client holds. Requiring alg makes the comparison a string match over one vocabulary on both sides.

One code path in the verifier. Determination becomes a single lookup followed by a single consistency check, with no branch on whether this key type happens to be self-determining, and no second path that a test suite must cover and an implementer may get wrong. The redundancy between alg and kty/crv is then available as a check rather than as an alternative (Section 3.3).

The cost to the signer is nil. A signer constructs the key it conveys inline, mints the assertion that carries its confirmation key, and publishes the JWKS its identity resolves to. There is no third party to persuade and no existing artifact that must change, because a deployment adopting this document is publishing keys for a purpose that did not previously exist.

[RFC9864] RECOMMENDS rather than requires that a JWK carry alg, permitting a deployment to rely instead on "some other mechanism for ensuring that the key is used as intended". This document tightens that RECOMMENDED to a requirement. It does so as a matter of choice, on the grounds above, and not because the exception [RFC9864] allows is unavailable — for OKP and EC keys it plainly is available.

Authors' Addresses

Dick Hardt
Hellō
Thibault Meunier
Cloudflare