<?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-04" 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-04" 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 two HTTP header fields and one Accept-Signature parameter for use with HTTP Message Signatures as defined in RFC 9421. The Signature-Key request header distributes public keys used to verify signatures, with five 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), JWT-based delegation (jwt), and X.509 certificate chains (x509). The sigkey parameter extends Accept-Signature (RFC 9421 Section 5) to indicate the type of Signature-Key the server requires. The Signature-Error response header provides structured error information when signature verification fails. 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>This document defines:</t>

<ul>
<li><t><strong>Signature-Key</strong> (<eref target="#signature-key-http-request-header">Signature-Key HTTP Request Header</eref>) — a request header that distributes public keys for HTTP Message Signature verification. The header supports five 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>JWT (jwt)</strong> - Delegated keys embedded in signed JWTs for horizontal scale</li>
<li><strong>X.509 (x509)</strong> - Certificate-based verification with PKI trust chains</li>
</ol></li>
</ul>
<t>Additional schemes may be defined through the IANA registry established by this document.</t>

<ul>
<li><t><strong>sigkey</strong> (<eref target="#accept-signature-sigkey-parameter">Accept-Signature sigkey Parameter</eref>) — a parameter for the Accept-Signature header (<xref target="RFC9421"/>, Section 5) that indicates the type of Signature-Key the server requires. This extends RFC 9421's existing mechanism for requesting signatures rather than defining a new header.</t>
</li>
<li><t><strong>Signature-Error</strong> (<eref target="#signature-error-http-response-header">Signature-Error HTTP Response Header</eref>) — a response header that provides structured error information when signature verification fails, enabling clients to diagnose and correct signing issues.</t>
</li>
</ul>
<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>
</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>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 SHOULD 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 <eref target="#signature-key-integrity">Signature-Key Integrity</eref> in Security Considerations. Alternative key distribution mechanisms outside this specification may be used for scenarios requiring independent signature addition.</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>
</ul>

<artwork><![CDATA[Signature-Key: sig=hwk;kty="OKP";crv="Ed25519";x="JrQLj5P..."
]]>
</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>
</ul>

<artwork><![CDATA[Signature-Key: sig=hwk;kty="EC";crv="P-256";x="f83OJ3D...";y="x_FEzRu..."
]]>
</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>
</ul>

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

<ul>
<li><t>The <tt>alg</tt> parameter MUST NOT be present (algorithm is derived from the key type and curve)</t>
</li>
<li><t>The <tt>kid</tt> parameter SHOULD NOT be used</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-self-issued-key-delegation-jkt-jwt"><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 spacing="compact">
<li><tt>jwt</tt> (REQUIRED, String) - Compact-serialized JWT</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>
</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><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><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>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-discovery-jwks-uri"><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</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><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="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 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>cnf.jwk</tt> claim with embedded JWK</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>SHOULD contain standard claims: <tt>sub</tt>, <tt>exp</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, to optimize for a quick rejection</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, extract <tt>jwks_uri</tt>. Fetch JWKS from <tt>jwks_uri</tt>, find key matching <tt>kid</tt> in 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="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><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>

<section anchor="accept-signature-sigkey-parameter"><name>Accept-Signature sigkey Parameter</name>
<t><xref target="RFC9421"/> Section 5 defines the <tt>Accept-Signature</tt> response header for requesting HTTP Message Signatures. This document extends <tt>Accept-Signature</tt> with a <tt>sigkey</tt> parameter that indicates the type of Signature-Key the server requires.</t>

<section anchor="parameter-definition"><name>Parameter Definition</name>
<t>The <tt>sigkey</tt> parameter is an Item parameter on each member of the <tt>Accept-Signature</tt> Dictionary. Its value is a Token (<xref target="RFC8941"/>, Section 3.3.4) with three defined values:</t>
<table>
<thead>
<tr>
<th>Value</th>
<th>Meaning</th>
<th>Acceptable Signature-Key schemes</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>jkt</tt></td>
<td>Pseudonymous key identified by JWK Thumbprint</td>
<td>hwk, jkt-jwt</td>
</tr>

<tr>
<td><tt>uri</tt></td>
<td>Key identified by a URI</td>
<td>jwks_uri, jwt, x509 (with URI SAN)</td>
</tr>

<tr>
<td><tt>x509</tt></td>
<td>Key from an X.509 certificate chain</td>
<td>x509</td>
</tr>
</tbody>
</table><t>These values represent ordered levels of identification. A server requesting <tt>sigkey=uri</tt> accepts any scheme that provides a URI-based identifier. A server requesting <tt>sigkey=x509</tt> specifically requires PKI infrastructure.</t>
<t>When <tt>sigkey</tt> is present, the <tt>keyid</tt> parameter (<xref target="RFC9421"/>, Section 5) SHOULD NOT be included and MUST be ignored by the client. Key identification is handled by the Signature-Key header schemes, not by <tt>keyid</tt>. The <tt>algs</tt> and <tt>tag</tt> parameters remain applicable alongside <tt>sigkey</tt>.</t>
</section>

<section anchor="label-binding"><name>Label Binding</name>
<t>The signature label in <tt>Accept-Signature</tt> ties together all four headers on the signed request. When a server requests:</t>

<sourcecode type="http"><![CDATA[Accept-Signature: sig1=("@method" "@path" "@authority");
    alg="ecdsa-p256-sha256";sigkey=uri
]]>
</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;keyid="https://client.example"
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="response-status-codes"><name>Response Status Codes</name>
<t><tt>Accept-Signature</tt> with a <tt>sigkey</tt> parameter can be set for any response. Below is a list of what it 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 appropriate 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 <tt>Accept-Signature</tt> with <tt>sigkey</tt> to its existing 429 responses with zero risk. Legacy clients ignore the unknown header 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="sigkey-semantics"><name>sigkey Semantics</name>

<section anchor="jkt"><name>jkt</name>
<t>The server requires a signed request using a pseudonymous Signature-Key scheme (hwk or jkt-jwt). The server can track the client by JWK Thumbprint (<xref target="RFC7638"/>) without knowing its identity. This is useful for rate limiting anonymous requests, tracking repeat visitors by key thumbprint, spam prevention without requiring verified identity, and hardware-backed pseudonymous identity.</t>
</section>

<section anchor="uri"><name>uri</name>
<t>The server requires a signed request with a URI-identified Signature-Key (jwks_uri, jwt, or x509 with a URI SAN). This is useful for API access policies based on known clients, webhook signature verification, and allowlisting trusted clients for elevated rate limits.</t>
</section>

<section anchor="x509"><name>x509</name>
<t>The server requires a signed request using an X.509 certificate chain (x509 scheme). This is useful for enterprise environments with PKI infrastructure, regulated industries requiring certificate-based authentication, and scenarios requiring certificate revocation checking.</t>
<t><xref target="RFC9421"/> Section 5.2 defines the processing of <tt>Accept-Signature</tt> by the client. If the <tt>sigkey</tt> parameter is unsupported, the client MAY ignore it.</t>
<t>If a client already knows the server's <tt>sigkey</tt> requirement (from a previous interaction or metadata), it MAY sign the initial request directly without waiting for a challenge response.</t>
</section>
</section>

<section anchor="incremental-adoption"><name>Incremental Adoption</name>
<t><tt>Accept-Signature</tt> with <tt>sigkey</tt> is designed for zero-coordination deployment. The <tt>sigkey</tt> parameter is unknown to legacy clients and ignored per Structured Fields semantics — servers can add it to existing responses without breaking anything.</t>
<t><strong>Stage 1 — Rate limiting (429):</strong> A server adds <tt>Accept-Signature</tt> with <tt>sigkey=jkt</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</tt> and <tt>sigkey=jkt</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 upgrades from <tt>sigkey=jkt</tt> to <tt>sigkey=uri</tt> on sensitive paths, requiring verifiable client identity via <tt>jwks_uri</tt>, <tt>jwt</tt>, or <tt>x509</tt> schemes. 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><tt>Accept-Signature</tt> 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 <tt>Accept-Signature</tt> with <tt>sigkey</tt>; 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: sig1=("@method" "@path" "@authority");
    alg="ecdsa-p256-sha256";sigkey=uri
]]>
</sourcecode>
<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 <tt>Accept-Signature</tt> for authentication:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="x7Tg2pLq", method="example",
    request="eyJhbW91bnQiOiIxMDAw..."
Accept-Signature: sig1=("@method" "@path" "@authority");sigkey=jkt
]]>
</sourcecode>
</section>

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

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
Accept-Signature: sig1=("@method" "@path" "@authority");sigkey=jkt
]]>
</sourcecode>
<t>Identity with algorithm restriction:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 401 Unauthorized
Accept-Signature: sig1=("@method" "@authority" "@path");
    alg="ecdsa-p256-sha256";sigkey=uri
]]>
</sourcecode>
<t>Rate limiting with pseudonymous upgrade:</t>

<sourcecode type="http"><![CDATA[HTTP/1.1 429 Too Many Requests
Retry-After: 30
Accept-Signature: sig1=("@method" "@path" "@authority");sigkey=jkt
]]>
</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: sig1=("@method" "@path" "@authority");sigkey=jkt
]]>
</sourcecode>
</section>

<section anchor="client-processing"><name>Client Processing</name>
<t>When a client receives a response containing an <tt>Accept-Signature</tt> header with a <tt>sigkey</tt> parameter, it MAY retry the request with an HTTP Message Signature using a Signature-Key scheme appropriate for the indicated <tt>sigkey</tt> value.</t>
<t>When a <tt>429</tt> response includes both <tt>Retry-After</tt> and <tt>Accept-Signature</tt> with <tt>sigkey</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</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. 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>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,
    supported_algorithms=("ed25519" "ecdsa-p256-sha256")
]]>
</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>. The <tt>type</tt> member SHOULD 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.</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 rsa-v1_5-sha256"
}
]]>
</sourcecode>
<t>Extension members in the Problem Details object (e.g., <tt>supported_algorithms</tt>) 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</tt> header with <tt>sigkey</tt> 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>

<ul spacing="compact">
<li><tt>supported_algorithms</tt> (REQUIRED): An Inner List of String (<xref target="RFC8941"/>, Section 3.1.1) listing the algorithms the server accepts, using identifiers from the HTTP Signature Algorithms registry (<xref target="RFC9421"/>, Section 6.2). The registry description for each identifier specifies the corresponding key type and curve. The response MUST include this member.</li>
</ul>

<sourcecode type="http"><![CDATA[Signature-Error: error=unsupported_algorithm,
    supported_algorithms=("ed25519" "ecdsa-p256-sha256")
]]>
</sourcecode>
</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> (OPTIONAL): An Inner List of String (<xref target="RFC8941"/>, Section 3.1.1) listing the covered components the server requires. The response SHOULD include this member.</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="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="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>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>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</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>jkt-jwt</strong>: Cache embedded keys until JWT expiration; cache by <tt>iss</tt> thumbprint URI</t>
</li>
</ul>
<t>Verifiers SHOULD implement cache limits to prevent resource exhaustion attacks.</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. Implementations SHOULD use the shortest practical lifetime. 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.</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>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, not by the optional <tt>alg</tt> parameter in Signature-Input (<xref target="RFC9421"/>, Section 2.3). For JWK-based schemes (hwk, jkt-jwt, jwks_uri, jwt), the algorithm is identified by the key type and curve (<tt>kty</tt> + <tt>crv</tt>) or by the <tt>alg</tt> parameter in the JWK (<xref target="RFC7517"/>). For the x509 scheme, the algorithm is determined by the certificate's public key type.</t>
<t>If the <tt>alg</tt> parameter is present in Signature-Input, verifiers MUST verify it is consistent with the key material. If it is absent, verifiers derive the algorithm from the key.</t>
<t>Verifiers MUST:</t>

<ul>
<li><t>Validate the algorithm against policy (reject weak algorithms)</t>
</li>
<li><t>Ensure the key type is consistent with the derived algorithm</t>
</li>
<li><t>Reject keys whose type does not match an acceptable algorithm</t>
</li>
</ul>
</section>

<section anchor="signature-key-integrity"><name>Signature-Key Integrity</name>
<t>The Signature-Key header SHOULD 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 SHOULD reject requests where <tt>signature-key</tt> is not a covered component.</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, x509, and 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, 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>
</section>

<section anchor="signature-key-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 require Specification Required per <xref target="RFC8126"/>.</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>jwt</td>
<td>JWT Confirmation Key - delegated key in JWT</td>
<td>[this document]</td>
</tr>

<tr>
<td>x509</td>
<td>X.509 Certificate - PKI certificate chain</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="http-signature-metadata-parameters"><name>HTTP Signature Metadata Parameters</name>
<t>This document registers the following parameter in the "HTTP Signature Metadata Parameters" registry established by <xref target="RFC9421"/>, Section 6.3.</t>
<t>Parameter Name: sigkey</t>
<t>Status: standard</t>
<t>Specification document(s): [this document]</t>
<t>Description: Indicates the type of Signature-Key the server requires. Defined values: <tt>jkt</tt> (pseudonymous key identified by JWK Thumbprint), <tt>uri</tt> (key identified by a URI), <tt>x509</tt> (X.509 certificate chain).</t>
</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 Specification Required policy (<xref target="RFC8126"/>).</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>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>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>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<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.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.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"/>
</references>
<references><name>Informative References</name>
<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.7435.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8414.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="document-history"><name>Document History</name>
<t><em>Note: This section is to be removed before publishing as an RFC.</em></t>

<section anchor="draft-hardt-httpbis-signature-key-04"><name>draft-hardt-httpbis-signature-key-04</name>

<ul spacing="compact">
<li>Renamed spec from "HTTP Signature-Key Header" to "HTTP Signature Keys"</li>
<li>Added <tt>sigkey</tt> parameter for Accept-Signature (RFC 9421 Section 5) with three values: <tt>jkt</tt> (pseudonymous), <tt>uri</tt> (URI-identified), <tt>x509</tt> (PKI certificate)</li>
<li>Added Signature-Error response header for structured signature verification error responses</li>
<li>Added incremental adoption section describing zero-coordination deployment via 429/401/402 status codes</li>
<li>Added privacy considerations for key thumbprint tracking, agent identity disclosure, and JWKS fetch side channel</li>
<li>Registered <tt>sigkey</tt> in the HTTP Signature Metadata Parameters registry (RFC 9421 Section 6.3)</li>
<li>Established Signature Error Code Registry</li>
</ul>
</section>

<section anchor="draft-hardt-httpbis-signature-key-03"><name>draft-hardt-httpbis-signature-key-03</name>

<ul spacing="compact">
<li>Added jkt-jwt scheme for self-issued key delegation</li>
<li>Renamed <tt>well-known</tt> parameter to <tt>dwk</tt> (dot well-known)</li>
<li>Added <tt>iss</tt> and <tt>dwk</tt> claims to jwt scheme (SHOULD) for issuer key discovery</li>
<li>Added early validation step to jwt verification procedure (format, typ, exp checks before network fetches)</li>
<li>Added TOFU reference (RFC 7435) to jkt-jwt scheme</li>
<li>Added design rationale for jwks_uri vs inline JWKS</li>
<li>Moved hwk string vs byte sequence design note to rationale appendix</li>
<li>Reordered schemes</li>
<li>Added acknowledgments</li>
</ul>
</section>

<section anchor="draft-hardt-httpbis-signature-key-02"><name>draft-hardt-httpbis-signature-key-02</name>

<ul spacing="compact">
<li>Changed x5t parameter to byte sequence per reviewer feedback</li>
<li>Added structured field types to all parameters</li>
<li>Added design note explaining string vs byte sequence choice for hwk</li>
</ul>
</section>

<section anchor="draft-hardt-httpbis-signature-key-01"><name>draft-hardt-httpbis-signature-key-01</name>

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

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

<section anchor="why-jwks-uri-instead-of-inline-jwks"><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-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-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>

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

</back>

</rfc>
