<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" version="3" ipr="trust200902" docName="draft-hardt-httpbis-signature-key-08" submissionType="IETF" category="std" xml:lang="en" indexInclude="true">

<front>
<title abbrev="Signature-Keys">HTTP Signature Keys</title><seriesInfo value="draft-hardt-httpbis-signature-key-08" stream="IETF" status="standard" name="Internet-Draft"/>
<author initials="D." surname="Hardt" fullname="Dick Hardt"><organization>Hellō</organization><address><postal><street/>
</postal><email>dick.hardt@gmail.com</email>
</address></author><author initials="T." surname="Meunier" fullname="Thibault Meunier"><organization>Cloudflare</organization><address><postal><street/>
</postal><email>ot-ietf@thibault.uk</email>
</address></author><date/>
<area>Applications and Real-Time</area>
<workgroup>HTTP</workgroup>
<keyword>http</keyword>
<keyword>signature</keyword>
<keyword>key</keyword>
<keyword>jwk</keyword>
<keyword>jwt</keyword>

<abstract>
<t>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.</t>
</abstract>

<note><name>Discussion Venues</name>
<t><em>Note: This section is to be removed before publishing as an RFC.</em></t>
<t>Source for this draft and an issue tracker can be found at <eref target="https://github.com/dickhardt/signature-key">https://github.com/dickhardt/signature-key</eref>.</t>
</note>

</front>

<middle>

<section anchor="conventions-and-definitions"><name>Conventions and Definitions</name>
<t>{::boilerplate bcp14-tagged}</t>
</section>

<section anchor="introduction"><name>Introduction</name>
<t>HTTP Message Signatures <xref target="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.</t>
<t>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.</t>
<t><strong>A verifier may have no prior relationship with the signer.</strong> 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.</t>
<t><strong>The key material and its trust model are separate questions.</strong> "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.</t>
<t><strong>Key conveyance must be covered by the signature it introduces.</strong> 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. <xref target="signature-key-integrity"/> describes the scheme-substitution and identity-substitution attacks this prevents.</t>
<t><strong>The verifier must be able to state what it will accept.</strong> 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.</t>
<t>This document defines:</t>

<ul>
<li><t><strong>Signature-Key</strong> (<xref target="signature-key-http-request-header"/>) — a request header that distributes public keys for HTTP Message Signature verification. The header supports eight schemes, each designed for different trust models and operational requirements:</t>

<ol spacing="compact">
<li><strong>Header Web Key (hwk)</strong> - Self-contained public keys for pseudonymous verification</li>
<li><strong>JKT JWT (jkt-jwt)</strong> - Self-issued key delegation via JWK Thumbprint JWTs ("jacket jot")</li>
<li><strong>JWKS URI (jwks_uri)</strong> - Identified signers with key discovery via metadata</li>
<li><strong>Direct JWKS (jwks)</strong> - Keys fetched directly from an HTTPS URL that is also the signer identity</li>
<li><strong>JWT (jwt)</strong> - Delegated keys embedded in signed JWTs for horizontal scale</li>
<li><strong>Self-Issued JWT (self-jwt)</strong> - Self-signed JWTs where the signer and issuer are the same party</li>
<li><strong>X.509 (x509)</strong> - Certificate-based verification with PKI trust chains</li>
<li><strong>Cached Assertion (cached)</strong> - A reference to an assertion the verifier has already cached</li>
</ol></li>
</ul>
<t>Additional schemes may be defined through the IANA registry established by this document.</t>

<ul>
<li><t><strong>Accept-Signature-Scheme</strong> and <strong>Accept-Signature-Alg</strong> (<xref target="accept-signature-scheme-and-accept-signature-alg-response-headers"/>) — response headers stating the Signature-Key schemes and the signature algorithms the server accepts. Both are Lists, so a server states its full accepted set and a client selects a scheme and an algorithm before signing.</t>
</li>
<li><t><strong>Signature-Error</strong> (<xref target="signature-error-http-response-header"/>) — a response header that provides structured error information when signature verification fails, enabling clients to diagnose and correct signing issues.</t>
</li>
<li><t><strong>Signature-Key-Cache</strong> (<xref target="signature-key-cache-response-header"/>) — a response header by which a verifier issues the caller an opaque cache identifier for an assertion it has cached, so that later requests can reference the assertion instead of resending it.</t>
</li>
</ul>
<t>Three properties follow from the gaps above and are held as invariants throughout this document:</t>

<ol>
<li><t>Keying material or its identifier is conveyed in the Signature-Key header, which is a covered component (<xref target="signature-key-integrity"/>). The signature protects the key or identifier that introduces it.</t>
</li>
<li><t>The trust model is a scheme, not a fixed choice. A single header (<xref target="signature-key-http-request-header"/>) 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.</t>
</li>
<li><t>Unknown schemes and algorithms have defined, mandatory feedback. A verifier that does not implement a presented scheme returns <tt>unsupported_scheme</tt> with the set it supports (<xref target="unsupported-scheme"/>). A verifier requires fully-specified algorithms and rejects underspecified ones (<xref target="algorithm-determination"/>). The extension point is exercised on ordinary traffic rather than only at the moment a new value is first deployed, per the guidance of <xref target="RFC9170"/>.</t>
</li>
</ol>
<t>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.</t>
<t>The mechanisms in this document were designed as general-purpose building blocks and are used by other specifications. In the AAuth protocol <xref target="I-D.hardt-oauth-aauth-protocol"/>, all parties communicate using Signature-Key to distribute the keys that verify their signed requests. Email Verification <xref target="I-D.hardt-email-verification"/> uses the <tt>hwk</tt> 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.</t>
</section>

<section anchor="signature-key-http-request-header"><name>Signature-Key HTTP Request Header</name>
<t>The <tt>Signature-Key</tt> header provides the public key or key reference needed to verify an HTTP Message Signature. It is a Structured Field Dictionary <xref target="RFC8941"/> keyed by signature label, where each member describes how to obtain the verification key for the corresponding signature.</t>
<t><strong>Format:</strong></t>

<artwork><![CDATA[Signature-Key: <label>=<scheme>;<parameters>...
]]>
</artwork>
<t>Where:
- <tt>&lt;label&gt;</tt> (dictionary key) matches the label in Signature-Input and Signature headers
- <tt>&lt;scheme&gt;</tt> (token) identifies the key distribution scheme
- <tt>&lt;parameters&gt;</tt> are semicolon-separated key-value pairs whose values are structured field strings or byte sequences, varying by scheme</t>
<t>Multiple keys are comma-separated per the dictionary format. See <xref target="RFC8941"/> for definitions of dictionary, token, string, and byte sequence.</t>
<t><strong>Unknown schemes:</strong></t>
<t>A verifier that selects a member whose scheme token it does not implement, including any unregistered value, MUST reject the request with a <tt>Signature-Error</tt> of <tt>error=unsupported_scheme</tt> (<xref target="unsupported-scheme"/>) 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 (<xref target="scheme-registry"/>) rather than a fixed set of branches, so that unknown schemes take this defined path.</t>
<t>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 <xref target="label-consistency"/>. 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.</t>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Input: sig=("@method" "@authority" "@path" "signature-key"); created=1732210000
Signature: sig=:MEQCIA5...
Signature-Key: sig=hwk;kty="OKP";crv="Ed25519";x="JrQLj..."
]]>
</artwork>
<t><strong>Label Correlation:</strong></t>
<t>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.</t>
<t>Verifiers MUST:</t>

<ol>
<li><t>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.</t>
</li>
<li><t>Parse Signature-Key as a Structured Fields Dictionary</t>
</li>
<li><t>For each label being verified, select the Signature-Key dictionary member with the same name</t>
</li>
<li><t>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</t>
</li>
</ol>
<blockquote><t><strong>Note:</strong> A verifier might choose to verify only a subset of labels present (e.g., the application-required signature); labels not verified can be ignored.</t>
</blockquote><t>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.</t>

<section anchor="label-consistency"><name>Label Consistency</name>
<t>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.</t>
</section>

<section anchor="multiple-signatures"><name>Multiple Signatures</name>
<t>The dictionary format supports multiple signatures per message. Each signature has its own dictionary member keyed by its unique label:</t>

<artwork><![CDATA[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"
]]>
</artwork>
<t>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 <tt>signature-key</tt>. This ensures all signatures protect the integrity of all key material. See <xref target="signature-key-integrity"/> in Security Considerations. Alternative key distribution mechanisms outside this specification may be used for scenarios requiring independent signature addition.</t>
</section>

<section anchor="algorithm-determination"><name>Algorithm Determination</name>
<t>Several schemes in this document convey or reference a JSON Web Key <xref target="RFC7517"/>. For any such JWK, the signature algorithm MUST be fully determined by the key, meaning the JWK carries an <tt>alg</tt> 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 <tt>alg</tt> member is absent or whose <tt>alg</tt> 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.</t>
<t>Algorithm identifiers in this document are values from the IANA "JSON Web Signature and Encryption Algorithms" registry <xref target="IANA.JOSE.Algorithms"/>, established by <xref target="RFC7518"/> and extended since. This document uses the JOSE signing algorithms of <xref target="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 (<xref target="RFC9421"/>, Section 6.2), and that the <tt>alg</tt> signature parameter is not used at all with JOSE signing algorithms. This document therefore does not use that registry; see <xref target="algorithm-selection"/>.</t>
<t>In particular:</t>

<ul>
<li><t>The <tt>none</tt> algorithm MUST NOT be used, nor any algorithm whose JOSE Implementation Requirement is <tt>Prohibited</tt>. <xref target="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 <tt>Accept-Signature-Alg</tt> (<xref target="accept-signature-alg"/>); listing <tt>none</tt> would advertise that the server takes an unsigned request for a signed one.</t>
</li>
<li><t>The polymorphic <tt>EdDSA</tt> identifier MUST NOT be used. Use the fully-specified <tt>Ed25519</tt> or <tt>Ed448</tt> identifiers registered by <xref target="RFC9864"/> instead.</t>
</li>
<li><t>For RSA keys, the <tt>alg</tt> MUST name both the padding scheme and the hash, for example <tt>PS256</tt> (RSASSA-PSS with SHA-256) or <tt>RS256</tt> (RSASSA-PKCS1-v1_5 with SHA-256). A key type of <tt>RSA</tt> alone is insufficient, since it determines neither the padding nor the hash.</t>
</li>
<li><t>The JOSE ECDSA identifiers <tt>ES256</tt>, <tt>ES384</tt>, and <tt>ES512</tt> are already fully specified and are used as-is.</t>
</li>
<li><t>Symmetric algorithms MUST NOT be used. The <tt>oct</tt> key type and the JOSE MAC identifiers <tt>HS256</tt>, <tt>HS384</tt>, and <tt>HS512</tt> 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 <tt>Accept-Signature-Alg</tt> (<xref target="accept-signature-alg"/>). See <xref target="symmetric-algorithms"/>.</t>
</li>
</ul>
<t><xref target="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 <tt>alg</tt> 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.</t>
<t>This document raises that RECOMMENDED to a requirement, and does so uniformly rather than conditionally on key type.</t>
<t>For OKP and EC keys, <tt>kty</tt> and <tt>crv</tt> do determine the algorithm between them, and so are an instance of the "other mechanism" <xref target="RFC9864"/> permits. No registered JOSE signing algorithm pairs the <tt>Ed25519</tt> curve with anything but <tt>Ed25519</tt>, or <tt>P-256</tt> with anything but <tt>ES256</tt>. They do not determine it for an RSA key, which has no <tt>crv</tt> and whose padding scheme and hash are both free, nor for the <tt>AKP</tt> key type of <xref target="RFC9964"/>, which covers several ML-DSA parameter sets. Requiring <tt>alg</tt> of every conveyed key, including those it would be possible to derive, is a deliberate choice; <xref target="why-alg-is-required"/> gives the reasons. <xref target="I-D.richer-oauth-httpsig"/> arrives at the same requirement independently for the keys it binds as JWKs.</t>
<t>A verifier MUST reject a key whose <tt>alg</tt> names an algorithm it does not support, reporting <tt>unsupported_algorithm</tt> (<xref target="unsupported_algorithm"/>). <tt>Accept-Signature-Alg</tt> (<xref target="accept-signature-alg"/>) 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.</t>
<t>Where the <tt>alg</tt> 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 <tt>cnf.jwk</tt>, 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 <tt>id</tt>, <tt>kid</tt>, and <tt>dwk</tt>, which identify a key rather than describe it, so the <tt>alg</tt> member of the resolved JWKS entry is the only channel. A deployment adopting one of those schemes MUST publish a key that carries <tt>alg</tt>. Pointing at an existing key that omits it does not satisfy this document, even though such a key is valid under <xref target="RFC7517"/>, where <tt>alg</tt> is OPTIONAL. Only the key the <tt>kid</tt> 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.</t>
<t>A JWK also carries key-structure members: <tt>kty</tt>, which <xref target="RFC7517"/> requires, and <tt>crv</tt> where the key type has one. Because a fully-specified <tt>alg</tt> 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 <tt>kty</tt> and, where present, <tt>crv</tt> are consistent with <tt>alg</tt>, and MUST reject the key if they are not. A JWK with an <tt>alg</tt> of <tt>ES256</tt> and a <tt>kty</tt> of <tt>RSA</tt> is inconsistent and MUST be rejected, as is one with an <tt>alg</tt> of <tt>ES256</tt> and a <tt>crv</tt> of <tt>P-384</tt>. Rejecting on disagreement prevents a key from being used under either of two conflicting interpretations.</t>
<t>Post-quantum signature algorithms are accommodated by this rule without special treatment. For example, the ML-DSA identifiers <tt>ML-DSA-44</tt>, <tt>ML-DSA-65</tt>, and <tt>ML-DSA-87</tt> registered by <xref target="RFC9964"/> are fully specified and are used directly as the JWK <tt>alg</tt> value. The requirement is algorithm-agnostic and accommodates additional post-quantum and hybrid algorithms as they are registered.</t>
<t>A verifier that encounters a JWK whose <tt>kty</tt> it does not implement, including the <tt>AKP</tt> key type defined by <xref target="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 <tt>unsupported_algorithm</tt> (<xref target="unsupported_algorithm"/>). Absence of support for a key type is a reason to decline, not a parsing failure.</t>
<t>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 <tt>kid</tt> without requiring any other member to be usable, and MUST NOT fail because an unselected member names a <tt>kty</tt> or <tt>alg</tt> it does not implement.</t>
<t>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.</t>
<t>Within a single JWK, a member a verifier does not understand is ignored, as <xref target="RFC7517"/>, Section 4 requires. A member this document forbids, such as <tt>kid</tt> in the hwk scheme (<xref target="header-web-key-hwk"/>), is different: it is understood and rejected, not unknown and ignored.</t>
</section>

<section anchor="header-web-key-hwk"><name>Header Web Key (hwk)</name>
<t>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 <xref target="RFC7517"/>.</t>
<t><strong>Parameters by key type:</strong></t>
<t>OKP (Octet Key Pair):</t>

<ul>
<li><t><tt>kty</tt> (REQUIRED, String) - "OKP"</t>
</li>
<li><t><tt>crv</tt> (REQUIRED, String) - Curve name (e.g., "Ed25519")</t>
</li>
<li><t><tt>x</tt> (REQUIRED, String) - Public key value</t>
</li>
<li><t><tt>alg</tt> (REQUIRED, String) - Fully-specified algorithm identifier (e.g., "Ed25519")</t>
</li>
</ul>

<artwork><![CDATA[Signature-Key: sig=hwk;kty="OKP";crv="Ed25519";x="JrQLj5P...";alg="Ed25519"
]]>
</artwork>
<t>EC (Elliptic Curve):</t>

<ul>
<li><t><tt>kty</tt> (REQUIRED, String) - "EC"</t>
</li>
<li><t><tt>crv</tt> (REQUIRED, String) - Curve name (e.g., "P-256", "P-384")</t>
</li>
<li><t><tt>x</tt> (REQUIRED, String) - X coordinate</t>
</li>
<li><t><tt>y</tt> (REQUIRED, String) - Y coordinate</t>
</li>
<li><t><tt>alg</tt> (REQUIRED, String) - Fully-specified algorithm identifier (e.g., "ES256")</t>
</li>
</ul>

<artwork><![CDATA[Signature-Key: sig=hwk;kty="EC";crv="P-256";x="f83OJ3D...";y="x_FEzRu...";alg="ES256"
]]>
</artwork>
<t>RSA:</t>

<ul>
<li><t><tt>kty</tt> (REQUIRED, String) - "RSA"</t>
</li>
<li><t><tt>n</tt> (REQUIRED, String) - Modulus</t>
</li>
<li><t><tt>e</tt> (REQUIRED, String) - Exponent</t>
</li>
<li><t><tt>alg</tt> (REQUIRED, String) - Fully-specified algorithm identifier naming padding and hash (e.g., "PS256")</t>
</li>
</ul>

<artwork><![CDATA[Signature-Key: sig=hwk;kty="RSA";n="0vx7agoebGcQ...";e="AQAB";alg="PS256"
]]>
</artwork>
<t><strong>Constraints:</strong></t>

<ul>
<li><t>The <tt>alg</tt> parameter MUST be present and fully specified. The inline JWK is subject to Algorithm Determination (<xref target="algorithm-determination"/>).</t>
</li>
<li><t>The <tt>kid</tt> parameter MUST NOT be used. The key is carried inline, so there is nothing for an identifier to select, and a <tt>kid</tt> that disagrees with the inline key has no defined resolution.</t>
</li>
</ul>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Privacy-preserving agents that avoid identity disclosure</t>
</li>
<li><t>Experimental or temporary access without registration</t>
</li>
<li><t>Rate limiting and reputation building on a per-key basis</t>
</li>
</ul>
</section>

<section anchor="jkt-jwt-scheme"><name>JKT JWT Self-Issued Key Delegation (jkt-jwt)</name>
<t>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.</t>
<t>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).</t>
<t>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:</t>

<ol spacing="compact">
<li>The enclave generates a long-lived key pair (the identity key)</li>
<li>The device generates an ephemeral key pair in software (the signing key)</li>
<li>The enclave signs a JWT binding the ephemeral key via the <tt>cnf</tt> claim</li>
<li>HTTP requests are signed with the fast ephemeral key</li>
<li>The JWT proves the ephemeral key was authorized by the enclave key</li>
</ol>
<t>The enclave key's JWK Thumbprint URI (<tt>urn:jkt:&lt;hash-algorithm&gt;:&lt;thumbprint&gt;</tt>) serves as a stable, pseudonymous device identity. Verifiers build trust in this identity over time (TOFU — Trust On First Use <xref target="RFC7435"/>).</t>
<t><strong>Parameters:</strong></t>

<ul>
<li><t><tt>jwt</tt> (REQUIRED, String) - Compact-serialized JWT</t>
</li>
<li><t><tt>cache</tt> (OPTIONAL, Boolean) - As for the jwt scheme (<xref target="jwt-confirmation-key-jwt"/>): the caller indicates it can present a cache identifier on subsequent requests, using the cached scheme (<xref target="cached-scheme"/>), and requests that the verifier issue one. A JWT presented with <tt>cache</tt> MUST contain a <tt>jti</tt> claim. See <xref target="signature-key-cache-response-header"/>.</t>
</li>
</ul>
<t><strong>JWT requirements:</strong></t>
<t>Header:</t>

<ul>
<li><t><tt>typ</tt> (REQUIRED) - Identifies the thumbprint hash algorithm. Defined values: <tt>jkt-s256+jwt</tt> (SHA-256), <tt>jkt-s512+jwt</tt> (SHA-512). Implementations MUST support <tt>jkt-s256+jwt</tt> and MAY support additional algorithms.</t>
</li>
<li><t><tt>alg</tt> (REQUIRED) - Signature algorithm used by the enclave key</t>
</li>
<li><t><tt>jwk</tt> (REQUIRED) - JWK public key of the enclave/identity key (the key that signed this JWT)</t>
</li>
</ul>
<t>Payload:</t>

<ul>
<li><t><tt>iss</tt> (REQUIRED) - JWK Thumbprint URI of the signing key, in the format <tt>urn:jkt:&lt;hash-algorithm&gt;:&lt;thumbprint&gt;</tt> where the thumbprint is computed per <xref target="RFC7638"/>. The hash algorithm in the URN MUST match the algorithm indicated by the JWT <tt>typ</tt>. The verifier knows the hash algorithm from the <tt>typ</tt> it accepted, computes the thumbprint of the header <tt>jwk</tt>, prepends the known <tt>urn:jkt:&lt;hash-algorithm&gt;:</tt> prefix, and compares to <tt>iss</tt> by string equality.</t>
</li>
<li><t><tt>iat</tt> (REQUIRED) - Issued-at timestamp</t>
</li>
<li><t><tt>exp</tt> (REQUIRED) - Expiration timestamp</t>
</li>
<li><t><tt>cnf</tt> (REQUIRED) - Confirmation claim <xref target="RFC7800"/> containing <tt>jwk</tt>: the ephemeral public key delegated for HTTP message signing</t>
</li>
<li><t><tt>jti</tt> (OPTIONAL) - Unique identifier for this delegation. REQUIRED when the JWT is presented with the <tt>cache</tt> parameter, since a cacheable assertion must be identifiable (<xref target="signature-key-cache-response-header"/>). The <tt>iss</tt> thumbprint does not serve: it names the enclave key, so successive delegations from one enclave share it.</t>
</li>
</ul>
<t>The <tt>sub</tt> claim is not used. The identity is the enclave key itself, fully represented by the <tt>iss</tt> thumbprint.</t>
<t>The header <tt>jwk</tt> and the delegated key in <tt>cnf.jwk</tt> are each subject to Algorithm Determination (<xref target="algorithm-determination"/>).</t>
<t><strong>JWT Type Values:</strong></t>
<t>The <tt>typ</tt> value encodes both the purpose and the thumbprint hash algorithm:</t>
<table>
<thead>
<tr>
<th><tt>typ</tt></th>
<th>Hash Algorithm</th>
<th><tt>iss</tt> prefix</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>jkt-s256+jwt</tt></td>
<td>SHA-256</td>
<td><tt>urn:jkt:sha-256:</tt></td>
</tr>

<tr>
<td><tt>jkt-s512+jwt</tt></td>
<td>SHA-512</td>
<td><tt>urn:jkt:sha-512:</tt></td>
</tr>
</tbody>
</table><t>The <tt>jkt-</tt> 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 <tt>cnf</tt> claim. The suffix (<tt>s256</tt>, <tt>s512</tt>) identifies the hash algorithm used for the thumbprint. The <tt>typ</tt> and <tt>iss</tt> prefix MUST be consistent.</t>
<t>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 <tt>typ</tt> values following the <tt>jkt-&lt;alg&gt;+jwt</tt> pattern.</t>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig=jkt-jwt;jwt="eyJ..."
]]>
</artwork>
<t>JWT header:</t>

<sourcecode type="json"><![CDATA[{
  "typ": "jkt-s256+jwt",
  "alg": "ES256",
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
    "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
  }
}
]]>
</sourcecode>
<t>JWT payload:</t>

<sourcecode type="json"><![CDATA[{
  "iss": "urn:jkt:sha-256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
  "iat": 1732210000,
  "exp": 1732296400,
  "cnf": {
    "jwk": {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"
    }
  }
}
]]>
</sourcecode>
<t>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 <tt>urn:jkt:sha-256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs</tt>.</t>
<t>The stable (enclave) key algorithm in the JWT <tt>alg</tt> header is determined by what the enclave hardware supports. This document's example uses <tt>ES256</tt> 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 <tt>cnf.jwk</tt> request key algorithm is likewise enclave-determined.</t>
<t><strong>Verification procedure:</strong></t>

<ol>
<li><t>Parse the JWT without verifying the signature</t>
</li>
<li><t>Check the <tt>typ</tt> header (e.g., <tt>jkt-s256+jwt</tt>). Reject if the type is not supported.</t>
</li>
<li><t>Determine the hash algorithm and <tt>iss</tt> prefix from the <tt>typ</tt> (e.g., <tt>jkt-s256+jwt</tt> → SHA-256, <tt>urn:jkt:sha-256:</tt>)</t>
</li>
<li><t>Extract the <tt>jwk</tt> from the JWT header</t>
</li>
<li><t>Compute the JWK Thumbprint (<xref target="RFC7638"/>) of the header <tt>jwk</tt> using the determined hash algorithm</t>
</li>
<li><t>Construct the expected <tt>iss</tt> value by prepending the known prefix to the computed thumbprint</t>
</li>
<li><t>Verify the <tt>iss</tt> claim matches the constructed value by string equality</t>
</li>
<li><t>Verify the JWT signature using the header <tt>jwk</tt></t>
</li>
<li><t>Validate <tt>exp</tt> and <tt>iat</tt> claims per policy</t>
</li>
<li><t>Extract the ephemeral public key from <tt>cnf.jwk</tt></t>
</li>
<li><t>Verify the HTTP Message Signature using the ephemeral key</t>
</li>
</ol>
<t><strong>Caching:</strong></t>
<t>Caching — the cached scheme (<xref target="cached-scheme"/>) together with the <tt>Signature-Key-Cache</tt> response header (<xref target="signature-key-cache-response-header"/>) — 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 (<xref target="pqc-sizes"/>). 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 <tt>cnf.jwk</tt> key.</t>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Devices with hardware-backed secure enclaves delegating to fast ephemeral keys</t>
</li>
<li><t>Persistent pseudonymous identity without requiring registration or authority</t>
</li>
<li><t>Mobile apps, laptops, and IoT devices with enclave-backed identity</t>
</li>
</ul>
</section>

<section anchor="jwks-uri-scheme"><name>JWKS URI Discovery (jwks_uri)</name>
<t>The jwks_uri scheme identifies the signer and enables key discovery via a metadata document containing a <tt>jwks_uri</tt> property.</t>
<t><strong>Parameters:</strong></t>

<ul>
<li><t><tt>id</tt> (REQUIRED, String) - Signer identifier (HTTPS URL)</t>
</li>
<li><t><tt>dwk</tt> (REQUIRED, String) - Dot well-known metadata document name under <tt>/.well-known/</tt></t>
</li>
<li><t><tt>kid</tt> (REQUIRED, String) - Key identifier</t>
</li>
</ul>
<t><strong>Discovery procedure:</strong></t>

<ol>
<li><t>Fetch <tt>{id}/.well-known/{dwk}</tt></t>
</li>
<li><t>Parse as JSON metadata. The document MUST contain <tt>issuer</tt> and <tt>jwks_uri</tt> members. Reject with <tt>issuer_missing</tt> (<xref target="issuer_missing"/>) if <tt>issuer</tt> is absent.</t>
</li>
<li><t>Verify <tt>issuer</tt> equals the <tt>id</tt> parameter, by byte equality as presented. Reject with <tt>issuer_mismatch</tt> (<xref target="issuer_mismatch"/>) if they differ.</t>
</li>
<li><t>Extract <tt>jwks_uri</tt> property</t>
</li>
<li><t>Fetch JWKS from <tt>jwks_uri</tt></t>
</li>
<li><t>Find key with matching <tt>kid</tt></t>
</li>
</ol>
<t>The <tt>issuer</tt> check binds the metadata document to the identity it was fetched under. Without it, a document served at <tt>{id}/.well-known/{dwk}</tt> — through misconfigured shared hosting, a subdomain takeover, or any other means — could point <tt>jwks_uri</tt> at keys that do not belong to <tt>id</tt>, and the verifier would attribute the request accordingly. This is the same check <xref target="RFC8414"/>, Section 3.3 requires of authorization server metadata, and a document conforming to <xref target="RFC8414"/> or OpenID Connect Discovery already carries <tt>issuer</tt>.</t>
<t>The JWK selected from the retrieved JWKS is subject to Algorithm Determination (<xref target="algorithm-determination"/>).</t>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig=jwks_uri;id="https://client.example";dwk="example-configuration";kid="key-1"
]]>
</artwork>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Identified services with stable HTTPS identity</t>
</li>
<li><t>Search engine crawlers and monitoring services</t>
</li>
<li><t>Services requiring explicit entity identification</t>
</li>
</ul>
</section>

<section anchor="jwks-scheme"><name>Direct JWKS (jwks)</name>
<t>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.</t>
<t><strong>Parameters:</strong></t>

<ul>
<li><t><tt>url</tt> (REQUIRED, String) - HTTPS URL of the signer's JWKS</t>
</li>
<li><t><tt>kid</tt> (REQUIRED, String) - Key identifier</t>
</li>
</ul>
<t><strong>Discovery procedure:</strong></t>

<ol>
<li><t>Apply egress admission (<xref target="scheme-specific-risks"/>) to <tt>url</tt></t>
</li>
<li><t>Fetch <tt>url</tt></t>
</li>
<li><t>Parse as a JWKS (<xref target="RFC7517"/>)</t>
</li>
<li><t>Find the key with matching <tt>kid</tt></t>
</li>
</ol>
<t>The JWK selected from the fetched JWKS is subject to Algorithm Determination (<xref target="algorithm-determination"/>).</t>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig=jwks;url="https://client.example/keys.jwks";kid="key-1"
]]>
</artwork>
<t><strong>Identifier semantics:</strong></t>
<t>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 <tt>url</tt>. 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 (<xref target="jwks-uri-scheme"/>), whose indirection exists for that purpose (see <xref target="why-jwks-uri"/>). The jwks scheme trades that decoupling for a single fetch and zero configuration.</t>
<t>The <tt>url</tt> 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.</t>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Signers that want a self-describing identifier with no metadata to host</t>
</li>
<li><t>Deployments where the JWKS URL is an acceptable stable identity</t>
</li>
</ul>
</section>

<section anchor="jwt-confirmation-key-jwt"><name>JWT Confirmation Key (jwt)</name>
<t>The jwt scheme embeds a public key inside a signed JWT using the <tt>cnf</tt> (confirmation) claim <xref target="RFC7800"/>, enabling delegation and horizontal scale.</t>
<t><strong>Parameters:</strong></t>

<ul>
<li><t><tt>jwt</tt> (REQUIRED, String) - Compact-serialized JWT</t>
</li>
<li><t><tt>cache</tt> (OPTIONAL, Boolean) - When true, the caller indicates it can present a cache identifier on subsequent requests, using the cached scheme (<xref target="cached-scheme"/>), and requests that the verifier issue one. Absent means the caller does not want one. Boolean true is indicated by omitting the value (<xref target="RFC8941"/>, Section 4.1.1.2), so the parameter is serialized as <tt>cache</tt> rather than <tt>cache=?1</tt>. Because it is carried in the Signature-Key header, this signal is covered by the per-request signature. A JWT presented with <tt>cache</tt> MUST contain a <tt>jti</tt> claim (<xref target="RFC7519"/>, Section 4.1.7); a verifier MUST NOT issue a cache identifier for a JWT without one. See <xref target="signature-key-cache-response-header"/>.</t>
</li>
</ul>

<artwork><![CDATA[Signature-Key: sig1=jwt;jwt="eyJhbGciOiJFZERTQSJ9...";cache
]]>
</artwork>
<t><strong>JWT requirements:</strong></t>

<ul>
<li><t>MUST contain <tt>cnf.jwk</tt> claim with embedded JWK. The key conveyed in the assertion is subject to Algorithm Determination (<xref target="algorithm-determination"/>).</t>
</li>
<li><t>SHOULD contain <tt>iss</tt> claim (HTTPS URL of the issuer) — using SHOULD rather than MUST allows existing JWT infrastructure to be used without modification</t>
</li>
<li><t>SHOULD contain <tt>dwk</tt> claim (dot well-known metadata document name) — the verifier constructs <tt>{iss}/.well-known/{dwk}</tt> to discover the issuer's <tt>jwks_uri</tt>. Using SHOULD allows deployments where the verifier already knows the issuer's keys.</t>
</li>
<li><t>MUST contain <tt>exp</tt> claim. The assertion carries a confirmation key, and <tt>exp</tt> is what bounds how long that key is accepted; without it the key remains acceptable indefinitely. See <xref target="layered-cryptographic-agility"/>.</t>
</li>
<li><t>SHOULD contain standard claims: <tt>sub</tt>, <tt>iat</tt></t>
</li>
<li><t>Verifiers SHOULD verify the JWT <tt>typ</tt> header parameter has an expected value per deployment policy, following the explicit-typing guidance of <xref target="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.</t>
</li>
</ul>
<blockquote><t><strong>Note:</strong> The mechanism by which the JWT is obtained is out of scope of this specification.</t>
</blockquote><t><strong>Verification procedure:</strong></t>

<ol>
<li><t>Parse the JWT parameter value per <xref target="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.</t>
</li>
<li><t>Verify the JWT <tt>typ</tt> header parameter has an expected value per policy. Reject if unexpected.</t>
</li>
<li><t>Validate <tt>exp</tt> claim if present. Reject if the token has expired.</t>
</li>
<li><t>Verify required claims are present (<tt>cnf.jwk</tt>, plus any claims required by deployment policy). Reject if a required claim is missing.</t>
</li>
<li><t>If <tt>iss</tt> and <tt>dwk</tt> claims are present, fetch <tt>{iss}/.well-known/{dwk}</tt>, parse as JSON metadata, and verify the document's <tt>issuer</tt> member equals the <tt>iss</tt> claim as for the jwks_uri scheme (<xref target="jwks-uri-scheme"/>), rejecting with <tt>issuer_missing</tt> or <tt>issuer_mismatch</tt>. Extract <tt>jwks_uri</tt>, fetch the JWKS from it, and find the key matching <tt>kid</tt> in the JWT header. If <tt>iss</tt> or <tt>dwk</tt> is absent, the verifier MUST obtain the issuer's key through an application-specific mechanism.</t>
</li>
<li><t>Verify JWT signature using the discovered key</t>
</li>
<li><t>Validate remaining JWT claims per policy (<tt>iss</tt>, <tt>sub</tt>, etc.)</t>
</li>
<li><t>Extract JWK from <tt>cnf.jwk</tt></t>
</li>
<li><t>Verify HTTP Message Signature using extracted key</t>
</li>
</ol>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig=jwt;jwt="eyJhbGciOiJFUzI1NiI..."
]]>
</artwork>
<t><strong>JWT payload example:</strong></t>

<sourcecode type="json"><![CDATA[{
  "iss": "https://issuer.example",
  "dwk": "example-configuration",
  "sub": "instance-123",
  "exp": 1732210000,
  "cnf": {
    "jwk": {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"
    }
  }
}
]]>
</sourcecode>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Distributed services with ephemeral instance keys</t>
</li>
<li><t>Delegation scenarios where instances act on behalf of an authority</t>
</li>
<li><t>Short-lived credentials for horizontal scaling</t>
</li>
</ul>
</section>

<section anchor="self-issued-jwt-self-jwt"><name>Self-Issued JWT (self-jwt)</name>
<t>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 <tt>cnf</tt> claim is present — the signing key is the confirmation key.</t>
<t><strong>Parameters:</strong></t>

<ul spacing="compact">
<li><tt>jwt</tt> (REQUIRED, String) - Compact-serialized JWT</li>
</ul>
<t><strong>JWT requirements:</strong></t>

<ul>
<li><t>MUST contain <tt>iss</tt> claim (HTTPS URL of the issuer)</t>
</li>
<li><t>MUST contain <tt>dwk</tt> claim (dot well-known metadata document name) — the verifier constructs <tt>{iss}/.well-known/{dwk}</tt> to discover the issuer's <tt>jwks_uri</tt></t>
</li>
<li><t>MUST have <tt>kid</tt> 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 (<xref target="algorithm-determination"/>).</t>
</li>
<li><t>MUST NOT contain <tt>cnf</tt> claim</t>
</li>
<li><t>MUST contain <tt>exp</tt> claim, bounding how long the assertion is accepted</t>
</li>
<li><t>SHOULD contain standard claims: <tt>sub</tt>, <tt>aud</tt>, <tt>iat</tt></t>
</li>
</ul>
<t>The self-jwt scheme does not support the <tt>cache</tt> 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 <tt>iss</tt> and <tt>kid</tt>. That key is already cacheable on those two values (<xref target="caching-and-performance"/>), 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.</t>

<ul spacing="compact">
<li>Verifiers SHOULD verify the JWT <tt>typ</tt> header parameter has an expected value per deployment policy, following the explicit-typing guidance of <xref target="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.</li>
</ul>
<blockquote><t><strong>Note:</strong> The mechanism by which the JWT is obtained is out of scope of this specification.</t>
</blockquote><t><strong>Verification procedure:</strong></t>

<ol>
<li><t>Parse the JWT parameter value per <xref target="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.</t>
</li>
<li><t>Verify the JWT <tt>typ</tt> header parameter has an expected value per policy. Reject if unexpected.</t>
</li>
<li><t>Validate <tt>exp</tt> claim if present. Reject if the token has expired.</t>
</li>
<li><t>Verify <tt>iss</tt>, <tt>dwk</tt> claims and <tt>kid</tt> JWT header parameter are present. Reject if any is absent.</t>
</li>
<li><t>Verify <tt>cnf</tt> claim is absent. Reject if present.</t>
</li>
<li><t>Construct <tt>{iss}/.well-known/{dwk}</tt>, parse as JSON metadata, and verify the document's <tt>issuer</tt> member equals the <tt>iss</tt> claim as for the jwks_uri scheme (<xref target="jwks-uri-scheme"/>), rejecting with <tt>issuer_missing</tt> or <tt>issuer_mismatch</tt>. Extract <tt>jwks_uri</tt>, fetch the JWKS from it, and find the key matching <tt>kid</tt> from the JWT header. Reject if the key is not found (error: <tt>unknown_key</tt>).</t>
</li>
<li><t>Verify JWT signature using the discovered key.</t>
</li>
<li><t>Validate remaining JWT claims per policy (<tt>sub</tt>, <tt>aud</tt>, etc.)</t>
</li>
<li><t>Verify HTTP Message Signature using the same key from step 6.</t>
</li>
</ol>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig=self-jwt;jwt="eyJhbGciOiJFUzI1NiIsImtpZCI6InIxIn0..."
]]>
</artwork>
<t>JWT header:</t>

<sourcecode type="json"><![CDATA[{
  "alg": "ES256",
  "kid": "r1",
  "typ": "aauth-resource+jwt"
}
]]>
</sourcecode>
<t>JWT payload:</t>

<sourcecode type="json"><![CDATA[{
  "iss": "https://resource.example",
  "dwk": "aauth-resource",
  "aud": "https://agent.example",
  "eid": "evt-abc123",
  "exp": 1732210000
}
]]>
</sourcecode>
<t>The verifier fetches <tt>https://resource.example/.well-known/aauth-resource</tt>, retrieves the JWKS, finds the key with <tt>kid="r1"</tt>, verifies the JWT signature with it, then uses that same key to verify the HTTP Message Signature.</t>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Resources delivering events with application-layer claims that the verifier needs alongside key verification</t>
</li>
<li><t>Clients presenting themselves directly without delegating to a separate authority</t>
</li>
</ul>
</section>

<section anchor="x-509-certificates-x509"><name>X.509 Certificates (x509)</name>
<t>The x509 scheme provides certificate-based verification using PKI trust chains.</t>
<t><strong>Parameters:</strong></t>

<ul>
<li><t><tt>x5u</tt> (REQUIRED, String) - URL to X.509 certificate chain (PEM format, <xref target="RFC7517"/> Section 4.6)</t>
</li>
<li><t><tt>x5t</tt> (REQUIRED, Byte Sequence) - Certificate thumbprint: SHA-256 hash of DER-encoded end-entity certificate</t>
</li>
</ul>
<t><strong>Verification procedure:</strong></t>

<ol>
<li><t>Check cache for certificate with matching <tt>x5t</tt></t>
</li>
<li><t>If not cached or expired, fetch PEM from <tt>x5u</tt></t>
</li>
<li><t>Validate certificate chain to trusted root CA</t>
</li>
<li><t>Check certificate validity and revocation status</t>
</li>
<li><t>Verify <tt>x5t</tt> matches end-entity certificate</t>
</li>
<li><t>Extract public key from end-entity certificate</t>
</li>
<li><t>Verify signature using extracted key</t>
</li>
<li><t>Cache certificate indexed by <tt>x5t</tt></t>
</li>
</ol>
<t>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.</t>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig=x509;x5u="https://client.example/.well-known/cert.pem";x5t=:bWcoon4QTVn8Q6xiY0ekMD6L8bNLMkuDV2KtvsFc1nM=:
]]>
</artwork>
<t><strong>Use cases:</strong></t>

<ul>
<li><t>Enterprise environments with PKI infrastructure</t>
</li>
<li><t>Integration with existing certificate management systems</t>
</li>
<li><t>Scenarios requiring certificate revocation checking</t>
</li>
<li><t>Regulated industries requiring certificate-based authentication</t>
</li>
</ul>
</section>

<section anchor="cached-scheme"><name>Cached Assertion (cached)</name>
<blockquote><t><strong>Editor's Note:</strong> Assertion caching, comprising this scheme, the <tt>cache</tt> signal on the jwt and jkt-jwt schemes, the <tt>Signature-Key-Cache</tt> response header (<xref target="signature-key-cache-response-header"/>), and the <tt>cache_miss</tt> error (<xref target="cache_miss"/>), 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 (<xref target="RFC7541"/>) and QPACK (<xref target="RFC9204"/>) header compression already avoid retransmitting a repeated header field value, entity tags (<xref target="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 (<xref target="pqc-sizes"/>); the shape of the answer is open. Feedback on whether this belongs in this document, and at this layer, is specifically sought.</t>
</blockquote><t>The cached scheme references an assertion the verifier has previously cached and issued a cache identifier for (<xref target="signature-key-cache-response-header"/>), in place of presenting the assertion in full.</t>
<t><strong>Parameters:</strong></t>

<ul spacing="compact">
<li><tt>cid</tt> (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.</li>
</ul>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key: sig1=cached;cid="2f9c8a1e-a7b3"
]]>
</artwork>
<t>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 (<xref target="presenting-and-resolving-a-cached-assertion"/>), obtains the assertion's confirmation key, and verifies the per-request signature against that key. The <tt>signature-key</tt> component is covered by the signature (<xref target="signature-key-integrity"/>), so the cache identifier is signed over and cannot be substituted by an intermediary.</t>
<t>A caller MUST NOT present a cache identifier unless a verifier has issued one for that assertion via Signature-Key-Cache (<xref target="signature-key-cache-response-header"/>). A verifier that does not implement assertion caching treats cached as an unimplemented scheme and returns <tt>unsupported_scheme</tt> (<xref target="unsupported-scheme"/>); the caller then retries with the full assertion.</t>
</section>
</section>

<section anchor="accept-signature-scheme-and-accept-signature-alg-response-headers"><name>Accept-Signature-Scheme and Accept-Signature-Alg Response Headers</name>
<t><xref target="RFC9421"/> Section 5 defines the <tt>Accept-Signature</tt> response header for requesting HTTP Message Signatures. Its signature metadata parameters are Item parameters, whose values are bare Items (<xref target="RFC8941"/>, Section 3.1.2) and cannot be lists. A server therefore cannot use <tt>Accept-Signature</tt> to state that it accepts any of several Signature-Key schemes, nor any of several algorithms: its <tt>alg</tt> parameter names one algorithm.</t>
<t>This document defines two response header fields that carry those sets. Both are List Structured Fields (<xref target="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.</t>
<t>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 <tt>Signature-Error</tt> (<xref target="error-codes"/>) as before.</t>

<section anchor="accept-signature-scheme"><name>Accept-Signature-Scheme</name>
<t><tt>Accept-Signature-Scheme</tt> is a List (<xref target="RFC8941"/>, Section 3.1) of Tokens, each naming a scheme registered in the HTTP Signature-Key Scheme registry (<xref target="scheme-registry"/>). It states the Signature-Key schemes the server accepts.</t>

<sourcecode type="http"><![CDATA[Accept-Signature-Scheme: hwk, jwks_uri, jwt
]]>
</sourcecode>
<t>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 (<xref target="pseudonymity-vs-identity"/>). A client that would be identified under the server's first preference and pseudonymous under its second is entitled to take the second.</t>
<t>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.</t>
<t>Listing the <tt>cached</tt> scheme (<xref target="cached-scheme"/>) states that the server implements assertion caching. A client that sees it can set the <tt>cache</tt> signal (<xref target="jwt-confirmation-key-jwt"/>, <xref target="jkt-jwt-scheme"/>) on its first request rather than probing. <tt>cached</tt> 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 <tt>cached</tt> 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.</t>
</section>

<section anchor="accept-signature-alg"><name>Accept-Signature-Alg</name>
<t><tt>Accept-Signature-Alg</tt> is a List (<xref target="RFC8941"/>, Section 3.1) of Tokens, each a fully-specified identifier from the IANA "JSON Web Signature and Encryption Algorithms" registry <xref target="IANA.JOSE.Algorithms"/> — the same identifiers a conveyed key carries in its <tt>alg</tt> member (<xref target="algorithm-determination"/>), and not those of the HTTP Signature Algorithms registry, which this document does not use (<xref target="algorithm-selection"/>). 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.</t>

<sourcecode type="http"><![CDATA[Accept-Signature-Alg: Ed25519, ES256
]]>
</sourcecode>
<t>Each Token is the registered identifier verbatim, including its case: <tt>ES256</tt>, not <tt>es256</tt>. Structured Field parsing preserves the case of a Token (<xref target="RFC8941"/>, Section 4.2.6), and the comparison a client performs is against the <tt>alg</tt> member of a JWK, a case-sensitive JSON string. A case-folded token names no registered algorithm and matches no key.</t>
<t>Order, unknown-token handling, and the no-recognized-value case are as for <tt>Accept-Signature-Scheme</tt>.</t>
<t>Because a fully-specified algorithm identifier determines the key type and curve (<xref target="algorithm-determination"/>), this list also tells the client which keys are usable, and so which key to generate or select when it holds more than one.</t>
<t><tt>Accept-Signature-Alg</tt> states what the server accepts. The <tt>alg</tt> parameter of <tt>Accept-Signature</tt> (<xref target="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 (<xref target="algorithm-selection"/>): under the JOSE signing algorithms the algorithm is signaled by the key, not requested on the wire. A server that sends <tt>Accept-Signature-Alg</tt> SHOULD NOT send the <tt>alg</tt> parameter, and a client MAY ignore an <tt>alg</tt> received alongside <tt>Accept-Signature-Alg</tt>; the algorithm the client uses is the one its key carries (<xref target="algorithm-determination"/>).</t>
</section>

<section anchor="relationship-to-accept-signature"><name>Relationship to Accept-Signature</name>
<t><tt>Accept-Signature</tt> continues to carry what is to be signed: the covered components, and the per-label parameters of <xref target="RFC9421"/> Section 5.1. The two headers defined here carry what the server will accept in the <tt>Signature-Key</tt> header and in the signature itself. They are independent fields; a response MAY include any combination.</t>
<t>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.</t>
<t><tt>Accept-Signature</tt> also defines a <tt>keyid</tt> parameter (<xref target="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 <tt>Signature-Key</tt>, <tt>keyid</tt> has nothing left to name: a server SHOULD NOT send it, and a client MAY ignore it. If a signer includes <tt>keyid</tt> in <tt>Signature-Input</tt> for a label it also lists in <tt>Signature-Key</tt>, the two MUST identify the same key, and a verifier verifying that label MUST take the key from <tt>Signature-Key</tt>.</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
Accept-Signature: sig1=("@method" "@path" "@authority");created
Accept-Signature-Scheme: jwks_uri, jwt
Accept-Signature-Alg: Ed25519
]]>
</sourcecode>
<t>The client responds with matching labels:</t>

<artwork><![CDATA[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...:
]]>
</artwork>
<t>The <tt>signature-key</tt> covered component is added by the client per this specification's requirement that <tt>signature-key</tt> appear in covered components. The server does not need to list it in <tt>Accept-Signature</tt>.</t>
</section>

<section anchor="sending-on-errors-and-on-challenges"><name>Sending on Errors and on Challenges</name>
<t>Both headers MAY be sent on any response. They are useful on two occasions in particular.</t>
<t>On a challenge, before the client has signed anything, they let the client choose correctly the first time.</t>
<t>On a <tt>Signature-Error</tt> response, they say what would have worked. A server returning <tt>unsupported_scheme</tt> (<xref target="unsupported-scheme"/>) SHOULD include <tt>Accept-Signature-Scheme</tt>, and a server returning <tt>unsupported_algorithm</tt> (<xref target="unsupported_algorithm"/>) SHOULD include <tt>Accept-Signature-Alg</tt>. 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 <tt>Signature-Error</tt> itself.</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
Signature-Error: error=unsupported_scheme
Accept-Signature-Scheme: jwks_uri, jwt
]]>
</sourcecode>
</section>

<section anchor="response-status-codes"><name>Response Status Codes</name>
<t>These headers can be set on any response. Below is a list of what they MAY mean on responses with the following status codes:</t>
<table>
<thead>
<tr>
<th>Status</th>
<th>Meaning</th>
<th>Legacy client behavior</th>
<th>Signature-aware client behavior</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>401</tt></td>
<td>Authentication required</td>
<td>Falls back to WWW-Authenticate</td>
<td>Signs request with an accepted Signature-Key scheme</td>
</tr>

<tr>
<td><tt>402</tt></td>
<td>Payment + authentication required</td>
<td>Processes payment mechanism</td>
<td>Signs request AND processes payment</td>
</tr>

<tr>
<td><tt>429</tt></td>
<td>Rate limited</td>
<td>Respects Retry-After, slows down</td>
<td>Signs request, gets higher per-key rate limit</td>
</tr>
</tbody>
</table><t>The <tt>429</tt> 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 <tt>Retry-After</tt>. 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.</t>
</section>

<section anchor="incremental-adoption"><name>Incremental Adoption</name>
<t>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.</t>
<t><strong>Stage 1 - Rate limiting (429):</strong> A server adds <tt>Accept-Signature-Scheme: hwk</tt> 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.</t>
<t><strong>Stage 2 - Authentication (401):</strong> The server starts requiring signatures on some paths, returning 401 with <tt>Accept-Signature-Scheme: hwk</tt>. It can include <tt>WWW-Authenticate</tt> alongside for legacy clients that have other auth mechanisms. Signature-aware clients sign; legacy clients fall back to bearer tokens or other schemes.</t>
<t><strong>Stage 3 - Identity (401):</strong> The server advertises <tt>Accept-Signature-Scheme: jwks_uri, jwt, x509</tt> on sensitive paths, requiring verifiable client identity. The server can now make identity-based policy decisions without pre-registration.</t>
<t>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.</t>
</section>

<section anchor="coexistence-with-www-authenticate"><name>Coexistence with WWW-Authenticate</name>
<t>These headers and <tt>WWW-Authenticate</tt> (<xref target="RFC9110"/>, Section 11.6.1) are independent header fields; a response MAY include both. A client that understands Signature-Key processes the <tt>Accept-Signature-*</tt> headers; a legacy client processes <tt>WWW-Authenticate</tt>. Neither header's presence invalidates the other.</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Accept-Signature-Scheme: jwks_uri, jwt
Accept-Signature-Alg: ES256
]]>
</sourcecode>
<t>What a client that understands both mechanisms does depends on what the <tt>WWW-Authenticate</tt> 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.</t>

<ul>
<li><t>Where the challenge is an authentication or authorization challenge, such as <tt>Basic</tt> or <tt>Bearer</tt>, 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.</t>
</li>
<li><t>Where the challenge is not an authentication or authorization challenge, such as the payment challenge defined by the Micropayment Protocol (<xref target="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.</t>
</li>
</ul>
<t>A <tt>402</tt> response MAY include a payment mechanism such as x402 <xref target="x402"/> or the Micropayment Protocol (<xref target="I-D.ryan-httpauth-payment"/>) alongside a signature challenge. Payment is not authentication, so this is the complementary case and a client satisfies both:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="x7Tg2pLq", method="example",
    request="eyJhbW91bnQiOiIxMDAw..."
Accept-Signature-Scheme: hwk
]]>
</sourcecode>
</section>

<section anchor="examples"><name>Examples</name>
<t>Pseudonymous access:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
Accept-Signature-Scheme: hwk
]]>
</sourcecode>
<t>Identity with algorithm restriction:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
Accept-Signature-Scheme: jwks_uri, jwt
Accept-Signature-Alg: ES256
]]>
</sourcecode>
<t>Rate limiting with pseudonymous upgrade:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 429 Too Many Requests
Retry-After: 30
Accept-Signature-Scheme: hwk
]]>
</sourcecode>
<t>Payment with pseudonymous authentication:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="x7Tg2pLq", method="example",
    request="eyJhbW91bnQiOiIxMDAw..."
Accept-Signature-Scheme: hwk
]]>
</sourcecode>
</section>

<section anchor="client-processing"><name>Client Processing</name>
<t>When a client receives a response containing <tt>Accept-Signature-Scheme</tt> (<xref target="accept-signature-scheme"/>), it MAY retry the request with an HTTP Message Signature using any listed Signature-Key scheme it can satisfy.</t>
<t><xref target="RFC9421"/> Section 5.2 defines the processing of <tt>Accept-Signature</tt> by the client. A client MAY ignore <tt>Accept-Signature-Scheme</tt> and <tt>Accept-Signature-Alg</tt>, and MUST ignore tokens within them that it does not recognize.</t>
<t>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.</t>
<t>A conforming verifier, when presented with a well-formed request bearing an unknown or unregistered scheme, returns <tt>unsupported_scheme</tt> and an <tt>Accept-Signature-Scheme</tt> header naming what it accepts. This exercises the unknown-scheme path as a matter of defined behavior.</t>
<t>When a <tt>429</tt> response includes both <tt>Retry-After</tt> and <tt>Accept-Signature-Scheme</tt>, the client MAY retry one time with a signed request without waiting for the <tt>Retry-After</tt> 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.</t>
<t>A server MAY return a <tt>429</tt> response without <tt>Accept-Signature-Scheme</tt> to a signed request when it wants to rate-limit the client regardless of signing. In this case, the client MUST respect <tt>Retry-After</tt> as usual.</t>
<blockquote><t><strong>Open Issue:</strong> 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 <eref target="https://github.com/dickhardt/signature-key/issues/7">GitHub issue #7</eref>.</t>
</blockquote></section>
</section>

<section anchor="signature-error-http-response-header"><name>Signature-Error HTTP Response Header</name>
<t>When a server rejects a signed request due to a signature-related error, the response SHOULD include the <tt>Signature-Error</tt> 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 <tt>400 Bad Request</tt>, since the signature or keying material is malformed or invalid. A server MAY use <tt>401 Unauthorized</tt> for recoverable errors (e.g., <tt>unsupported_algorithm</tt>, <tt>unsupported_scheme</tt>, <tt>invalid_input</tt>) where the client can retry with corrected parameters.</t>

<section anchor="header-structure"><name>Header Structure</name>
<t>The <tt>Signature-Error</tt> header is a Dictionary (<xref target="RFC8941"/>, Section 3.2) with the following member:</t>

<ul spacing="compact">
<li><tt>error</tt> (REQUIRED): A Token (<xref target="RFC8941"/>, Section 3.3.4) indicating the error code.</li>
</ul>
<t>Additional members are defined per error code. Recipients MUST ignore unknown members.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=unsupported_algorithm
]]>
</sourcecode>
<t>The <tt>Signature-Error</tt> header is the authoritative source for machine-readable error information. The client MUST NOT depend on the response body for error handling.</t>
</section>

<section anchor="response-body"><name>Response Body</name>
<t>Servers SHOULD use Problem Details <xref target="RFC9457"/> (<tt>application/problem+json</tt>) for the response body when returning <tt>Signature-Error</tt>, 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 <tt>type</tt> member MUST be a URN of the form <tt>urn:ietf:params:sig-error:&lt;error-code&gt;</tt>, where <tt>&lt;error-code&gt;</tt> matches the <tt>error</tt> value in the header; a <tt>type</tt> of any other form cannot be interpreted against this document's registry.</t>

<sourcecode type="json"><![CDATA[{
  "type": "urn:ietf:params:sig-error:unsupported_algorithm",
  "title": "Unsupported signature algorithm",
  "status": 400,
  "detail": "The server does not support RS256"
}
]]>
</sourcecode>
<t>Extension members in the Problem Details object MAY duplicate information from the <tt>Signature-Error</tt> header for convenience. When the header and body conflict, the header takes precedence.</t>
</section>

<section anchor="access-denied"><name>Access Denied</name>
<t>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 <tt>403 Forbidden</tt>. This is not a signature error — the authentication succeeded but authorization was denied. The response MUST NOT include an <tt>Accept-Signature-Scheme</tt> header, an <tt>Accept-Signature-Alg</tt> header, or a <tt>Signature-Error</tt> header.</t>
</section>

<section anchor="error-codes"><name>Error Codes</name>

<section anchor="unsupported_algorithm"><name>unsupported_algorithm</name>
<t>The signing algorithm used by the client is not supported by the server.</t>
<t>The response SHOULD include an <tt>Accept-Signature-Alg</tt> header (<xref target="accept-signature-alg"/>) naming the algorithms the server accepts.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=unsupported_algorithm
Accept-Signature-Alg: Ed25519, ES256
]]>
</sourcecode>
<t>This error also covers a JWK whose key type the server does not implement (<xref target="algorithm-determination"/>). Because a fully-specified algorithm identifier determines the key type, the accompanying <tt>Accept-Signature-Alg</tt> tells the client which key types are usable without a separate list: a client offered <tt>Ed25519</tt> learns that an OKP key on the Ed25519 curve is accepted.</t>
</section>

<section anchor="unsupported-scheme"><name>unsupported_scheme</name>
<t>The Signature-Key scheme presented by the client is not supported by the server.</t>
<t>The response SHOULD include an <tt>Accept-Signature-Scheme</tt> header (<xref target="accept-signature-scheme"/>) naming the schemes the server accepts.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=unsupported_scheme
Accept-Signature-Scheme: jwks_uri, jwt
]]>
</sourcecode>
<t>This error is recoverable. A server MAY return it with <tt>401 Unauthorized</tt> so the client can retry with an accepted scheme.</t>
</section>

<section anchor="cache_miss"><name>cache_miss</name>
<t>A cache identifier presented with the cached scheme (<xref target="cached-scheme"/>) could not be resolved to a cached assertion. It is unknown, has been evicted, or failed integrity or decryption.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=cache_miss
]]>
</sourcecode>
<t>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 <tt>401 Unauthorized</tt>. A verifier MUST NOT treat an unresolved cache identifier as an authorization failure, and MUST NOT return <tt>cache_miss</tt> for an assertion that resolved but failed validation.</t>
</section>

<section anchor="invalid-signature"><name>invalid_signature</name>
<t>The HTTP Message Signature is missing, malformed, or cryptographic verification failed. This includes missing <tt>Signature</tt>, <tt>Signature-Input</tt>, or <tt>Signature-Key</tt> headers, an expired <tt>created</tt> timestamp, or a signature that does not verify.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=invalid_signature
]]>
</sourcecode>
</section>

<section anchor="invalid-input"><name>invalid_input</name>
<t>The Signature-Input is missing required covered components.</t>

<ul spacing="compact">
<li><tt>required_input</tt> (RECOMMENDED): An Inner List of String (<xref target="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.</li>
</ul>

<sourcecode type="http"><![CDATA[Signature-Error: error=invalid_input,
    required_input=("@method" "@authority" "@path"
    "signature-key" "content-digest")
]]>
</sourcecode>
</section>

<section anchor="invalid-request"><name>invalid_request</name>
<t>The request is malformed or missing required information unrelated to signature verification — such as missing query parameters or an unsupported content type.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=invalid_request
]]>
</sourcecode>
</section>

<section anchor="invalid-key"><name>invalid_key</name>
<t>The public key in <tt>Signature-Key</tt> could not be parsed, is expired, or does not meet the server's trust requirements.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=invalid_key
]]>
</sourcecode>
</section>

<section anchor="unknown-key"><name>unknown_key</name>
<t>The public key from <tt>Signature-Key</tt> does not match any key at the client's <tt>jwks_uri</tt> (applicable when the client uses <tt>scheme=jwks_uri</tt>). The server SHOULD re-fetch the JWKS once before returning this error, to handle key rotation.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=unknown_key
]]>
</sourcecode>
</section>

<section anchor="issuer_missing"><name>issuer_missing</name>
<t>The metadata document fetched during discovery does not contain an <tt>issuer</tt> member. Applicable to the jwks_uri scheme (<xref target="jwks-uri-scheme"/>), and to the jwt and self-jwt schemes when they discover the issuer's keys through a metadata document.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=issuer_missing
]]>
</sourcecode>
</section>

<section anchor="issuer_mismatch"><name>issuer_mismatch</name>
<t>The <tt>issuer</tt> member of the metadata document fetched during discovery does not match the identity the document was fetched under: the <tt>id</tt> parameter for the jwks_uri scheme, or the <tt>iss</tt> claim for the jwt and self-jwt schemes.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=issuer_mismatch
]]>
</sourcecode>
</section>

<section anchor="invalid-jwt"><name>invalid_jwt</name>
<t>The JWT in the <tt>Signature-Key</tt> header (when using <tt>scheme=jwt</tt> or <tt>scheme=jkt-jwt</tt>) is malformed or its signature verification failed.</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=invalid_jwt
]]>
</sourcecode>
</section>

<section anchor="expired-jwt"><name>expired_jwt</name>
<t>The JWT in the <tt>Signature-Key</tt> header (when using <tt>scheme=jwt</tt> or <tt>scheme=jkt-jwt</tt>) has expired (<tt>exp</tt> claim is in the past).</t>

<sourcecode type="http"><![CDATA[Signature-Error: error=expired_jwt
]]>
</sourcecode>
</section>
</section>
</section>

<section anchor="signature-key-cache-response-header"><name>Signature-Key-Cache Response Header</name>
<t>A verifier that has cached an assertion presented in a signed request, and that was asked to do so by the <tt>cache</tt> signal on the presented scheme (<xref target="jwt-confirmation-key-jwt"/>, <xref target="jkt-jwt-scheme"/>), MAY return the <tt>Signature-Key-Cache</tt> response header to issue the caller a cache identifier for later reference.</t>
<t><tt>Signature-Key-Cache</tt> is a Dictionary (<xref target="RFC8941"/>, Section 3.2) keyed by the signature label whose assertion was cached.</t>
<t>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 <tt>cid</tt> parameter of the cached scheme (<xref target="cached-scheme"/>).</t>
<t>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 <xref target="why-the-verifier-issues-the-cache-identifier"/>.</t>
<t>The member's parameters describe the cached assertion:</t>

<ul>
<li><t><tt>jti</tt> (String, OPTIONAL): The <tt>jti</tt> claim of the cached assertion (<xref target="RFC7519"/>, Section 4.1.7), echoed so the caller can associate the cache identifier with the assertion's own identity rather than with the per-request signature label. A JWT is cacheable only if it carries a <tt>jti</tt> (<xref target="jwt-confirmation-key-jwt"/>, <xref target="jkt-jwt-scheme"/>), so this value always exists; a verifier SHOULD include it when a caller may have more than one assertion in flight.</t>
</li>
<li><t><tt>expires</tt> (Integer, OPTIONAL): An advisory time, in seconds since the Unix epoch, after which the verifier may no longer honor the cache identifier. When present it MUST NOT be later than the assertion's own expiry. This is advisory; the caller MUST be prepared for a cache miss (<xref target="cache_miss"/>) at any time regardless of <tt>expires</tt>.</t>
</li>
</ul>
<t><strong>Example:</strong></t>

<artwork><![CDATA[Signature-Key-Cache: sig1="2f9c8a1e-a7b3";jti="2f9c8a1e";expires=1730000000
]]>
</artwork>
<t><strong>Round trip:</strong></t>
<t>The caller presents the assertion in full and asks for a cache identifier. The JWT carries a <tt>jti</tt>, without which it is not cacheable:</t>

<sourcecode type="http"><![CDATA[Signature-Key: sig1=jwt;jwt="eyJhbGciOiJFZERTQSJ9...";cache
]]>
</sourcecode>
<t>The verifier caches the assertion and issues a cache identifier for it:</t>

<sourcecode type="http"><![CDATA[Signature-Key-Cache: sig1="2f9c8a1e-a7b3";jti="2f9c8a1e";expires=1730000000
]]>
</sourcecode>
<t>On subsequent requests the caller presents the cache identifier in place of the assertion, as the <tt>cid</tt> parameter of the cached scheme. The String is the one the verifier issued, unchanged:</t>

<sourcecode type="http"><![CDATA[Signature-Key: sig1=cached;cid="2f9c8a1e-a7b3"
]]>
</sourcecode>
<t>Each request is signed as usual, and <tt>signature-key</tt> remains a covered component, so the cache identifier is signed over on every request that carries it.</t>
<t>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 (<xref target="presenting-and-resolving-a-cached-assertion"/>), exactly as an expired assertion presented in full would, rather than reporting <tt>cache_miss</tt>. A verifier that has already evicted the entry returns <tt>cache_miss</tt>, 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.</t>

<section anchor="presenting-and-resolving-a-cached-assertion"><name>Presenting and Resolving a Cached Assertion</name>
<t>A receiver processes a request bearing the cached scheme in two stages, which MUST remain distinct:</t>

<ol>
<li><t>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 <tt>cache_miss</tt> (<xref target="cache_miss"/>). 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.</t>
</li>
<li><t>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.</t>
</li>
</ol>
<t>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.</t>
<t>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.</t>
</section>

<section anchor="degradation-and-interoperability"><name>Degradation and Interoperability</name>
<t>Implementation of assertion caching is OPTIONAL. The degradation behavior in this subsection is not.</t>
<t>A verifier that does not implement assertion caching MUST NOT emit <tt>Signature-Key-Cache</tt>, and MUST reject a request using the cached scheme with <tt>unsupported_scheme</tt> (<xref target="unsupported-scheme"/>). 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 <tt>cache_miss</tt> path (<xref target="cache_miss"/>). 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.</t>
</section>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>

<section anchor="key-validation"><name>Key Validation</name>
<t>Verifiers MUST validate all cryptographic material before use:</t>

<ul>
<li><t><strong>hwk</strong>: Validate JWK structure and key parameters per <xref target="RFC7517"/></t>
</li>
<li><t><strong>jwks_uri</strong>: Verify HTTPS transport and validate fetched JWKS per <xref target="RFC7517"/></t>
</li>
<li><t><strong>jwks</strong>: Verify HTTPS transport and validate fetched JWKS per <xref target="RFC7517"/></t>
</li>
<li><t><strong>x509</strong>: Validate complete certificate chain per <xref target="RFC5280"/>, check revocation status</t>
</li>
<li><t><strong>jwt</strong>: Verify JWT signature per <xref target="RFC7519"/> and validate embedded JWK per <xref target="RFC7517"/></t>
</li>
<li><t><strong>self-jwt</strong>: Verify JWT signature per <xref target="RFC7519"/> using the key discovered from <tt>{iss}/.well-known/{dwk}</tt>; reuse that key as the HTTP signing key</t>
</li>
<li><t><strong>jkt-jwt</strong>: Verify JWT signature per <xref target="RFC7519"/> using header <tt>jwk</tt>, validate thumbprint matches <tt>iss</tt> per <xref target="RFC7638"/>, validate embedded ephemeral JWK per <xref target="RFC7517"/></t>
</li>
</ul>
</section>

<section anchor="caching-and-performance"><name>Caching and Performance</name>
<t>Verifiers MAY cache keys to improve performance but MUST implement appropriate cache expiration:</t>

<ul>
<li><t><strong>jwks_uri</strong>: 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.</t>
</li>
<li><t><strong>jwks</strong>: Cache by <tt>url</tt>; the same cache-control handling and once-per-minute refetch floor as <tt>jwks_uri</tt> apply.</t>
</li>
<li><t><strong>x509</strong>: Cache by <tt>x5t</tt>, invalidate on certificate expiry</t>
</li>
<li><t><strong>jwt</strong>: Cache embedded keys until JWT expiration</t>
</li>
<li><t><strong>self-jwt</strong>: Cache discovered keys until JWT expiration; cache by <tt>iss</tt> + <tt>kid</tt></t>
</li>
<li><t><strong>jkt-jwt</strong>: Cache embedded keys until JWT expiration; cache by <tt>iss</tt> thumbprint URI</t>
</li>
</ul>
<t>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.</t>
<t>When the <tt>Signature-Key</tt> scheme is <tt>jwks_uri</tt> and a cached key matching the JWT <tt>kid</tt> fails signature verification, the verifier SHOULD refresh the issuer's JWKS once and retry verification before returning <tt>unknown_key</tt> (if the key is then absent) or <tt>invalid_jwt</tt> (if verification still fails), subject to the once-per-minute fetch floor and egress admission (<xref target="scheme-specific-risks"/>) that apply to unknown-<tt>kid</tt> refreshes. This covers silent re-keying where the issuer replaces key material under the same <tt>kid</tt> without changing the identifier.</t>
</section>

<section anchor="scheme-specific-risks"><name>Scheme-Specific Risks</name>
<t><strong>hwk</strong>: No identity verification - suitable only for scenarios where pseudonymous access is acceptable.</t>
<t><strong>jkt-jwt</strong>: 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 <tt>jwt</tt> 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 <tt>exp</tt> 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 <tt>iss</tt> value is a JWK Thumbprint URI — a globally unique, collision-resistant identifier. The verifier MUST always compute the expected <tt>iss</tt> from the header <tt>jwk</tt> and compare by string equality — never trust the <tt>iss</tt> value alone.</t>
<t><strong>jwks_uri</strong>: 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 <tt>id</tt> 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 <tt>id</tt> as evidence that the origin authorized the request.</t>
<t>Because the JWKS location (and, for <tt>jwks_uri</tt>, 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 <tt>jwks_uri</tt>, or a <tt>jwks</tt> <tt>url</tt>:</t>

<ul spacing="compact">
<li>Require HTTPS for all outbound fetches.</li>
<li>Enforce response-size and timeout limits.</li>
<li>Refuse or constrain redirects (at minimum, do not follow redirects to a different host).</li>
<li>Reject private, loopback, and link-local destination addresses unless explicitly allowed by deployment configuration.</li>
<li>Defend against DNS rebinding by pinning the resolved IP address for the duration of the connection.</li>
<li>Treat cross-origin <tt>jwks_uri</tt> URLs (where the JWKS host differs from the metadata host) as requiring explicit deployment admission.</li>
</ul>
<t><strong>jwks</strong>: 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 <tt>url</tt> matches an expected or authorized origin.</t>
<t><strong>jwt</strong>: Delegation trust depends on JWT issuer verification. Verifiers MUST validate JWT signatures and claims before trusting embedded keys.</t>
<t><strong>self-jwt</strong>: Trust is rooted entirely in the issuer's JWKS. The same SSRF egress admission requirements that apply to <tt>jwks_uri</tt> and <tt>jwt</tt> apply here — the <tt>iss</tt> and <tt>dwk</tt> values are asserted by the presenter. Verifiers MUST validate that <tt>cnf</tt> is absent before treating the scheme as self-issued; a JWT containing <tt>cnf</tt> 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.</t>
<t><strong>x509</strong>: Requires robust certificate validation including revocation checking. Verifiers MUST NOT skip certificate chain validation.</t>
</section>

<section anchor="algorithm-selection"><name>Algorithm Selection</name>
<t>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 <tt>alg</tt> member, per Algorithm Determination (<xref target="algorithm-determination"/>); 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.</t>
<t><xref target="RFC9421"/>, Section 1.4 offers three ways for an application to establish the algorithm: state it in the <tt>alg</tt> signature parameter, derive it from the key material, or agree it out of band. This document takes the second, which <xref target="RFC9421"/>, Section 3.3.7 develops for JOSE signing algorithms: the algorithm is signaled by the key, and "the explicit <tt>alg</tt> signature parameter is not used at all when using JOSE signing algorithms".</t>
<t>Signers therefore MUST NOT include the <tt>alg</tt> parameter in Signature-Input (<xref target="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 — <xref target="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.</t>
<t>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 <tt>alg</tt> for conveyed JWKs (<xref target="algorithm-determination"/>). This aligns with <xref target="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.</t>
<t>Verifiers MUST:</t>

<ul>
<li><t>Take the algorithm from the key's <tt>alg</tt> member, and reject a key that has none (<xref target="algorithm-determination"/>)</t>
</li>
<li><t>Reject a key whose <tt>kty</tt> or <tt>crv</tt> is inconsistent with its <tt>alg</tt></t>
</li>
<li><t>Reject an <tt>alg</tt> naming an algorithm the verifier does not support, or that its policy declines, reporting <tt>unsupported_algorithm</tt> (<xref target="unsupported_algorithm"/>) and stating what it does accept in <tt>Accept-Signature-Alg</tt> (<xref target="accept-signature-alg"/>)</t>
</li>
</ul>
</section>

<section anchor="symmetric-algorithms"><name>Symmetric Algorithms</name>
<t>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.</t>
<t>For this reason symmetric algorithms MUST NOT be used with Signature-Key (<xref target="algorithm-determination"/>). A verifier MUST reject a JWK whose <tt>kty</tt> is <tt>oct</tt> or whose <tt>alg</tt> is a MAC identifier such as <tt>HS256</tt>. A shared-secret MAC remains available to deployments that have a pre-shared key and use <tt>keyid</tt> as <xref target="RFC9421"/> describes; it is out of scope here precisely because it needs no key distribution.</t>
</section>

<section anchor="cache-identifiers"><name>Cache Identifiers</name>
<t>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.</t>
<t>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.</t>
<t>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.</t>
<t>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 <tt>ETag</tt> 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.</t>
<t>Assertion caching lets a caller create verifier-side state at will. Nothing bounds how many distinct assertions it presents with <tt>cache</tt>, and a self-issued scheme such as jkt-jwt can mint a fresh <tt>jti</tt>, and so a fresh cache entry, on every request. The cache limits required by <xref target="caching-and-performance"/> 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: <tt>cache</tt> is a request, not an instruction.</t>
<t>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 (<xref target="cache_miss"/>).</t>
</section>

<section anchor="pqc-sizes"><name>Post-Quantum Key and Signature Sizes</name>
<t>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.</t>
</section>

<section anchor="signature-key-integrity"><name>Signature-Key Integrity</name>
<t>The Signature-Key header MUST be included as a covered component in Signature-Input:</t>

<artwork><![CDATA[Signature-Input: sig=("@method" "@authority" "@path" "signature-key"); created=1732210000
]]>
</artwork>
<t>If <tt>signature-key</tt> is not covered, an attacker can modify the header without invalidating the signature. Attacks include:</t>
<t><strong>Scheme substitution</strong>: An attacker extracts the public key from an <tt>hwk</tt> scheme and republishes it via <tt>jwks_uri</tt> under their own identity, causing verifiers to attribute the request to the attacker.</t>
<t><strong>Identity substitution</strong>: An attacker modifies the <tt>id</tt> parameter in a <tt>jwks_uri</tt> scheme to point to their own metadata endpoint that returns the same public key, impersonating a different signer.</t>
<t>Verifiers MUST reject requests where <tt>signature-key</tt> is not a covered component. There is no deployment in which accepting an uncovered <tt>Signature-Key</tt> is safe: both attacks above succeed against any verifier that does so, and neither is detectable after the fact.</t>
</section>
</section>

<section anchor="privacy-considerations"><name>Privacy Considerations</name>

<section anchor="pseudonymity-vs-identity"><name>Pseudonymity vs. Identity</name>
<t>The hwk and jkt-jwt schemes enable pseudonymous operation where the signer's identity is not disclosed. Verifiers should be aware that:</t>

<ul>
<li><t>A server can track a client across requests by JWK Thumbprint (<xref target="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.</t>
</li>
<li><t>The jkt-jwt thumbprint is stable across sessions (tied to the enclave key), enabling long-term tracking even when ephemeral keys rotate.</t>
</li>
<li><t>Verifiers should not log or retain pseudonymous keys beyond operational necessity.</t>
</li>
</ul>
<t>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.</t>
</section>

<section anchor="key-discovery-tracking"><name>Key Discovery Tracking</name>
<t>The jwks_uri, jwks, jwt, self-jwt, and x509 schemes require verifiers to fetch resources from signer-controlled URLs. This creates tracking vectors:</t>

<ul>
<li><t>Signers can observe when and from where keys are fetched. In particular, when a server fetches a client's JWKS from <tt>jwks_uri</tt> at verification time, the fetch reveals to the JWKS host that someone is verifying signatures for that client.</t>
</li>
<li><t>Verifiers should cache keys to minimize fetches.</t>
</li>
<li><t>Verifiers may wish to use shared caching infrastructure to reduce fingerprinting.</t>
</li>
</ul>
</section>

<section anchor="jwt-contents"><name>JWT Contents</name>
<t>JWTs in the jwt scheme may contain additional claims beyond <tt>cnf</tt>. Verifiers should:</t>

<ul>
<li><t>Only process claims necessary for verification</t>
</li>
<li><t>Not log or retain unnecessary JWT claims</t>
</li>
<li><t>Be aware that JWT contents are visible to network observers unless using TLS</t>
</li>
</ul>
</section>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>

<section anchor="http-field-name-registration"><name>HTTP Field Name Registration</name>
<t>This document registers the following header fields in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" defined in <xref target="RFC9110"/>.</t>
<t>Header field name: Signature-Key</t>
<t>Applicable protocol: http</t>
<t>Status: standard</t>
<t>Author/Change controller: IETF</t>
<t>Specification document(s): [this document]</t>
<t>Header field name: Signature-Error</t>
<t>Applicable protocol: http</t>
<t>Status: standard</t>
<t>Author/Change controller: IETF</t>
<t>Specification document(s): [this document]</t>
<t>Header field name: Signature-Key-Cache</t>
<t>Applicable protocol: http</t>
<t>Status: standard</t>
<t>Author/Change controller: IETF</t>
<t>Specification document(s): [this document]</t>
<t>Header field name: Accept-Signature-Scheme</t>
<t>Applicable protocol: http</t>
<t>Status: standard</t>
<t>Author/Change controller: IETF</t>
<t>Specification document(s): [this document]</t>
<t>Header field name: Accept-Signature-Alg</t>
<t>Applicable protocol: http</t>
<t>Status: standard</t>
<t>Author/Change controller: IETF</t>
<t>Specification document(s): [this document]</t>
</section>

<section anchor="scheme-registry"><name>Signature-Key Scheme Registry</name>
<t>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.</t>

<section anchor="registration-procedure"><name>Registration Procedure</name>
<t>New scheme registrations follow the Specification Required policy (<xref target="RFC8126"/>, Section 4.6). See <xref target="designated-expert-instructions"/> for instructions to the designated expert.</t>
</section>

<section anchor="initial-registry-contents"><name>Initial Registry Contents</name>
<table>
<thead>
<tr>
<th>Scheme</th>
<th>Description</th>
<th>Reference</th>
</tr>
</thead>

<tbody>
<tr>
<td>hwk</td>
<td>Header Web Key - inline public key</td>
<td>[this document]</td>
</tr>

<tr>
<td>jkt-jwt</td>
<td>JKT JWT Self-Issued Key Delegation - enclave-backed delegation</td>
<td>[this document]</td>
</tr>

<tr>
<td>jwks_uri</td>
<td>JWKS URI Discovery - key discovery via metadata</td>
<td>[this document]</td>
</tr>

<tr>
<td>jwks</td>
<td>Direct JWKS - JWKS fetched directly from an HTTPS URL that is also the signer identity</td>
<td>[this document]</td>
</tr>

<tr>
<td>jwt</td>
<td>JWT Confirmation Key - delegated key in JWT</td>
<td>[this document]</td>
</tr>

<tr>
<td>self-jwt</td>
<td>Self-Issued JWT - signer and issuer are the same party</td>
<td>[this document]</td>
</tr>

<tr>
<td>x509</td>
<td>X.509 Certificate - PKI certificate chain</td>
<td>[this document]</td>
</tr>

<tr>
<td>cached</td>
<td>Cached Assertion - reference to an assertion the verifier has cached</td>
<td>[this document]</td>
</tr>
</tbody>
</table></section>

<section anchor="registration-template"><name>Registration Template</name>

<dl spacing="compact">
<dt>Scheme Name:</dt>
<dd>The token value used in the Signature-Key header</dd>
<dt>Description:</dt>
<dd>A brief description of the scheme</dd>
<dt>Specification:</dt>
<dd>Reference to the specification defining the scheme</dd>
<dt>Parameters:</dt>
<dd>List of parameters defined for this scheme</dd>
</dl>
</section>
</section>

<section anchor="urn-sub-namespace-registration"><name>URN Sub-namespace Registration</name>
<t>This document registers the following URN sub-namespace in the "IETF URN Sub-namespace for Registered Protocol Parameter Identifiers" registry defined in <xref target="RFC3553"/>.</t>
<t>Registry name: sig-error</t>
<t>Specification: [this document]</t>
<t>Repository: [this document], Section on Error Codes</t>
<t>Index value: Values are registered in the "Signature Error Code" registry defined in this document.</t>
<t>The URN pattern is <tt>urn:ietf:params:sig-error:&lt;error-code&gt;</tt>, where <tt>&lt;error-code&gt;</tt> corresponds to a value in the Signature Error Code registry. These URNs are used as Problem Details <tt>type</tt> values (<xref target="RFC9457"/>) in response bodies accompanying <tt>Signature-Error</tt> headers.</t>
</section>

<section anchor="signature-error-code-registry"><name>Signature Error Code Registry</name>
<t>This document establishes the "Signature Error Code" registry. New values may be registered following the Expert Review policy (<xref target="RFC8126"/>, Section 4.5). See <xref target="designated-expert-instructions"/> for instructions to the designated expert.</t>

<section anchor="initial-registry-contents-1"><name>Initial Registry Contents</name>
<table>
<thead>
<tr>
<th>Value</th>
<th>Description</th>
<th>Reference</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>unsupported_algorithm</tt></td>
<td>Signing algorithm not supported</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>unsupported_scheme</tt></td>
<td>Signature-Key scheme not supported</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>cache_miss</tt></td>
<td>Cache identifier could not be resolved</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>invalid_signature</tt></td>
<td>Signature missing, malformed, or verification failed</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>invalid_input</tt></td>
<td>Missing required covered components</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>invalid_request</tt></td>
<td>Missing required info unrelated to signature</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>invalid_key</tt></td>
<td>Key cannot be parsed or doesn't meet trust requirements</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>unknown_key</tt></td>
<td>Key not found at jwks_uri</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>issuer_missing</tt></td>
<td>Metadata document lacks an issuer member</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>issuer_mismatch</tt></td>
<td>Metadata document issuer does not match the discovery identity</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>invalid_jwt</tt></td>
<td>JWT malformed or signature verification failed</td>
<td>[this document]</td>
</tr>

<tr>
<td><tt>expired_jwt</tt></td>
<td>JWT expired</td>
<td>[this document]</td>
</tr>
</tbody>
</table></section>

<section anchor="registration-template-1"><name>Registration Template</name>

<dl spacing="compact">
<dt>Value:</dt>
<dd>The error code token used in the <tt>Signature-Error</tt> header and the <tt>urn:ietf:params:sig-error:</tt> URN</dd>
<dt>Description:</dt>
<dd>A brief description of the error condition and when a verifier generates it</dd>
<dt>Reference:</dt>
<dd>Reference to the document or specification defining the error code</dd>
</dl>
</section>
</section>

<section anchor="designated-expert-instructions"><name>Designated Expert Instructions</name>
<t>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 (<xref target="RFC8126"/>, Section 4.6); the Signature Error Code registry uses the Expert Review policy (<xref target="RFC8126"/>, Section 4.5).</t>
<t>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.</t>
<t>For all registrations, the designated expert should verify that:</t>

<ul spacing="compact">
<li>The proposed value conforms to the registry's syntax and is not confusingly similar to an existing entry.</li>
<li>The registration does not duplicate the semantics of an existing entry without clear justification.</li>
</ul>
<t>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:</t>

<ul spacing="compact">
<li>How the verifier obtains the public key and establishes its authenticity.</li>
<li>All parameters used by the scheme, including which are required and which are optional.</li>
<li>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.</li>
</ul>
<t>For the Signature Error Code registry, the expert should additionally verify that:</t>

<ul spacing="compact">
<li>The error code is a lowercase token using only lowercase letters and underscore, consistent with the registry's existing entries.</li>
<li>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 <tt>invalid_request</tt> does.</li>
<li>The description makes clear when a verifier generates the error, and the error does not leak sensitive information to unauthenticated callers.</li>
</ul>
</section>
</section>

<section anchor="document-history"><name>Document History</name>
<t><em>Note: This section is to be removed before publishing as an RFC.</em></t>

<ul spacing="compact">
<li>draft-hardt-httpbis-signature-key-08</li>
</ul>
<t>Not backward compatible with -07. Breaking changes are listed first.</t>
<t>Breaking changes:</t>

<ul spacing="compact">
<li>Removed the <tt>sigkey</tt> Accept-Signature parameter and its registry entry. A parameter value is a bare Item and cannot be a list, so <tt>sigkey</tt> could name only one scheme. Use <tt>Accept-Signature-Scheme</tt> and <tt>Accept-Signature-Alg</tt>, which are Lists of Tokens and let a client select before it signs rather than after a rejection.</li>
<li>Removed the <tt>supported_algorithms</tt> member of <tt>Signature-Error</tt>, added in -04. Use <tt>Accept-Signature-Alg</tt>, which works on a challenge and on an error alike.</li>
<li>Made the hwk <tt>alg</tt> parameter REQUIRED and fully specified. It was forbidden in -06 and -07, so an hwk key serialized per -07 is rejected by a -08 verifier, and one serialized per -08 is rejected by a -07 verifier. There is nothing to negotiate over, so both ends of a deployment move together.</li>
<li>Forbade the polymorphic <tt>EdDSA</tt> identifier, deprecated by <xref target="RFC9864"/>. Use <tt>Ed25519</tt> or <tt>Ed448</tt>.</li>
<li>Required verifiers to take the algorithm from the JWK <tt>alg</tt> rather than derive it from <tt>kty</tt> and <tt>crv</tt>, and to reject a JWK whose <tt>kty</tt> or <tt>crv</tt> disagrees with its <tt>alg</tt>.</li>
<li>Required RSA <tt>alg</tt> to name both padding and hash, for example <tt>PS256</tt> or <tt>RS256</tt>. A key type of <tt>RSA</tt> alone is no longer sufficient.</li>
<li>Forbade symmetric algorithms: the <tt>oct</tt> key type and the JOSE MAC identifiers. Every scheme here distributes a public key, and a shared secret handed to the verifier proves nothing.</li>
<li>Forbade the <tt>none</tt> algorithm and any algorithm whose JOSE Implementation Requirement is <tt>Prohibited</tt>, as <xref target="RFC9421"/>, Section 3.3.7 requires of a JWS algorithm used for an HTTP Message Signature. Gave the rule its response-side half: a server MUST NOT list such an algorithm in <tt>Accept-Signature-Alg</tt>, which would otherwise advertise that it accepts unsigned requests as signed.</li>
<li>Named the registry these identifiers come from, citing the IANA "JSON Web Signature and Encryption Algorithms" registry itself rather than <xref target="RFC7518"/>, which established it but no longer holds all of it. This document uses the JOSE signing algorithms of <xref target="RFC9421"/>, Section 3.3.7 and does not use the HTTP Signature Algorithms registry.</li>
<li>Corrected <tt>Accept-Signature-Alg</tt> to carry identifiers from that same JOSE registry rather than from the HTTP Signature Algorithms registry. A server has to advertise algorithms in the namespace a conveyed key uses, or a client cannot compare what the server accepts against the keys it holds.</li>
<li>Replaced the requirement that a Signature-Input <tt>alg</tt> parameter be "consistent with the key material" with the rule of <xref target="RFC9421"/>, Section 3.3.7: signers MUST NOT send it and verifiers MUST ignore it. The consistency rule had no testable meaning, since Section 3.3.7 states that JWA values are not registered in the HTTP Signature Algorithms registry and so no mapping between the two namespaces exists. Cited Section 1.4, which names deriving the algorithm from the key material as one of the three approaches an application may take.</li>
<li>Removed the Editor's Note that made the post-quantum paragraph contingent on ML-DSA being registered for HTTP Message Signatures. Under <xref target="RFC9421"/>, Section 3.3.7 the JOSE path never consults that registry, so the pending registration does not gate ML-DSA here: <xref target="RFC9964"/> registers ML-DSA in the JOSE registry, which is the one that applies.</li>
<li>Raised <tt>signature-key</tt> coverage from SHOULD to MUST on both sides: signers MUST include it as a covered component and verifiers MUST reject requests where it is not covered. The scheme-substitution and identity-substitution attacks in <xref target="signature-key-integrity"/> succeed against any verifier that accepts an uncovered header, so no valid reason to ignore the requirement exists. This also removes an internal contradiction, since <xref target="accept-signature-scheme"/> already described coverage as a requirement of this specification.</li>
<li>Raised <tt>exp</tt> from a member of the "standard claims" SHOULD list to MUST in the jwt and self-jwt schemes. For jwt, <tt>exp</tt> is what bounds acceptance of the confirmation key the assertion carries.</li>
<li>Raised the hwk <tt>kid</tt> prohibition from SHOULD NOT to MUST NOT. The key is inline, so a <tt>kid</tt> selects nothing and a disagreeing <tt>kid</tt> has no defined resolution.</li>
<li>Raised rejection of a malformed JWT from SHOULD to MUST. A value that does not parse as a JWT cannot be verified, so the SHOULD had no exception case; the early-rejection rationale was the point being made and is retained as such.</li>
<li>Raised cache limits from SHOULD to MUST. Cache entries are created by unauthenticated callers.</li>
<li>Required the Problem Details <tt>type</tt> member, where a Problem Details body is returned, to be the <tt>urn:ietf:params:sig-error:</tt> URN form. Whether to return that body remains a SHOULD; the format of the member, once present, is not optional, since another form cannot be resolved against the registry.</li>
</ul>
<t>Other changes:</t>

<ul>
<li>Required a verifier resolving a key from a JWKS to select the member matching <tt>kid</tt> without requiring any other member to be usable, and forbade failing because an unselected member names an unimplemented <tt>kty</tt> or <tt>alg</tt>. Without it no issuer could add a post-quantum key alongside a classical one, since doing so would break every verifier that does not implement the new type, including those that were only ever going to use the classical key. Noted that an unknown member within a single JWK is ignored per <xref target="RFC7517"/>, Section 4, which is distinct from a member this document forbids.</li>
<li>Stated that an <tt>Accept-Signature-Alg</tt> Token is the registered identifier verbatim, case included: <tt>ES256</tt>, not <tt>es256</tt>. Structured Field parsing preserves a Token's case, and the value is compared against the <tt>alg</tt> member of a JWK, a case-sensitive JSON string, so a case-folded token matches no key.</li>
<li>Corrected the claim that <tt>kty</tt> and <tt>crv</tt> underdetermine the algorithm for EC keys. Within JOSE they do not: <tt>ES256</tt>, <tt>ES384</tt>, and <tt>ES512</tt> correspond one to one with <tt>P-256</tt>, <tt>P-384</tt>, and <tt>P-521</tt>, and no registered signing algorithm pairs a curve with another hash. Genuine underdetermination is limited to RSA, which has no <tt>crv</tt>, and to the <tt>AKP</tt> key type of <xref target="RFC9964"/>.</li>
<li>Stated that requiring <tt>alg</tt> of every conveyed key is therefore a choice rather than a necessity, and gave the reasons in a new Design Rationale section, <xref target="why-alg-is-required"/>: the set of underdetermined key types grows as algorithms are registered, <tt>Accept-Signature-Alg</tt> comparison has to be total, a verifier keeps one code path, and the signer bears no cost. Said plainly that this tightens <xref target="RFC9864"/>, which RECOMMENDS rather than requires the member.</li>
<li>Required a verifier to reject an <tt>alg</tt> it does not support with <tt>unsupported_algorithm</tt>, and stated that <tt>Accept-Signature-Alg</tt> names exactly that set, neither a subset nor a superset. Replaced the vague "validate the algorithm against policy" verifier obligations with the specific checks.</li>
<li>Stated where <tt>alg</tt> comes from in each scheme, and that jwks_uri, jwks, and self-jwt have no in-band channel for it: the resolved JWKS entry is the only source, so adopting those schemes means publishing a key that carries <tt>alg</tt> rather than pointing at one that omits it. Only the key the <tt>kid</tt> selects is subject to the requirement, so an existing metadata document can be reused by adding a conforming key.</li>
<li>Corrected the <tt>Accept-Signature-Alg</tt> examples, which still used HTTP Signature Algorithms registry identifiers (<tt>ed25519</tt>, <tt>ecdsa-p256-sha256</tt>, <tt>rsa-v1_5-sha256</tt>) after the header was defined to carry JOSE identifiers.</li>
<li>Rewrote the justification in Algorithm Determination. <xref target="RFC9864"/> RECOMMENDS rather than requires the JWK <tt>alg</tt> member, and allows a deployment to rely on some other mechanism for ensuring a key is used as intended, so citing it as the basis for a MUST overstated it. The requirement now says what it is — a tightening — and gives the reason: keys conveyed in band come from a party the verifier has no prior relationship with, so no such other mechanism exists, and <tt>kty</tt> and <tt>crv</tt> underdetermine the algorithm for RSA, EC, and <tt>AKP</tt> keys alike. Noted that <xref target="I-D.richer-oauth-httpsig"/> reaches the same requirement independently for JWK-bound keys.</li>
<li>Audited every BCP 14 SHOULD against RFC 2119 Section 6. Each retained SHOULD now names the circumstance under which it may be ignored: omitting <tt>Signature-Error</tt> or <tt>required_input</tt> where diagnostics to an unauthenticated caller are a disclosure risk, returning a non-Problem-Details body under content negotiation, the general-purpose verifier that has no authorized-origin list to check <tt>id</tt> against, and the deployment that keeps an inline scheme because it has confirmed its headers fit.</li>
<li>Downgraded to lowercase the statements that were not interoperability requirements, per RFC 2119 Section 6 and the RFC 8174 convention that only uppercase is normative: single-signature deployment advice, the recommendation to document enclave stable-key algorithms, the "shortest practical lifetime" guidance, which was unmeasurable as written, and header buffer sizing.</li>
<li>Grounded the <tt>typ</tt> header check of the jwt and self-jwt schemes in the JWT BCP: verifiers SHOULD require an expected <tt>typ</tt>, per the explicit-typing guidance of <xref target="RFC8725"/>, Section 3.11. The check is a token-confusion defence, not the optimization the text previously called it.</li>
<li>Corrected the layered cryptographic agility rationale: post-quantum verification is not the expensive part, since ML-DSA verification is comparable to Ed25519. What caching saves the verifier is the assertion's bytes on the wire and the repeated resolution, not verification time. The signer's per-request cost is time and the verifier's is size.</li>
<li>Corrected the basis of the classical hot path: what bounds exposure is the lifetime of the confirmation key, not the expiry of the assertion carrying it. A signer that rebinds one long-lived key into successive assertions leaves that key acceptable indefinitely whatever each <tt>exp</tt> says, and gains nothing from short assertion lifetimes. jkt-jwt has the intended shape by construction.</li>
<li><t>Removed the archived IETF 125 presentation from the repository.</t>
</li>
<li><t>Added the <tt>jwks</tt> scheme: a direct JWKS fetch whose HTTPS <tt>url</tt> is both the signer identity and the key location, under the same egress-admission rules as <tt>jwks_uri</tt>.</t>
</li>
<li><t>Stated that the jwks <tt>url</tt> is compared by byte equality as presented, with no canonicalization.</t>
</li>
<li><t>Required the discovery metadata document to contain <tt>issuer</tt> and <tt>jwks_uri</tt>, and required verifiers to reject a document whose <tt>issuer</tt> does not match the identity it was fetched under — the check of <xref target="RFC8414"/>, Section 3.3 — with the new <tt>issuer_missing</tt> and <tt>issuer_mismatch</tt> error codes. Applies to jwks_uri, jwt, and self-jwt discovery. Addresses issue #12.</t>
</li>
<li><t>Added the <tt>unsupported_scheme</tt> error code and made unknown-scheme rejection mandatory and conformance-testable, scoped to the <tt>Signature-Key</tt> member the verifier selected. A member the verifier did not select is ignored, so a signer can offer a signature under a new scheme without breaking verifiers that lack it.</t>
</li>
<li><t>Added an Algorithm Determination section as the single home for the fully-specified algorithm rules, referenced from every scheme that conveys or references a JWK.</t>
</li>
<li><t>Required defined rejection of unimplemented JWK key types, including <tt>AKP</tt> <xref target="RFC9964"/>, reported as <tt>unsupported_algorithm</tt>.</t>
</li>
<li><t>Noted that the ML-DSA identifiers of <xref target="RFC9964"/> satisfy the rule without special treatment. Added a deployment consideration on post-quantum key and signature sizes.</t>
</li>
<li><t>Added assertion caching as a strawman for discussion: the <tt>cached</tt> scheme, the <tt>cache</tt> signal on jwt and jkt-jwt, the <tt>Signature-Key-Cache</tt> response header, the <tt>cache_miss</tt> error, and the resolution and validation model. A JWT is cacheable only if it carries a <tt>jti</tt>; self-jwt is excluded, its key being already cacheable on <tt>iss</tt> and <tt>kid</tt> and its claims request-specific. Implementation is optional; the degradation behavior is not. Whether this is the right layer for caching is an open question.</t>
</li>
<li><t>Distinguished the cache entry's expiry from the assertion's: a verifier still holding the entry resolves it and lets validation reject an expired assertion, while one that has evicted it returns <tt>cache_miss</tt> and the caller resends in full.</t>
</li>
<li><t>Made <tt>cached</tt> in <tt>Accept-Signature-Scheme</tt> a capability announcement: a client cannot present it until a verifier has issued it a cache identifier.</t>
</li>
<li><t>Bounded verifier-side cache state: a caller can mint an assertion, and so a cache entry, per request, so the cache limits apply to assertion caching and a verifier should bound entries per confirmation key.</t>
</li>
<li><t>Specified client behaviour when a response carries both <tt>WWW-Authenticate</tt> and a signature challenge: alternatives where the auth-scheme authenticates, in which case the client signs rather than presenting the credential, and complements where it does not, such as a payment challenge, in which case the client satisfies both. Addresses issue #17.</t>
</li>
<li><t>Stated what <tt>keyid</tt> (<xref target="RFC9421"/>, Section 5.1) means alongside <tt>Signature-Key</tt>: a server SHOULD NOT send it, a <tt>keyid</tt> in <tt>Signature-Input</tt> MUST identify the same key as the <tt>Signature-Key</tt> member for that label, and the verifier takes the key from <tt>Signature-Key</tt>.</t>
</li>
<li><t>Made the scheme preference order in <tt>Accept-Signature-Scheme</tt> non-binding on the client. The order is the server's preference, while the choice of scheme decides whether the signer is identified, which is the client's to make.</t>
</li>
<li><t>Had a server that sends <tt>Accept-Signature-Alg</tt> not send the <tt>alg</tt> parameter of <tt>Accept-Signature</tt>, which names algorithms in the HTTP Signature Algorithms registry this document does not use; a client MAY ignore an <tt>alg</tt> received alongside <tt>Accept-Signature-Alg</tt>.</t>
</li>
<li><t>Expanded the Introduction to state the gaps this document addresses and the invariants that follow.</t>
</li>
<li><t>Added rationale for a scheme token rather than a header per scheme, for carrying the accepted sets in header fields rather than in parameters or error members, for layered cryptographic agility, for the verifier issuing the cache identifier rather than deriving it from the assertion as an entity tag would be, and for not reserving grease values.</t>
</li>
<li><t>Gave the SHOULD for sending <tt>Accept-Signature-Scheme</tt> and <tt>Accept-Signature-Alg</tt> on an error response its exception case, per RFC 2119 Section 6: a server may withhold the header where enumerating what it accepts to an unauthenticated caller is a disclosure risk.</t>
</li>
<li><t>Corrected the Accept-Signature parameter name from <tt>algs</tt> to <tt>alg</tt>, per <xref target="RFC9421"/>, Section 5.1.</t>
</li>
<li><t>Converted internal cross-references to mmark xref syntax so they render as section numbers.</t>
</li>
<li><t>IANA review feedback: added Designated Expert Instructions for the HTTP Signature-Key Scheme and Signature Error Code registries per RFC 8126 Section 4.5.</t>
</li>
<li><t>Changed the Signature Error Code registry policy from Specification Required to Expert Review.</t>
</li>
<li><t>Added a registration template to the Signature Error Code registry.</t>
</li>
<li><t>draft-hardt-httpbis-signature-key-07</t>

<ul spacing="compact">
<li>Editorial. Noted in the Introduction that the mechanisms defined here are used by other specifications, citing the AAuth protocol <xref target="I-D.hardt-oauth-aauth-protocol"/> and Email Verification <xref target="I-D.hardt-email-verification"/>. No normative change.</li>
</ul></li>
<li><t>draft-hardt-httpbis-signature-key-06</t>

<ul spacing="compact">
<li>Added the <tt>self-jwt</tt> scheme for self-issued JWTs where the signer and the JWT issuer are the same party. The JWT signing key, discovered via <tt>{iss}/.well-known/{dwk}</tt>, is reused as the HTTP signing key, and no <tt>cnf</tt> claim is present.</li>
</ul></li>
<li><t>draft-hardt-httpbis-signature-key-05</t>

<ul spacing="compact">
<li>Incorporated implementer feedback from Joshua Gay (sidecat), and added him to the acknowledgments.</li>
<li>Added a mandatory egress-admission checklist to the <tt>jwks_uri</tt> SSRF risk bullet: HTTPS, size and timeout limits, redirect policy, private and loopback address rejection, DNS rebinding defense, and cross-origin JWKS admission.</li>
<li>Added a once-per-minute JWKS fetch floor, and a same-<tt>kid</tt> refresh rule allowing one refresh and retry before returning <tt>unknown_key</tt> or <tt>invalid_jwt</tt>, subject to that floor and to egress-admission policy.</li>
<li>Noted that the stable (enclave) key algorithm in <tt>jkt-jwt</tt> is enclave-determined, and that deployments supporting Ed25519 or other stable-key algorithms SHOULD document this.</li>
</ul></li>
<li><t>draft-hardt-httpbis-signature-key-04</t>

<ul spacing="compact">
<li>Renamed the specification from "HTTP Signature-Key Header" to "HTTP Signature Keys".</li>
<li>Added the <tt>sigkey</tt> parameter to Accept-Signature (<xref target="RFC9421"/>, Section 5) with three values: <tt>jkt</tt> (pseudonymous), <tt>uri</tt> (URI-identified), and <tt>x509</tt> (PKI certificate), and registered it in the HTTP Signature Metadata Parameters registry.</li>
<li>Added the Signature-Error response header and established the Signature Error Code registry.</li>
<li>Added an incremental adoption section describing zero-coordination deployment via 429, 401, and 402 responses.</li>
<li>Added privacy considerations for key thumbprint tracking, agent identity disclosure, and the JWKS fetch side channel.</li>
</ul></li>
<li><t>draft-hardt-httpbis-signature-key-03</t>

<ul spacing="compact">
<li>Added the <tt>jkt-jwt</tt> scheme for self-issued key delegation, with a TOFU reference to <xref target="RFC7435"/>.</li>
<li>Renamed the <tt>well-known</tt> parameter to <tt>dwk</tt> (dot well-known).</li>
<li>Added <tt>iss</tt> and <tt>dwk</tt> claims to the jwt scheme (SHOULD) for issuer key discovery.</li>
<li>Added an early validation step to jwt verification: format, <tt>typ</tt>, and <tt>exp</tt> checks before any network fetch.</li>
<li>Added design rationale for <tt>jwks_uri</tt> rather than an inline JWKS, and moved the hwk string versus byte sequence note to the rationale appendix.</li>
<li>Reordered the schemes and added acknowledgments.</li>
</ul></li>
<li><t>draft-hardt-httpbis-signature-key-02</t>

<ul spacing="compact">
<li>Changed the <tt>x5t</tt> parameter to a byte sequence, per reviewer feedback.</li>
<li>Added structured field types to all parameters.</li>
<li>Added a design note on the string versus byte sequence choice for hwk.</li>
</ul></li>
<li><t>draft-hardt-httpbis-signature-key-01</t>

<ul spacing="compact">
<li>Initial public draft, with four schemes: hwk, jwks_uri, x509, and jwt.</li>
</ul></li>
</ul>
</section>

<section anchor="acknowledgments"><name>Acknowledgments</name>
<t>The author would like to thank Joshua Gay and Yaron Sheffer for their feedback on this specification.</t>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<reference anchor="IANA.JOSE.Algorithms" target="https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms">
  <front>
    <title>JSON Web Signature and Encryption Algorithms</title>
    <author>
      <organization>IANA</organization>
    </author>
  </front>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3553.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5280.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7517.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7518.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7519.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7638.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7800.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8126.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8725.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8941.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9110.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9421.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9457.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9864.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9964.xml"/>
</references>
<references><name>Informative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.hardt-email-verification.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.hardt-oauth-aauth-protocol.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.richer-oauth-httpsig.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ryan-httpauth-payment.xml"/>
<reference anchor="OpenID.Discovery" target="https://openid.net/specs/openid-connect-discovery-1_0.html">
  <front>
    <title>OpenID Connect Discovery 1.0</title>
    <author fullname="Nat Sakimura" initials="N." surname="Sakimura">
      <organization>NRI</organization>
    </author>
    <author fullname="John Bradley" initials="J." surname="Bradley">
      <organization>Ping Identity</organization>
    </author>
    <author fullname="Michael B. Jones" initials="M." surname="Jones">
      <organization>Microsoft</organization>
    </author>
    <author fullname="Edmund Jay" initials="E." surname="Jay">
      <organization>Illumila</organization>
    </author>
    <date year="2014" month="November"/>
  </front>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5077.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7435.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7541.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7924.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8414.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8446.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8701.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9170.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9204.xml"/>
<reference anchor="x402" target="https://docs.x402.org">
  <front>
    <title>x402: HTTP 402 Payment Protocol</title>
    <author>
      <organization>x402 Foundation</organization>
    </author>
    <date year="2025"/>
  </front>
</reference>
</references>
</references>

<section anchor="design-rationale"><name>Design Rationale</name>

<section anchor="why-jwks-uri"><name>Why jwks_uri Instead of Inline JWKS?</name>
<t>The <tt>jwks_uri</tt> and <tt>jwt</tt> schemes reference a <tt>jwks_uri</tt> property in the <tt>.well-known</tt> metadata document rather than embedding the JWKS directly in the metadata. This separation of concerns is deliberate:</t>

<ol>
<li><t><strong>Independent key rotation</strong>: Keys can be rotated by updating the JWKS endpoint without modifying the <tt>.well-known</tt> metadata document. This decouples key lifecycle management from configuration management, allowing operations teams to rotate keys on their own schedule without redeploying metadata.</t>
</li>
<li><t><strong>Independent management</strong>: The <tt>.well-known</tt> 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.</t>
</li>
<li><t><strong>Caching semantics</strong>: 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 <tt>.well-known</tt> document's caching policy.</t>
</li>
<li><t><strong>Consistency with existing standards</strong>: This approach mirrors the pattern established by OpenID Connect Discovery <xref target="OpenID.Discovery"/> and OAuth Authorization Server Metadata <xref target="RFC8414"/>, which both use <tt>jwks_uri</tt> in metadata documents for the same reasons.</t>
</li>
</ol>
</section>

<section anchor="why-both-jwks-and-jwks-uri"><name>Why Both jwks and jwks_uri?</name>
<t>The <tt>jwks</tt> and <tt>jwks_uri</tt> schemes occupy adjacent points on a simplicity/decoupling axis. <tt>jwks_uri</tt> fetches a <tt>.well-known</tt> metadata document and follows its <tt>jwks_uri</tt> property to the keys, so identity (the <tt>id</tt> origin) is separate from key location (the discovered JWKS URL); the two can be managed and rotated independently, as <xref target="why-jwks-uri"/> describes. <tt>jwks</tt> collapses this to a single HTTPS fetch whose URL is both identity and key location. Neither dominates: <tt>jwks</tt> removes a hop and all metadata hosting at the cost of tying identity to the JWKS URL, while <tt>jwks_uri</tt> 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.</t>
</section>

<section anchor="why-a-separate-header"><name>Why a Separate Header?</name>
<t>An alternative design would extend Signature-Input with additional parameters to carry key material. This was considered and rejected for several reasons:</t>

<ol>
<li><t><strong>Parameter complexity</strong>: Each scheme has a different set of parameters (e.g., <tt>hwk</tt> needs <tt>kty</tt>, <tt>crv</tt>, <tt>x</tt>, <tt>y</tt>; <tt>jwks_uri</tt> needs <tt>id</tt>, <tt>dwk</tt>, <tt>kid</tt>; <tt>jwt</tt> 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.</t>
</li>
<li><t><strong>Separation of concerns</strong>: Signature-Input describes <em>what</em> is signed and <em>how</em> (covered components, algorithm, timestamps). Signature-Key describes <em>who</em> signed it and <em>where to find the key</em>. These are distinct concerns, and separating them into distinct headers makes each easier to understand and process independently.</t>
</li>
<li><t><strong>Extensibility</strong>: 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.</t>
</li>
<li><t><strong>Multiple signatures</strong>: 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.</t>
</li>
</ol>
</section>

<section anchor="why-schemes-instead-of-just-a-key-and-key-id"><name>Why Schemes Instead of Just a Key and Key ID?</name>
<t>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:</t>

<ol>
<li><t><strong>Trust model varies</strong>: 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.</t>
</li>
<li><t><strong>Verification procedure differs</strong>: Each scheme has a fundamentally different verification path. <tt>hwk</tt> requires no external fetches. <tt>jwks_uri</tt> requires metadata discovery. <tt>x509</tt> requires certificate chain validation. <tt>jwt</tt> 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.</t>
</li>
<li><t><strong>Security properties differ</strong>: 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 <tt>jwt</tt> or <tt>x509</tt> schemes").</t>
</li>
<li><t><strong>Interoperability</strong>: Explicit schemes create clear interoperability targets. Two implementations that support the <tt>jwt</tt> scheme know exactly what to expect from each other. Without schemes, the same key material could be interpreted differently by different implementations.</t>
</li>
</ol>
</section>

<section anchor="why-a-scheme-token-instead-of-a-header-per-scheme"><name>Why a Scheme Token Instead of a Header per Scheme?</name>
<t>An alternative design would define a distinct header field per key distribution mechanism (for example <tt>Signature-Key-Hwk</tt>, <tt>Signature-Key-Jwt</tt>) rather than one <tt>Signature-Key</tt> header carrying a scheme token. HTTP field names are the most heavily exercised extension point on the web, and <xref target="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.</t>

<ol>
<li><t><strong>Ignorability is the wrong property for keying material.</strong> Header-name extensibility works because an unknown header can be safely ignored. <tt>Signature-Key</tt> carries mandatory keying material. A verifier that ignores an unknown <tt>Signature-Key-X</tt> 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 <tt>unsupported_scheme</tt> with an <tt>Accept-Signature-Scheme</tt> header naming what it accepts. <xref target="RFC9170"/> Section 4.4 notes that effective feedback is what keeps the surrounding extension machinery working.</t>
</li>
<li><t><strong>The covered-component invariant stays fixed.</strong> The scheme substitution and identity substitution attacks in <xref target="signature-key-integrity"/> depend on <tt>signature-key</tt> being a covered component. With one header, <tt>signature-key</tt> 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.</t>
</li>
<li><t><strong>Label correlation stays simple.</strong> The dictionary is keyed by signature label so that each signature in a multi-signature message carries its own keying material (<xref target="multiple-signatures"/>). Resolving a label's key is one dictionary lookup. Across N per-scheme headers it becomes a scan with a cross-header collision policy.</t>
</li>
<li><t><strong>Fewer namespaces.</strong> <xref target="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 <tt>Accept-Signature-Scheme</tt> response header. Per-scheme header names would add a second namespace for the same axis.</t>
</li>
</ol>
<t>To keep the scheme registry usable despite its narrow traffic, this document relies on defined behavior rather than on greasing <xref target="RFC8701"/>. Unknown and unregistered schemes have a single mandatory outcome (<xref target="unsupported-scheme"/>), verifiers dispatch through the registry rather than a fixed branch set, and conformance testing exercises the unknown-scheme path directly (<xref target="client-processing"/>). 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 <xref target="RFC9170"/> Section 3.3 cautions against.</t>
</section>

<section anchor="why-accept-signature-scheme-and-accept-signature-alg-are-separate-headers"><name>Why Accept-Signature-Scheme and Accept-Signature-Alg Are Separate Headers</name>
<t>Earlier versions of this document carried the server's scheme requirement in a <tt>sigkey</tt> parameter on <tt>Accept-Signature</tt>, and the accepted sets in <tt>supported_schemes</tt> and <tt>supported_algorithms</tt> members of <tt>Signature-Error</tt>. 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.</t>

<ol>
<li><t><strong>A set cannot be expressed in a parameter.</strong> A Structured Fields parameter value is a bare Item (<xref target="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 <tt>sigkey</tt>: the <tt>alg</tt> parameter of <tt>Accept-Signature</tt> (<xref target="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.</t>
</li>
<li><t><strong>The Accept-Signature member value is already spoken for.</strong> The alternative to a parameter is a Dictionary member value, which may be an Inner List. In <tt>Accept-Signature</tt> 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.</t>
</li>
<li><t><strong>Capability does not vary by signature label.</strong> 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. <tt>Accept-Signature</tt> retains label granularity for what it is for: which components are to be covered, and the per-label parameters of <xref target="RFC9421"/> Section 5.1.</t>
</li>
<li><t><strong>One dimension per field is the established HTTP pattern.</strong> HTTP negotiates with <tt>Accept</tt>, <tt>Accept-Encoding</tt>, <tt>Accept-Language</tt>, and <tt>Accept-Charset</tt>: 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.</t>
</li>
<li><t><strong>The same syntax serves the challenge and the error.</strong> This is the property the previous design could not have. <tt>supported_schemes</tt> and <tt>supported_algorithms</tt> lived inside <tt>Signature-Error</tt>, 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. <xref target="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.</t>
</li>
<li><t><strong>Removing the error members avoids two ways to say one thing.</strong> Once the headers exist, retaining the members would leave two encodings of the same information, differing only in when they may appear. <xref target="RFC9170"/> Section 4.1 notes that redundant, partially-used mechanisms ossify. <tt>Signature-Error</tt> now states what went wrong, and the <tt>Accept-Signature-*</tt> fields state what would succeed.</t>
</li>
</ol>
<t>Adding header fields here does not contradict the argument against per-scheme key headers in <xref target="why-a-scheme-token-instead-of-a-header-per-scheme"/>. That argument turns on ignorability being the wrong property for mandatory keying material: a verifier that silently ignores an unknown <tt>Signature-Key-X</tt> 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 <tt>Accept-Signature-Scheme</tt> 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.</t>
</section>

<section anchor="layered-cryptographic-agility"><name>Layered Cryptographic Agility</name>
<t>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 (<xref target="signature-key-cache-response-header"/>) makes the post-quantum assertion, whose signature is large (<xref target="pqc-sizes"/>), affordable to reference on each request without retransmitting it.</t>
<t>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 <tt>exp</tt> any individual assertion carries. The jkt-jwt scheme (<xref target="jkt-jwt-scheme"/>) has the intended shape by construction, the stable enclave key establishing identity while the request key in <tt>cnf</tt> 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.</t>
<t>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 (<xref target="pqc-sizes"/>) 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.</t>
<t>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.</t>
</section>

<section anchor="why-the-verifier-issues-the-cache-identifier"><name>Why the Verifier Issues the Cache Identifier</name>
<t>The cache identifier is issued by the verifier, not chosen by the caller. The caller already has an identifier for the assertion, its <tt>jti</tt>, so it is worth stating why that one is not used and why the naming is the verifier's.</t>

<ol>
<li><t><strong>The cache is the verifier's.</strong> 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.</t>
</li>
<li><t><strong>A <tt>jti</tt> is unique per issuer, not per verifier.</strong> The <tt>jti</tt> claim is unique within the scope of its issuer (<xref target="RFC7519"/>, Section 4.1.7). A verifier accepting assertions from many issuers can be presented with the same <tt>jti</tt> by unrelated callers, so <tt>jti</tt> alone is not a key. A verifier could key on issuer and <tt>jti</tt> 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.</t>
</li>
<li><t><strong>A caller-chosen identifier is attacker-chosen input to a lookup.</strong> 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.</t>
</li>
<li><t><strong>Only the verifier can make the identifier self-contained.</strong> 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 (<xref target="precedents-for-assertion-caching"/>).</t>
</li>
</ol>
<t>The <tt>jti</tt> is still useful to the caller, and is echoed in <tt>Signature-Key-Cache</tt> 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.</t>
</section>

<section anchor="precedents-for-assertion-caching"><name>Precedents for Assertion Caching</name>
<t>The reference-and-fallback shape of the cached scheme follows established practice.</t>
<t>The TLS Cached Information Extension <xref target="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.</t>
<t>TLS session tickets (<xref target="RFC5077"/>; <xref target="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 (<xref target="signature-key-cache-response-header"/>) and for the fleet case in which an identifier minted by one node is presented to another that cannot resolve it (<xref target="cache_miss"/>).</t>
<t>Entity tags (<xref target="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 (<xref target="cache_miss"/>).</t>
<t>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 (<xref target="cache-identifiers"/>). 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 <tt>jti</tt> echoed in <tt>Signature-Key-Cache</tt> (<xref target="why-the-verifier-issues-the-cache-identifier"/>).</t>
<t>The TLS mechanisms above are cited as context for the design, not as normative dependencies.</t>
</section>

<section anchor="why-strings-instead-of-byte-sequences-for-hwk"><name>Why Strings Instead of Byte Sequences for hwk?</name>
<t>The hwk parameters use structured field strings rather than byte sequences. JWK key values are base64url-encoded per <xref target="RFC7517"/>, while structured field byte sequences use base64 encoding per <xref target="RFC8941"/>. Using strings allows implementations to pass JWK values directly without converting between base64url and base64, avoiding a potential source of encoding bugs.</t>
</section>

<section anchor="why-alg-is-required"><name>Why alg Is Required on Every Conveyed Key</name>
<t>The alternative considered was deriving the algorithm from the key's structure, as JOSE implementations commonly do today.</t>
<t>That derivation works, for two of the four key types this document can convey. No registered JOSE signing algorithm pairs the <tt>Ed25519</tt> curve with anything but <tt>Ed25519</tt>, or <tt>P-256</tt> with anything but <tt>ES256</tt>; for OKP and EC keys, <tt>kty</tt> and <tt>crv</tt> 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.</t>
<t>It fails for the other two. An RSA key has no <tt>crv</tt>, and <tt>kty</tt> of <tt>RSA</tt> determines neither the padding scheme nor the hash, so the same key admits <tt>RS256</tt>, <tt>PS256</tt>, <tt>RS512</tt>, and more. The <tt>AKP</tt> key type of <xref target="RFC9964"/> covers several ML-DSA parameter sets, none of them recoverable from <tt>kty</tt>. In both cases the key does not say what it is for, and nothing else in these schemes does either.</t>
<t>The requirement is uniform rather than restricted to those two cases, for four reasons.</t>
<t><strong>The set of underdetermined types grows.</strong> 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 <tt>AKP</tt> is already an entry in that table. A uniform requirement accommodates a new algorithm with no change here at all.</t>
<t><strong>Negotiation must be a total comparison.</strong> <tt>Accept-Signature-Alg</tt> (<xref target="accept-signature-alg"/>) 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 <tt>alg</tt>, 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 <tt>alg</tt> makes the comparison a string match over one vocabulary on both sides.</t>
<t><strong>One code path in the verifier.</strong> 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 <tt>alg</tt> and <tt>kty</tt>/<tt>crv</tt> is then available as a check rather than as an alternative (<xref target="algorithm-determination"/>).</t>
<t><strong>The cost to the signer is nil.</strong> 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.</t>
<t><xref target="RFC9864"/> RECOMMENDS rather than requires that a JWK carry <tt>alg</tt>, 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 <xref target="RFC9864"/> allows is unavailable — for OKP and EC keys it plainly is available.</t>
</section>
</section>

</back>

</rfc>
