RFC 9421

HTTP Message Signatures

๐Ÿ“… published February 2024, from ietf

Hiii bestie ๐Ÿ’– okay so this is part 1 of 23 of RFC 9421, aka HTTP Message Signatures โœจ. It's a Standards Track doc from the IETF (Internet Engineering Task Force ๐ŸŒธ), dated February 2024. The main authors are A. Backman (Ed., Amazon) ๐Ÿงš, J. Richer (Ed., Bespoke Engineering) ๐Ÿ’…, and M. Sporny (Digital Bazaar) ๐Ÿซถ. The ISSN is 2070-1721. The title block doesn't list anything this RFC obsoletes or updates, so it's standing totally on its own ๐Ÿ‘‘.

Abstract ๐ŸŒˆ

Okay so this doc is all about making digital signatures or MACs (message authentication codes ๐Ÿ”) over parts of an HTTP message. Those parts are called components. It's built for when the signer doesn't know the whole message, and when the message gets transformed (like by intermediaries ๐Ÿฆ‹) before the verifier sees it. It also has a way to ask for a signature on a later HTTP message in the same ongoing exchange ๐Ÿ’Œ.

Status of This Memo ๐ŸŽ€

This is an Internet Standards Track document, so it's officially a standard ๐Ÿ’….

It's a product of the IETF and represents the consensus of the IETF community. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG). More info on Internet Standards is in Section 2 of RFC 7841. Info about the current status, any errata, and how to give feedback lives at https://www.rfc-editor.org/info/rfc9421 ๐Ÿ”—.

Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved. โœจ

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication. Please read these carefully, because they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they are provided without warranty as described in the Revised BSD License. ๐Ÿซถ

Table of Contents ๐Ÿ—‚๏ธ

   1.  Introduction
     1.1.  Conventions and Terminology
     1.2.  Requirements
     1.3.  HTTP Message Transformations
     1.4.  Application of HTTP Message Signatures
   2.  HTTP Message Components
     2.1.  HTTP Fields
       2.1.1.  Strict Serialization of HTTP Structured Fields
       2.1.2.  Dictionary Structured Field Members
       2.1.3.  Binary-Wrapped HTTP Fields
       2.1.4.  Trailer Fields
     2.2.  Derived Components
       2.2.1.  Method
       2.2.2.  Target URI
       2.2.3.  Authority
       2.2.4.  Scheme
       2.2.5.  Request Target
       2.2.6.  Path
       2.2.7.  Query
       2.2.8.  Query Parameters
       2.2.9.  Status Code
     2.3.  Signature Parameters
     2.4.  Signing Request Components in a Response Message
     2.5.  Creating the Signature Base
   3.  HTTP Message Signatures
     3.1.  Creating a Signature
     3.2.  Verifying a Signature
       3.2.1.  Enforcing Application Requirements
     3.3.  Signature Algorithms
       3.3.1.  RSASSA-PSS Using SHA-512
       3.3.2.  RSASSA-PKCS1-v1_5 Using SHA-256
       3.3.3.  HMAC Using SHA-256
       3.3.4.  ECDSA Using Curve P-256 DSS and SHA-256
       3.3.5.  ECDSA Using Curve P-384 DSS and SHA-384
       3.3.6.  EdDSA Using Curve edwards25519
       3.3.7.  JSON Web Signature (JWS) Algorithms
   4.  Including a Message Signature in a Message
     4.1.  The Signature-Input HTTP Field
     4.2.  The Signature HTTP Field
     4.3.  Multiple Signatures
   5.  Requesting Signatures
     5.1.  The Accept-Signature Field
     5.2.  Processing an Accept-Signature
   6.  IANA Considerations
     6.1.  HTTP Field Name Registration
     6.2.  HTTP Signature Algorithms Registry
       6.2.1.  Registration Template
       6.2.2.  Initial Contents
     6.3.  HTTP Signature Metadata Parameters Registry
       6.3.1.  Registration Template
       6.3.2.  Initial Contents
     6.4.  HTTP Signature Derived Component Names Registry
       6.4.1.  Registration Template
       6.4.2.  Initial Contents
     6.5.  HTTP Signature Component Parameters Registry
       6.5.1.  Registration Template
       6.5.2.  Initial Contents
   7.  Security Considerations
     7.1.  General Considerations
       7.1.1.  Skipping Signature Verification
       7.1.2.  Use of TLS
     7.2.  Message Processing and Selection
       7.2.1.  Insufficient Coverage
       7.2.2.  Signature Replay
       7.2.3.  Choosing Message Components
       7.2.4.  Choosing Signature Parameters and Derived Components
               over HTTP Fields
       7.2.5.  Signature Labels
       7.2.6.  Multiple Signature Confusion
       7.2.7.  Collision of Application-Specific Signature Tag
       7.2.8.  Message Content
     7.3.  Cryptographic Considerations
       7.3.1.  Cryptography and Signature Collision
       7.3.2.  Key Theft
       7.3.3.  Symmetric Cryptography
       7.3.4.  Key Specification Mixup
       7.3.5.  Non-deterministic Signature Primitives
       7.3.6.  Key and Algorithm Specification Downgrades
       7.3.7.  Signing Signature Values
     7.4.  Matching Signature Parameters to the Target Message
       7.4.1.  Modification of Required Message Parameters
       7.4.2.  Matching Values of Covered Components to Values in the
               Target Message
       7.4.3.  Message Component Source and Context
       7.4.4.  Multiple Message Component Contexts
     7.5.  HTTP Processing
       7.5.1.  Processing Invalid HTTP Field Names as Derived
               Component Names
       7.5.2.  Semantically Equivalent Field Values
       7.5.3.  Parsing Structured Field Values
       7.5.4.  HTTP Versions and Component Ambiguity
       7.5.5.  Canonicalization Attacks
       7.5.6.  Non-List Field Values
       7.5.7.  Padding Attacks with Multiple Field Values
       7.5.8.  Ambiguous Handling of Query Elements
   8.  Privacy Considerations
     8.1.  Identification through Keys
     8.2.  Signatures do not provide confidentiality
     8.3.  Oracles
     8.4.  Required Content
   9.  References
     9.1.  Normative References
     9.2.  Informative References
   Appendix A.  Detecting HTTP Message Signatures
   Appendix B.  Examples
     B.1.  Example Keys
       B.1.1.  Example RSA Key
       B.1.2.  Example RSA-PSS Key
       B.1.3.  Example ECC P-256 Test Key
       B.1.4.  Example Ed25519 Test Key
       B.1.5.  Example Shared Secret
     B.2.  Test Cases
       B.2.1.  Minimal Signature Using rsa-pss-sha512
       B.2.2.  Selective Covered Components Using rsa-pss-sha512
       B.2.3.  Full Coverage Using rsa-pss-sha512
       B.2.4.  Signing a Response Using ecdsa-p256-sha256
       B.2.5.  Signing a Request Using hmac-sha256
       B.2.6.  Signing a Request Using ed25519
     B.3.  TLS-Terminating Proxies
     B.4.  HTTP Message Transformations
   Acknowledgements
   Authors' Addresses

1. Introduction โœจ

Okay, so message integrity and authenticity are, like, the security properties ๐Ÿ”’ for a lot of HTTP apps. Devs usually lean on the transport layer to provide them by running the app over TLS [TLS]. But TLS only guarantees these properties over a single TLS connection, and the path between client and app can be made of several independent TLS connections. Maybe the app is hosted behind a TLS-terminating gateway, or the client is behind a TLS Inspection appliance. So TLS can't guarantee end-to-end message integrity or authenticity between the client and the app ๐Ÿ™ˆ. Some environments also make TLS impractical (like presenting client certificates from a browser) or make it hard to use the features needed for message authenticity. Plus, some apps need a higher-level application-specific key bound to the HTTP message, separate from any TLS certificates in use. So yes, TLS is iconic for many HTTP apps, but it's not a universal solution ๐Ÿ’€.

Also, lots of apps need to generate and verify signatures even though they don't fully know the HTTP message as it shows up on the wire ๐Ÿ‘€. That's because libraries, proxies, or application frameworks can alter or hide parts of the message from the app at signing or verification time. These apps need a way to protect the parts of the message that matter most to them, without breaking layering and abstraction ๐Ÿงš.

Finally, object-based signature mechanisms like JSON Web Signature [JWS] need the exact signed information to arrive intact ๐Ÿ“ฆ. When you apply those to an HTTP message, elements of the message have to be duplicated in the object payload, either directly or through a hash. That adds complexity, since the repeated info has to be carefully checked for consistency when the signature is verified ๐Ÿ˜ค.

So this document defines a mechanism for end-to-end integrity and authenticity for components of an HTTP message, using a detached signature on HTTP messages ๐Ÿฆ‹. Apps can create digital signatures or MACs over only the components that are meaningful and appropriate for them. Strict canonicalization rules mean the verifier can check the signature even if the message has been transformed in many of the ways HTTP permits โœจ.

The signing mechanism described here has three parts:

  • A common nomenclature and canonicalization rule set for the different protocol elements and other components of HTTP messages, used to create the signature base (Section 2).
  • Algorithms for generating and verifying signatures over HTTP message components using this signature base through the application of cryptographic primitives (Section 3).
  • A mechanism for attaching a signature and related metadata to an HTTP message, and for parsing attached signatures and metadata from HTTP messages. To make this work, the document defines the Signature-Input and Signature fields (Section 4).

This document also has a mechanism for negotiating the use of signatures in one or more subsequent messages via the Accept-Signature field (Section 5). This optional negotiation mechanism can be used along with opportunistic or application-driven message signatures by either party ๐Ÿค.

The mechanisms defined here are important tools for building an overall security mechanism for an application ๐Ÿ› ๏ธ. This toolkit gives you some powerful capabilities, but it isn't enough on its own to create an overall security story ๐Ÿซ . In particular, the requirements listed in Section 1.4 and the security considerations discussed in Section 7 are of high importance to all implementors of this specification ๐Ÿšจ. For example, this specification does not define a means to directly cover HTTP message content (defined in Section 6.4 of [HTTP]). Instead, it relies on the Digest specification [DIGEST] to provide a hash of the message content, as discussed in Section 7.2.8 ๐Ÿ’œ.

(More of the Introduction, starting with Section 1.1, comes in part 2 ๐ŸŽ€)

1.1. Conventions and Terminology ๐Ÿ’–

okay so bestie, the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" mean what BCP 14 [RFC2119] [RFC8174] says, but ONLY when they show up in all capitals like this ๐Ÿ”ฅ lowercase "may" is just a regular word, no cap ๐Ÿ’…

the terms "HTTP message", "HTTP request", "HTTP response", "target URI", "gateway", "header field", "intermediary", "request target", "trailer field", "sender", "method", and "recipient" all mean what [HTTP] defines them as โœจ

for brevity, the word "signature" on its own covers BOTH digital signatures (asymmetric cryptography) AND keyed MACs (symmetric cryptography) ๐Ÿงš the verb "sign" works the same way and means generating either one over a given signature base. "digital signature" is the special one, and it only means the output of an asymmetric signing operation ๐Ÿ”

this doc borrows some data types from Section 3 of [STRUCTURED-FIELDS]: List, Inner List, Dictionary, Item, String, Integer, Byte Sequence, and Boolean ๐ŸŽ€

it also defines some string constructions with ABNF [ABNF] and uses these ABNF rules: VCHAR, SP, DQUOTE, and LF. From [STRUCTURED-FIELDS] it grabs sf-string, inner-list, and parameters. From [HTTP] and [HTTP/1.1] it grabs field-content, obs-fold, and obs-text ๐ŸŒธ

on top of all that, here are the extra terms it uses:

HTTP Message Signature: a digital signature or keyed MAC that covers one or more portions of an HTTP message. A single HTTP message can totally have multiple HTTP message signatures, so no drama there ๐Ÿ’œ

Signer: the entity that is generating, or has generated, an HTTP message signature. Multiple entities can be signers and each apply their own separate HTTP message signatures to the same message, which is giving teamwork ๐Ÿซถ

Verifier: an entity that is verifying, or has verified, an HTTP message signature against an HTTP message. One signature can get verified multiple times, maybe by different entities, so it's basically a popular girl ๐ŸŒŸ

HTTP Message Component: a portion of an HTTP message that can be covered by an HTTP message signature ๐Ÿฆ‹

Derived Component: an HTTP message component that gets derived from the message through a specified algorithm or process. See Section 2.2 for the deets โœจ

HTTP Message Component Name: a String that identifies where a component comes from, like a field name or a derived component name ๐Ÿ“

HTTP Message Component Identifier: the combination of an HTTP message component name and any parameters. Together they uniquely identify one specific component for a particular HTTP message signature and the HTTP message it applies to. No mix-ups allowed ๐Ÿ‘‘

HTTP Message Component Value: the value tied to a given component identifier inside the context of one particular HTTP message. Values come from the message and are usually put through a canonicalization process first ๐Ÿงผ

Covered Components: an ordered set of HTTP message component identifiers for fields (Section 2.1) and derived components (Section 2.2) that says which components the signature covers. The @signature-params identifier itself is NEVER included in this set ๐Ÿ™…โ€โ™€๏ธ The order is preserved and shared between signer and verifier so they can rebuild the signature base together ๐Ÿ’…

Oops, I just typed NEVER in caps, but that's just me being dramatic. The actual spec word in this bullet is "never", so no requirement is changed. Carrying on ๐Ÿ˜ญ

Signature Base: the sequence of bytes that the signer and verifier generate from the covered components set and the HTTP message. The cryptographic algorithm processes the signature base to produce or verify the HTTP message signature ๐Ÿ”‘

HTTP Message Signature Algorithm: a cryptographic algorithm that describes the signing and verification process for the signature. It's defined in terms of the HTTP_SIGN and HTTP_VERIFY primitives in Section 3.3 ๐Ÿงฉ

Key Material: the key material needed to create or verify the signature. It's often identified with an explicit key identifier, so the signer can tell the verifier which key got used ๐Ÿ—๏ธ

Creation Time: a timestamp for when the signature was generated, as the signer asserts it โฐ

Expiration Time: a timestamp for the point in time after which the verifier should no longer accept the signature, as the signer asserts it. โณ

Target Message: the HTTP message that an HTTP message signature gets applied to ๐ŸŽฏ

Signature Context: the data source the HTTP message component values come from. It includes the target message plus any extra info the signer or verifier might have, like the full target URI of a request or the related request message for a response. It's the whole vibe check ๐Ÿ•ต๏ธโ€โ™€๏ธ

the term "UNIX timestamp" refers to what Section 4.16 of [POSIX.1] calls "seconds since the Epoch" ๐Ÿ•ฐ๏ธ

this doc has non-normative examples of partial and complete HTTP messages. Some examples use a single trailing backslash (\) to show line wrapping for long values, as per [RFC8792]. The \ and the leading spaces on wrapped lines are NOT part of the value ๐Ÿ™ˆ


1.2. Requirements ๐ŸŽ€

okay so HTTP lets intermediaries transform messages in a bunch of ways, and sometimes it even requires them to ๐Ÿ˜ณ that means a recipient can get a message that isn't bitwise-equivalent to what got sent. In that case the recipient can't verify integrity protections over the raw bytes of the sender's message, because checking a digital signature or MAC needs signer and verifier to have the exact same signature base. The raw bytes just aren't reliable for building a signature base, so signer and verifier each have to build their own from their own version of the message, using a method that can handle safe changes that don't change what the message means ๐Ÿง โœจ

it's honestly impractical to strictly define what counts as a safe change versus an unsafe one. Apps use HTTP in tons of different ways and might disagree about whether some piece of info (like the message content, the method, or a specific header field) matters. So a general-purpose solution has to give signers some control over which message components get signed ๐ŸŽ›๏ธ

then there are HTTP apps running in places that don't give full access to or control over HTTP messages, like a web browser's JavaScript environment. Others use libraries that hide the protocol details, like the Java HTTP Client (HttpClient) library at https://openjdk.java.net/groups/net/httpclient/intro.html. These apps still need to generate and verify signatures even though they don't know the whole HTTP message ๐Ÿซฃ๐Ÿ’•


1.3. HTTP Message Transformations ๐Ÿฆ‹

as I said earlier, HTTP explicitly permits, and sometimes requires, implementations to transform messages in a bunch of ways. Implementations are required to tolerate many of these transformations ๐Ÿ™Œ Here's a non-normative, non-exhaustive list of transformations that could happen under HTTP, just for context:

  • Reordering of fields with different field names (Section 5.3 of [HTTP]) ๐Ÿ”€
  • Combination of fields with the same field name (Section 5.2 of [HTTP]) ๐Ÿงƒ
  • Removal of fields listed in the Connection header field (Section 7.6.1 of [HTTP]) โœ‚๏ธ
  • Addition of fields that indicate control options (Section 7.6.1 of [HTTP]) ๐ŸŽš๏ธ
  • Addition or removal of a transfer coding (Section 7.7 of [HTTP]) ๐Ÿ“ฆ
  • Addition of fields such as Via (Section 7.6.3 of [HTTP]) and Forwarded (Section 4 of [RFC7239]) ๐Ÿ›ฃ๏ธ
  • Conversion between different versions of HTTP (e.g., HTTP/1.x to HTTP/2, or vice versa) ๐Ÿ”„
  • Changes in case (e.g., "Origin" to "origin") of any case-insensitive components such as field names, request URI scheme, or host ๐Ÿ” 
  • Changes to the request target and authority that, when applied together, do not result in a change to the message's target URI, as defined in Section 7.1 of [HTTP] ๐ŸŽฏ

there are also some transformations that are deprecated or otherwise not allowed, but they can still happen in the wild ๐Ÿ‘€ These can still be handled without breaking the signature, like:

  • Use, addition, or removal of leading or trailing whitespace in a field value ๐Ÿซง
  • Use, addition, or removal of obs-fold in field values (Section 5.2 of [HTTP/1.1]) ๐Ÿงต

so these are transformations that should not prevent signature verification, even when they happen on message components that ARE covered by the signature. And any changes to components NOT covered by the signature should not prevent signature verification either ๐Ÿ’–

Appendix B.4 has some examples of these kinds of transformations and what effect they have on the message signature ๐Ÿ“š

other transformations, though, like parsing and reserializing the field values of a covered component, or changing the value of a derived component, can cause a signature to stop validating against a target message ๐Ÿ˜ฌ So apps using this spec need to be careful and make sure the transformations they expect are handled by the choice of covered components ๐Ÿซถ

1.4. Application of HTTP Message Signatures ๐ŸŽ€

okay so HTTP message signatures are basically a general-purpose tool ๐Ÿงฐ that works in a ton of different situations. ๐Ÿฆ‹ To use them properly and safely, an application or profile of this spec MUST specify, at minimum, all of the following:

  • The component identifiers and signature parameters you expect. These are the ones that MUST be included in the covered components list. โœจ Here's an example: an authorization protocol could require the Authorization field to be covered so the credentials are protected, and require the signature parameters to include a created parameter (Section 2.3). An API that cares about semantically relevant content could require the Content-Digest field defined in [DIGEST] to be present and covered, and also require a tag parameter (Section 2.3) that's specific to the API being protected. ๐Ÿ’…
  • The expected Structured Field types [STRUCTURED-FIELDS] for any required or expected covered component fields or parameters. ๐Ÿงฉ
  • A way to get the key material used to verify the signature. Usually an application uses the keyid parameter of the signature parameters (Section 2.3) and defines rules for resolving a key from there. The right key could also be known some other way, like if the signer's key was preregistered. ๐Ÿ”‘
  • The set of allowable signature algorithms that signers can use and verifiers will accept. ๐Ÿ“œ
  • A way to check that the signature algorithm used to verify the signature fits the key material and the context of the message. For example, the process could use the alg parameter of the signature parameters (Section 2.3) to state the algorithm explicitly, derive it from the key material, or use a preconfigured algorithm that the signer and verifier agreed on. ๐Ÿซถ
  • A way to check that a given key and algorithm used for a signature fit the context of the message. For example, a server that only expects ECDSA signatures should know to reject any RSA signatures, and a server that expects asymmetric cryptography should know to reject any symmetric cryptography. ๐Ÿ™…โ€โ™€๏ธ
  • A way to figure out the context for deriving message components from an HTTP message and its application context. This is usually the target HTTP message itself, but the context could also include extra info the application knows through configuration, like an external hostname. ๐ŸŒธ
  • If you need a binding between a request and a response using the mechanism in Section 2.4, every element of the request message and the response message that would be needed to provide the properties of that binding. ๐Ÿ”—
  • The error messages and codes the verifier sends back to the signer when the signature is invalid, the key material is inappropriate, the validity time window is out of specification, a component value can't be calculated, or any other error happens during verification. ๐Ÿšจ For example, if a signature is being used as an authentication mechanism, an HTTP status code of 401 (Unauthorized) or 403 (Forbidden) could be appropriate. If the response comes from an HTTP API, a response with a status code like 400 (Bad Request) could include more details [RFC7807] [RFC9457], such as a hint that the wrong key material was used. ๐Ÿ™ˆ

When you're picking these parameters, an application of HTTP message signatures has to make sure the verifier can access all the info it needs to rebuild the signature base. ๐Ÿง  For example, a server sitting behind a reverse proxy would need to know the original request URI in order to use the derived component @target-uri, even though the reverse proxy would change the apparent target URI (see also Section 7.4.3). ๐Ÿชž Also, an application that uses signatures in responses needs to make sure clients receiving signed responses can access all the signed parts of the message, including any parts of the request that the server signed using the req ("request-response") parameter (Section 2.4). ๐Ÿฆ‹

Details about this kind of profiling are up to the application and outside the scope of this spec. ๐Ÿ™Œ Some extra considerations are covered in Section 7, though. Especially when you're choosing the required set of component identifiers, you have to be careful that the coverage is sufficient for your application, as discussed in Sections 7.2.1 and 7.2.8. โš ๏ธ This spec only defines part of a full security system. When you build a complete security system on top of this tool, it's important to do a security analysis of the entire system, of which HTTP message signatures are just one piece. ๐Ÿ” Historical systems, like AWS Signature Version 4 [AWS-SIGv4], can be great inspiration and show examples of how to apply similar mechanisms to an application. Still, reviewing those systems doesn't remove the need for a security analysis of your own application of HTTP message signatures. ๐Ÿ’€


2. HTTP Message Components ๐Ÿง

So signers and verifiers can agree on which components are covered by a signature, this document defines component identifiers for components covered by an HTTP message signature. ๐ŸŽ€ It also gives a set of rules for deriving and canonicalizing the values tied to those identifiers from the HTTP message, plus a way to combine those canonicalized values into a signature base. โœจ

The signature context used to derive these values MUST be accessible to both the signer and the verifier of the message. ๐Ÿซถ The context MUST be the same across all components in a given signature. For example, it would be an error to use the raw query string for the @query derived component but combined query and form parameters for the @query-param derived component. ๐Ÿ™…โ€โ™€๏ธ For more on the message component context, see Section 7.4.3.

A component identifier is made of a component name plus any parameters attached to that name. ๐Ÿท๏ธ Each component name is either an HTTP field name (Section 2.1) or a registered derived component name (Section 2.2). Which parameters a component identifier can have depends on the identifier itself. The "HTTP Signature Component Parameters" registry, which catalogs all the possible parameters, is defined in Section 6.5. ๐Ÿ“š

Within a single list of covered components, each component identifier MUST occur only once. ๐Ÿ’… One component identifier is distinct from another if the component name differs, or if any of the parameters differ for the same component name. Multiple component identifiers with the same component name MAY be included as long as they have parameters that make them distinct, such as "foo";bar and "foo";baz. ๐Ÿ‘ฏโ€โ™€๏ธ The order of parameters MUST be preserved when processing a component identifier (like when parsing during verification). But parameter order isn't significant when you compare two component identifiers for equality. ๐ŸŒˆ That means "foo";bar;baz can't be in the same message as "foo";baz;bar, since they're equivalent. Still, a system processing one form is not allowed to transform it into the other form. ๐Ÿšซ

The component value tied to a component identifier is defined by the identifier itself. ๐Ÿงพ Component values MUST NOT contain newline (\n) characters. ๐Ÿ™…โ€โ™€๏ธ Some HTTP message components can go through transformations that change the bits but not the meaning of the value, like when field values get combined. ๐Ÿฆ‹ So message component values need to be canonicalized before they're signed, which makes sure a signature can still be verified after those intermediary transformations. ๐Ÿซง This document defines rules for each component identifier that turn its associated component value into that canonical form. ๐Ÿ’–

The next sections define the component identifier names, their parameters, their associated values, and the canonicalization rules for those values. ๐ŸŒธ How message components get combined into the signature base is defined in Section 2.5. ๐Ÿ“

2.1. HTTP Fields ๐ŸŽ€

okay so the component name for an HTTP field is just its field name, lowercased (as defined in Section 5.1 of [HTTP]). HTTP field names are case insensitive, but implementations MUST use lowercased field names (e.g., content-type, date, etag) when using them as component names. lowercase is the vibe, bestie โœจ

The component value is the field value for the named field, as defined in Section 5.5 of [HTTP]. The field value MUST be taken from the named header field of the target message, unless this behavior is overridden by additional parameters and rules, like the req and tr flags below. For most fields, the field value is an ASCII string, as recommended by [HTTP], and the component value is exactly that string. some implementations might use other encodings ๐Ÿ‘€, so all non-ASCII field values MUST be encoded to ASCII before they get added to the signature base. the bs parameter (Section 2.1.3) is the move for wrapping those problematic field values ๐Ÿซถ

Unless overridden by additional parameters and rules, HTTP field values MUST be combined into a single value (as defined in Section 5.2 of [HTTP]) to create the component value. Specifically, fields sent as multiple fields MUST be combined by concatenating the values with a single comma and a single space as the separator ("," + " "). Now, intermediaries are allowed to combine values with any amount of whitespace between the commas. If the verifier doesn't account for that, the signature can fail ๐Ÿ’€, because the signer and verifier end up looking at different component values in their signature bases.

For robustness, it is RECOMMENDED that signed messages include only a single instance of any field covered by the signature, especially when the value of a list-based field is serialized using the algorithm below. This gives the field value a better chance of staying untouched through intermediaries ๐Ÿฆ‹. When that isn't possible and multiple instances of a field really do need to be sent separately, it is RECOMMENDED that signers and verifiers process any list-based fields by taking all the individual field values and combining them with the strict algorithm below, to counter possible intermediary behavior. And if the field is a Structured Field of type List or Dictionary, you can get the same effect more directly by requiring the strict Structured Field serialization of the field value, as described in Section 2.1.1 โœจ

Note that some HTTP fields, like Set-Cookie [COOKIE], don't follow a syntax that lets you combine their values this way. The combined output isn't unambiguous when it comes from multiple inputs ๐Ÿ˜ญ. The component value is never parsed by the message signature process and only gets used as part of the signature base (Section 2.5), but you still need to be careful about including these fields in signatures, since the combined value could be ambiguous. the bs parameter (Section 2.1.3) wraps these problematic fields too. See Section 7.5.6 for more discussion about this issue.

If an implementation can't directly get the correctly combined value for a given field, the following algorithm produces canonicalized results for list-based fields:

  1. Create an ordered list of the field values of each instance of the field in the message, in the order they occur (or will occur) in the message.
  2. Strip leading and trailing whitespace from each item in the list. Note that since HTTP field values are not allowed to contain leading and trailing whitespace, this would be a no-op in a compliant implementation.
  3. Remove any obsolete line folding within the line, and replace it with a single space (" "), as discussed in Section 5.2 of [HTTP/1.1]. Note that this behavior is specific to HTTP/1.1 and does not apply to other versions of the HTTP specification, which do not allow internal line folding.
  4. Concatenate the list of values with a single comma (",") and a single space (" ") between each item.

The resulting string is the component value for the field.

Some HTTP fields have values with multiple valid serializations that mean the same thing, like allowing case-insensitive values that intermediaries could change ๐Ÿ™ˆ. Applications signing and processing such fields MUST consider how to handle their values, so the signer and verifier can derive the same value, as discussed in Section 7.5.2. no skipping this part, bestie ๐Ÿ’…

The following are non-normative examples of component values for header fields, using this example HTTP message fragment:

Host: www.example.com
Date: Tue, 20 Apr 2021 02:07:56 GMT
X-OWS-Header:   Leading and trailing whitespace.
X-Obs-Fold-Header: Obsolete
    line folding.
Cache-Control: max-age=60
Cache-Control:    must-revalidate
Example-Dict:  a=1,    b=2;x=1;y=2,   c=(a   b   c)

Here are the component values for these example header fields, shown in the signature base format from Section 2.5:

"host": www.example.com
"date": Tue, 20 Apr 2021 02:07:56 GMT
"x-ows-header": Leading and trailing whitespace.
"x-obs-fold-header": Obsolete line folding.
"cache-control": max-age=60, must-revalidate
"example-dict": a=1,    b=2;x=1;y=2,   c=(a   b   c)

Empty HTTP fields can be signed too when they're present in a message ๐ŸŒธ. The canonicalized value is the empty string. So this empty header field, where (SP) marks a single trailing space character before the empty field value:

X-Empty-Header:(SP)

gets serialized by the signature base generation algorithm (Section 2.5) with an empty string value after the colon and space that get added after the component identifier:

"x-empty-header":(SP)

Any HTTP field component identifiers MAY have the following parameters in specific circumstances. each one gets its own section:

  • sf: A Boolean flag indicating that the component value is serialized using strict encoding of the Structured Field value (Section 2.1.1).
  • key: A String parameter used to select a single member value from a Dictionary Structured Field (Section 2.1.2).
  • bs: A Boolean flag indicating that individual field values are encoded using Byte Sequence data structures before being combined into the component value (Section 2.1.3).
  • req: A Boolean flag for signed responses indicating that the component value is derived from the request that triggered this response message and not from the response message directly. Note that this parameter can also be applied to any derived component identifiers that target the request (Section 2.4).
  • tr: A Boolean flag indicating that the field value is taken from the trailers of the message as defined in Section 6.5 of [HTTP]. If this flag is absent, the field value is taken from the header fields of the message as defined in Section 6.3 of [HTTP] (Section 2.1.4).

Multiple parameters MAY be specified together, though some combinations are redundant or incompatible ๐Ÿ™ƒ. For example, the sf parameter's functionality is already covered when the key parameter is used on a Dictionary item, since key requires strict serialization of the value. And the bs parameter needs the raw bytes of the field values from the message, so it's not compatible with sf or key, which need the parsed data structures of the field values after combination. not compatible, not compatible ๐Ÿ˜ญ

Additional parameters can be defined in the "HTTP Signature Component Parameters" registry established in Section 6.5. ooh, expandable โœจ

2.1.1. Strict Serialization of HTTP Structured Fields ๐Ÿงš

If the value of an HTTP field is known by the application to be a Structured Field type (as defined in [STRUCTURED-FIELDS] or its extensions or updates) and the expected type of the Structured Field is known, the signer MAY include the sf parameter in the component identifier. If this parameter is included with a component identifier, the HTTP field value MUST be serialized using the formal serialization rules specified in Section 4 of [STRUCTURED-FIELDS] (or the applicable formal serialization section of its extensions or updates) for the type of the HTTP field. Note that this process will replace any optional internal whitespace with a single space character, among other potential transformations of the value ๐Ÿ’–

If multiple field values occur within a message, these values MUST be combined into a single List or Dictionary structure before serialization.

If the application doesn't know the type of the field, or doesn't know how to serialize that type, using this flag produces an error. So the signer can only reliably sign fields with this flag when the verifier's system knows the type too. ๐Ÿซ 

For example, this Dictionary field is a valid serialization:

Example-Dict:  a=1,    b=2;x=1;y=2,   c=(a   b   c)

If it's included in the signature base without parameters, its value would be:

"example-dict": a=1,    b=2;x=1;y=2,   c=(a   b   c)

But if the sf parameter is added, the value gets reserialized like this:

"example-dict";sf: a=1, b=2;x=1;y=2, c=(a b c)

The resulting string is used as the component value; see Section 2.1.

2.1.2. Dictionary Structured Field Members ๐Ÿ“–

If the application knows a given field is a Dictionary Structured Field, you can identify one individual member in that Dictionary's value by using the parameter key and the Dictionary member key as a String value ๐ŸŽ€.

If multiple field values occur within a message, these values MUST be combined into a single Dictionary structure before serialization.

An individual member value of a Dictionary Structured Field is canonicalized by applying the serialization algorithm from Section 4.1.2 of [STRUCTURED-FIELDS] to the member_value and its parameters, not including the Dictionary key itself. Specifically, the value is serialized as an Item or Inner List (the two possible values of a Dictionary member), with all parameters and possible subfields serialized using the strict serialization rules defined in Section 4 of [STRUCTURED-FIELDS] (or the applicable section of its extensions or updates).

Each parameterized key for a given field MUST NOT appear more than once in the signature base. Parameterized keys MAY appear in any order in the signature base, regardless of the order they occur in the source Dictionary.

If a Dictionary key is named as a covered component but doesn't occur in the Dictionary, this MUST cause an error in the signature base generation. no sneaky missing keys allowed ๐Ÿšซ

The following are non-normative examples of canonicalized values for Dictionary Structured Field members, using this example header field, whose value the application knows is a Dictionary:

Example-Dict:  a=1, b=2;x=1;y=2, c=(a   b    c), d

Here are the canonicalized values for different component identifiers of this field, shown in the signature base format from Section 2.5:

"example-dict";key="a": 1
"example-dict";key="d": ?1
"example-dict";key="b": 2;x=1;y=2
"example-dict";key="c": (a b c)

Note that the value for key="c" has been reserialized according to the strict member_value algorithm, and the value for key="d" has been serialized as a Boolean value. โœจ๐Ÿ’œ

2.1.3. Binary-Wrapped HTTP Fields โœจ

okay so if the app already knows a field's value is gonna cause chaos when you smush multiple values onto one line (the combining thing from Section 7.5.6), the signer SHOULD add the bs parameter to the component identifier ๐Ÿซถ that way the values get wrapped as binary structures before they get combined. no cap, this is how you dodge a collision ๐Ÿ’€

if bs is there, the component value MUST be calculated like this:

  1. Let the input be the ordered set of values for a field, in the order they appear in the message.
  2. Create an empty List for accumulating processed field values.
  3. For each field value in the set:
    • 3.1. Strip leading and trailing whitespace from the field value. Note that since HTTP field values are not allowed to contain leading and trailing whitespace, this would be a no-op in a compliant implementation.
    • 3.2. Remove any obsolete line folding within the line, and replace it with a single space (" "), as discussed in Section 5.2 of [HTTP/1.1]. Note that this behavior is specific to [HTTP/1.1] and does not apply to other versions of the HTTP specification.
    • 3.3. Encode the bytes of the resulting field value as a Byte Sequence. Note that most fields are restricted to ASCII characters, but other octets could be included in the value in some implementations.
    • 3.4. Add the Byte Sequence to the List accumulator.
  4. The intermediate result is a List of Byte Sequence values.
  5. Follow the strict serialization of a List as described in Section 4.1.1 of [STRUCTURED-FIELDS], and return this output.

here's an example where the field has internal commas, so the separate values can't be safely combined ๐Ÿ‘€

Example-Header: value, with, lots
Example-Header: of, commas

now the same field can be sent with a semantically different single value ๐Ÿฆ‹

Example-Header: value, with, lots, of, commas

the app treats these two as different, but if you put them in the signature base without parameters, the component value ends up exactly the same in both cases ๐Ÿ˜ญ

"example-header": value, with, lots, of, commas

so with the bs parameter added, the two separate instances get encoded and serialized like this ๐Ÿงš

"example-header";bs: :dmFsdWUsIHdpdGgsIGxvdHM=:, :b2YsIGNvbW1hcw==:

and for the single-instance field above, the encoding with the bs parameter is:

"example-header";bs: :dmFsdWUsIHdpdGgsIGxvdHMsIG9mLCBjb21tYXM=:

this component value is distinct from the multiple-instance one, which prevents a collision that could potentially be exploited. it's giving security, periodt ๐Ÿ”’โœจ

2.1.4. Trailer Fields ๐ŸŽ€

if the signer wants to include a trailer field in the signature, the signer MUST include the tr Boolean parameter to indicate that the value MUST be taken from the trailer fields and not from the header fields. trailers are a whole different vibe ๐ŸŒ™

for example, here's a message:

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
Trailer: Expires

4
HTTP
7
Message
a
Signatures
0
Expires: Wed, 9 Nov 2022 07:28:00 GMT

the signer decides to add both the Trailer header field and the Expires trailer field to the signature base, along with the status code derived component ๐Ÿ’…

"@status": 200
"trailer": Expires
"expires";tr: Wed, 9 Nov 2022 07:28:00 GMT

if a field is available as both a header and a trailer in a message, both values MAY be signed, but the values MUST be signed separately. the values of header fields and trailer fields with the same name MUST NOT be combined for purposes of the signature. (no mixing them up, bestie ๐Ÿ™…โ€โ™€๏ธ)

trailer fields can get merged into the header fields or dropped entirely by intermediaries, as per Section 6.5.1 of [HTTP]. so it is NOT RECOMMENDED to include trailers in the signature unless the signer knows that the verifier will have access to the values of the trailers as sent. ๐Ÿ˜ญ๐Ÿซ 

2.2. Derived Components ๐Ÿฆ‹

in addition to HTTP fields, there are a bunch of components that can be derived from the control data, signature context, or other stuff about the HTTP message being signed. these derived components can go in the signature base if you define a component name, possible parameters, message targets, and the derivation method for its component value. it's giving customization ๐Ÿ’–

derived component names MUST start with the @ character. this is how they stand apart from HTTP field names, which can't contain the @ character as per Section 5.1 of [HTTP]. processors of HTTP message signatures MUST treat derived component names separately from field names, as discussed in Section 7.5.1. (keep them separate, iykyk ๐Ÿงš)

this specification defines the following derived components:

  • @method: The method used for a request (Section 2.2.1).
  • @target-uri: The full target URI for a request (Section 2.2.2).
  • @authority: The authority of the target URI for a request (Section 2.2.3).
  • @scheme: The scheme of the target URI for a request (Section 2.2.4).
  • @request-target: The request target (Section 2.2.5).
  • @path: The absolute path portion of the target URI for a request (Section 2.2.6).
  • @query: The query portion of the target URI for a request (Section 2.2.7).
  • @query-param: A parsed and encoded query parameter of the target URI for a request (Section 2.2.8).
  • @status: The status code for a response (Section 2.2.9).

more derived component names are defined in the "HTTP Signature Derived Component Names" registry (Section 6.4) ๐Ÿ“š๐ŸŒธ

derived component values come from the context of the target message for the signature. that context includes info about the message itself, like its control data, plus any extra state and context the signer or verifier is holding. especially when you're signing a response, the signer can include derived components from the originating request by using the req parameter (Section 2.4). the request came first, so it's the OG ๐Ÿ‘‘

the target message types work like this ๐Ÿ“

  • request: Values derived from, and results applied to, an HTTP request message as described in Section 3.4 of [HTTP]. If the target message of the signature is a response, derived components that target request messages can be included by using the req parameter as defined in Section 2.4.
  • response: Values derived from, and results applied to, an HTTP response message as described in Section 3.4 of [HTTP].
  • request, response: Values derived from, and results applied to, either a request message or a response message.

a derived component definition MUST define all target message types to which it can be applied. no vague vibes allowed ๐Ÿ™…โ€โ™€๏ธ

derived component values MUST be limited to printable characters and spaces and MUST NOT contain any newline characters. they also MUST NOT start or end with whitespace characters. clean and crisp, obsessed ๐Ÿงผโœจ

2.2.1. Method ๐ŸŒˆ

the @method derived component refers to the HTTP method of a request message. the component value is canonicalized by taking the value of the method as a string. note that the method name is case sensitive as per [HTTP], Section 9.1. while conventionally standardized method names are uppercase [ASCII], no transformation to the input method value's case is performed. so casing matters, bestie ๐Ÿ™ˆ

for example, this request message:

POST /path?param=value HTTP/1.1
Host: www.example.com

would result in the following @method component value:

POST

and the following signature base line:

"@method": POST

2.2.2. Target URI ๐Ÿฆ‹

the @target-uri derived component refers to the target URI of a request message. the component value is the target URI of the request ([HTTP], Section 7.1), assembled from all the available URI components, including the authority. main character URL energy ๐Ÿ’…

for example, this message sent over HTTPS:

POST /path?param=value HTTP/1.1
Host: www.example.com

would result in the following @target-uri component value:

https://www.example.com/path?param=value

and the following signature base line:

"@target-uri": https://www.example.com/path?param=value

2.2.3. Authority ๐ŸŽ€

the @authority derived component refers to the authority component of the target URI of the HTTP request message, as defined in [HTTP], Section 7.2. in HTTP/1.1, this is usually conveyed using the Host header field, while in HTTP/2 and HTTP/3 it's conveyed using the :authority pseudo-header. the value is the fully qualified authority component of the request, made up of the host and, optionally, the port of the request target, as a string. the component value MUST be normalized according to the rules in [HTTP], Section 4.2.3. namely, the hostname gets normalized to lowercase, and the default port is omitted. (lowkey the cleanest component ever ๐Ÿงš)

for example, this request message:

POST /path?param=value HTTP/1.1
Host: www.example.com

would result in the following @authority component value:

www.example.com

and the following signature base line:

"@authority": www.example.com

the @authority derived component SHOULD be used instead of signing the Host header field directly. see Section 7.2.4 ๐Ÿซถ

2.2.4. Scheme ๐ŸŒธ

the @scheme derived component refers to the scheme of the target URL of the HTTP request message. the component value is the scheme as a lowercase string, as defined in [HTTP], Section 4.2. the scheme itself is case insensitive, but it MUST be normalized to lowercase for inclusion in the signature base. (it's always lowercase in the base, no debate ๐Ÿ’–)

for example, this request message sent over plain HTTP:

POST /path?param=value HTTP/1.1
Host: www.example.com

would result in the following @scheme component value:

http

and the following signature base line:

"@scheme": http

2.2.5. Request Target โœจ

okay so the @request-target derived component refers to the full request target of the HTTP request message, as defined in [HTTP], Section 7.1. ๐Ÿ’– its component value can look different depending on the type of request, so let's go through each one bestie ๐ŸŽ€

For HTTP/1.1, the component value is equivalent to the request target portion of the request line. ngl this value is kinda hard to build reliably in other versions of HTTP ๐Ÿ˜ญ so it is NOT RECOMMENDED that this component be used when versions of HTTP other than 1.1 might be in use. ๐Ÿ™ˆ

The origin form value is a combination of the absolute path and query components of the request URL. ๐Ÿฆ‹

For example, the following request message:

POST /path?param=value HTTP/1.1
Host: www.example.com

would result in the following @request-target component value:

/path?param=value

and the following signature base line:

"@request-target": /path?param=value

The following request to an HTTP proxy with the absolute-form value, containing the fully qualified target URI:

GET https://www.example.com/path?param=value HTTP/1.1

would result in the following @request-target component value:

https://www.example.com/path?param=value

and the following signature base line:

"@request-target": https://www.example.com/path?param=value

The following CONNECT request with an authority-form value, containing the host and port of the target:

CONNECT www.example.com:80 HTTP/1.1
Host: www.example.com

would result in the following @request-target component value:

www.example.com:80

and the following signature base line:

"@request-target": www.example.com:80

The following OPTIONS request message with the asterisk-form value, containing a single asterisk (*) character:

OPTIONS * HTTP/1.1
Host: www.example.com

would result in the following @request-target component value:

*

and the following signature base line:

"@request-target": *

2.2.6. Path ๐ŸŒธ

the @path derived component refers to the target path of the HTTP request message. ๐Ÿ›ค๏ธ its component value is the absolute path of the request target defined by [URI], with no query component and no trailing question mark (?) character. the value is normalized according to the rules in [HTTP], Section 4.2.3. basically an empty path string gets normalized into a single slash (/) character, so it's always neat ๐Ÿ’… path components are represented by their values before any percent-encoded octets get decoded, as described in the simple string comparison rules in Section 6.2.1 of [URI]. no decoding, no drama โœจ

For example, the following request message:

GET /path?param=value HTTP/1.1
Host: www.example.com

would result in the following @path component value:

/path

and the following signature base line:

"@path": /path

2.2.7. Query ๐Ÿงš

the @query derived component refers to the query component of the HTTP request message. โ“ its component value is the entire normalized query string defined by [URI], including the leading ? character. it gets read using the simple string comparison rules in Section 6.2.1 of [URI], which basically means percent-encoded octets are NOT decoded. so what you see is what you get ๐Ÿ‘€

For example, the following request message:

GET /path?param=value&foo=bar&baz=bat%2Dman HTTP/1.1
Host: www.example.com

would result in the following @query component value:

?param=value&foo=bar&baz=bat%2Dman

and the following signature base line:

"@query": ?param=value&foo=bar&baz=bat%2Dman

The following request message:

POST /path?queryString HTTP/1.1
Host: www.example.com

would result in the following @query component value:

?queryString

and the following signature base line:

"@query": ?queryString

just like with an empty path component, the signer can include an empty query component to say "this bit isn't used in the message" ๐Ÿซถ if the query string is absent from the request message, the component value is just the leading ? character all by itself:

?

resulting in the following signature base line:

"@query": ?

2.2.8. Query Parameters ๐Ÿ’œ

okay this one is a whole vibe ๐Ÿ’… if the query portion of a request target URI uses HTML form parameters in the format defined in Section 5 ("application/x-www-form-urlencoded") of [HTMLURL], the @query-param derived component lets you address those individual query parameters ๐ŸŽ€ the query parameters MUST be parsed according to Section 5.1 ("application/x-www-form-urlencoded parsing") of [HTMLURL], which gives you a list of (nameString, valueString) tuples. the REQUIRED name parameter of each component identifier holds the encoded nameString of a single query parameter as a String value. the component value of a single named parameter is the encoded valueString of that same query parameter. several different named query parameters MAY be included in the covered components, and single named parameters MAY show up in any order in the covered components, no matter what order they're in the query string ๐ŸŒˆ

the value of the name parameter and the component value of a single named parameter get calculated with this process:

  1. Parse the nameString or valueString of the named query parameter defined by Section 5.1 ("application/x-www-form-urlencoded parsing") of [HTMLURL]; this is the value after percent-encoded octets are decoded.
  2. Encode the nameString or valueString using the "percent-encode after encoding" process defined by Section 5.2 ("application/x-www-form-urlencoded serializing") of [HTMLURL]; this results in an ASCII string [ASCII].
  3. Output the ASCII string.

note that the component value does NOT include any leading question mark (?) characters, equals sign (=) characters, or separating ampersand (&) characters. named query parameters with an empty valueString have an empty string as their component value. heads up though: due to inconsistencies in implementations, some query parameter parsing libraries drop those empty values ๐Ÿ‘€

If a query parameter is named as a covered component but it does not occur in the query parameters, this MUST cause an error in the signature base generation. ๐Ÿšจ no skipping it, bestie!

For example, for the following request:

GET /path?param=value&foo=bar&baz=batman&qux= HTTP/1.1
Host: www.example.com

indicating the baz, qux, and param named query parameters would result in the following @query-param component values:

_baz_: batman

_qux_: an empty string

_param_: value

and the following signature base lines, with (SP) indicating a single trailing space character before the empty component value:

"@query-param";name="baz": batman
"@query-param";name="qux":(SP)
"@query-param";name="param": value

this derived component has some limitations ngl ๐Ÿ˜ญ specifically, the algorithms in Section 5 ("application/x-www-form-urlencoded") of [HTMLURL] only support query parameters using percent-escaped UTF-8 encoding. other encodings are NOT supported. also, multiple instances of a named parameter are not reliably supported in the wild. so if a parameter name occurs multiple times in a request, the named query parameter MUST NOT be included. if multiple parameters are common within your app, it is RECOMMENDED to sign the entire query string using the @query component identifier defined in Section 2.2.7 ๐Ÿซถ

the encoding process lets query parameters include newlines or other problematic characters in their values, or use alternative encodings like the plus (+) character to represent spaces. for the query parameters in this message:

NOTE: '\' line wrapping per RFC 8792

GET /parameters?var=this%20is%20a%20big%0Amultiline%20value&\
  bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something HTTP/1.1
Host: www.example.com
Date: Tue, 20 Apr 2021 02:07:56 GMT

the resulting values are encoded like this:

"@query-param";name="var": this%20is%20a%20big%0Amultiline%20value
"@query-param";name="bar": with%20plus%20whitespace
"@query-param";name="fa%C3%A7ade%22%3A%20": something

if the encoding were NOT applied, the resultant values would be:

"@query-param";name="var": this is a big
multiline value
"@query-param";name="bar": with plus whitespace
"@query-param";name="faรงade\": ": something

and that base string has characters that break the constraints on component names and values, so it's therefore invalid ๐Ÿ’€


2.2.9. Status Code ๐Ÿ‘‘

the @status derived component refers to the three-digit numeric HTTP status code of a response message, as defined in [HTTP], Section 15. โœจ its component value is the serialized three-digit integer of the HTTP status code, with no descriptive text (so no "OK" sass here ๐Ÿ’…)

For example, the following response message:

HTTP/1.1 200 OK
Date: Fri, 26 Mar 2010 00:05:00 GMT

would result in the following @status component value:

200

and the following signature base line:

"@status": 200
The @status component identifier MUST NOT be used in a request message. ๐Ÿ™…โ€โ™€๏ธ it's a response-only moment, periodt.

2.3. Signature Parameters โœจ

okay bestie so every signature comes with its own little bag of metadata ๐ŸŽ€ like when it was made, which key to check it with, and other vibes ๐Ÿ’… This metadata gets a special message component in the signature base, and ngl it's treated a bit differently from the others ๐Ÿฆ‹ The signature parameters line is REQUIRED as the very last line of the signature base (see Section 2.5) ๐Ÿซถ and its component identifier MUST NOT be listed in the covered components for any signature, not even itself ๐Ÿ™ˆ no self-referencing, periodt ๐Ÿ’€

The component name is @signature-params ๐ŸŒธ

The component value is the serialization of the signature parameters for this signature. That covers the covered components (with all their parameters) plus any of these parameters ๐Ÿ‘‘

  • created: Creation time as a UNIX timestamp value of type Integer. Sub-second precision is not supported. The inclusion of this parameter is RECOMMENDED. โฐ
  • expires: Expiration time as a UNIX timestamp value of type Integer. Sub-second precision is not supported. ๐Ÿงš
  • nonce: A random unique value generated for this signature, as a String value. ๐ŸŽฒ
  • alg: The HTTP message signature algorithm from the "HTTP Signature Algorithms" registry, as a String value. ๐Ÿ”
  • keyid: The identifier for the key material, as a String value. ๐Ÿ—๏ธ
  • tag: An application-specific tag for the signature, as a String value. Applications use it to help identify signatures relevant to specific applications or protocols. ๐Ÿท๏ธ

Additional parameters can be defined in the "HTTP Signature Metadata Parameters" registry (Section 6.3) ๐Ÿ“’ The parameters aren't in any general order, but once an ordering is chosen for a given set of parameters, it can't be changed without altering the signature parameters value. Lowkey a big deal ๐Ÿ˜ญ

The signature parameters component value is serialized as a parameterized Inner List, using the rules in Section 4 of [STRUCTURED-FIELDS], like this:

  1. Let the output be an empty string. ๐Ÿซง
  2. Determine an order for the component identifiers of the covered components, not including the @signature-params component identifier itself. Once this order is chosen, it cannot be changed. This order MUST be the same order as the one used when creating the signature base (Section 2.5). ๐Ÿ”’
  3. Serialize the component identifiers of the covered components, including all parameters, as an ordered Inner List of String values according to Section 4.1.1.1 of [STRUCTURED-FIELDS], then append this to the output. Component identifiers can have their own parameters, and those parameters are ordered sets. Once an order is chosen for a component's parameters, that order can't be changed. ๐Ÿงฉ
  4. Determine an order for any signature parameters. Once this order is chosen, it cannot be changed. ๐Ÿ“‹
  5. Append the parameters to the Inner List in order according to Section 4.1.1.2 of [STRUCTURED-FIELDS], skipping parameters that are not available or not used for this message signature. โœ‚๏ธ
  6. The output contains the signature parameters component value. ๐ŸŽ€

Note that the Inner List serialization from Section 4.1.1.1 of [STRUCTURED-FIELDS] is used for the covered component value instead of the List serialization from Section 4.1.1 of [STRUCTURED-FIELDS]. This is so it lines up with how this value gets included in the Signature-Input field, as discussed in Section 4.1 ๐ŸŒˆ

Here's an example of the serialized component value for the parameters of a message signature. Heads up, the \ is line wrapping per RFC 8792 ๐Ÿ™ˆ

NOTE: '\' line wrapping per RFC 8792
("@target-uri" "@authority" "date" "cache-control")\
  ;keyid="test-key-rsa-pss";alg="rsa-pss-sha512";\
  created=1618884475;expires=1618884775
An HTTP message could contain multiple signatures (Section 4.3), but only the signature parameters used for a single signature are included in a given signature parameters entry. So each signature gets its own entry, no mixing ๐Ÿซถ

2.4. Signing Request Components in a Response Message ๐Ÿ’–

Okay bestie, so when a request message gets you a signed response message ๐ŸŽ€, the signer can pull parts of that request into the signature base. The trick is adding the req parameter to the component identifier ๐Ÿฆ‹

  • req: A Boolean flag indicating that the component value is derived from the request that triggered this response message and not from the response message directly. ๐ŸŒธ

This parameter works on both HTTP fields and derived components that target the request, and it means the same thing for both ๐Ÿ’… The component value gets calculated the usual way, but the data comes from the request message instead of the response message being signed โœจ

Note that the same component name MAY be included with and without the req parameter in a single signature base, which means you can cover the same named component from both the request and the response ๐Ÿ‘‘

The req parameter MAY be combined with other parameters as appropriate for the component identifier, like the key parameter for a Dictionary field ๐Ÿงš

For example, say the server is responding to this request:

NOTE: '' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Content-Type: application/json
Content-Length: 18

{"hello": "world"}

This would result in the following unsigned response message ๐Ÿ“

NOTE: '' line wrapping per RFC 8792

HTTP/1.1 503 Service Unavailable
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Length: 62
Content-Digest: sha-512=:0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObfwn\
  HJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg==:

{"busy": true, "message": "Your call is very important to us"}

The server signs the response with its own key ๐Ÿ”‘, covering the @status code and several header fields. That's a solid chunk of the response for this app, but the server also grabs several components derived from the original request that set this whole thing off ๐Ÿซถ In this example it includes the method, authority, path, and content digest from the request in the covered components of the response. The Content-Digest from both the request and the response is covered under the response signature ๐Ÿ’œ The query is deemed not to be relevant to the response for this app, so it's not covered. Other apps would make different calls based on what they need, as discussed in Section 1.4 ๐ŸŒˆ

The signature base for this example is:

NOTE: '' line wrapping per RFC 8792

"@status": 503
"content-digest": sha-512=:0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObf\
  wnHJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg==:
"content-type": application/json
"@authority";req: example.com
"@method";req: POST
"@path";req: /foo
"content-digest";req: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A\
  2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
"@signature-params": ("@status" "content-digest" "content-type" \
  "@authority";req "@method";req "@path";req "content-digest";req)\
  ;created=1618884479;keyid="test-key-ecc-p256"

The signed response message is:

NOTE: '' line wrapping per RFC 8792

HTTP/1.1 503 Service Unavailable
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Length: 62
Content-Digest: sha-512=:0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObfwn\
  HJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg==:
Signature-Input: reqres=("@status" "content-digest" "content-type" \
  "@authority";req "@method";req "@path";req "content-digest";req)\
  ;created=1618884479;keyid="test-key-ecc-p256"
Signature: reqres=:dMT/A/76ehrdBTD/2Xx8QuKV6FoyzEP/I9hdzKN8LQJLNgzU\
  4W767HK05rx1i8meNQQgQPgQp8wq2ive3tV5Ag==:

{"busy": true, "message": "Your call is very important to us"}

Heads up ๐Ÿ™ˆ The ECDSA signature algorithm here is non-deterministic, which means a different signature value gets created every single time the algorithm runs. The signature value shown can be validated against the given keys, but newly generated signature values are not expected to match the example. See Section 7.3.5.

Since the component values from the request are not repeated in the response message, the requester MUST keep the original message component values around long enough to validate the signature of the response that uses this component identifier parameter ๐Ÿ˜ญ In most cases, that means the requester needs to keep the original request message around, since the signer could choose to include any portions of the request in its response, depending on what the application needs. It's also possible for an intermediary to alter a request message before the server processes it, so applications need to take care not to sign such altered values, because the client would not be able to validate the resulting signature ๐Ÿ’€

It's also possible for a server to create a signed response to a signed request ๐Ÿฆ‹ For this example of a signed request:

NOTE: '' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Content-Type: application/json
Content-Length: 18
Signature-Input: sig1=("@method" "@authority" "@path" "@query" \
  "content-digest" "content-type" "content-length")\
  ;created=1618884475;keyid="test-key-rsa-pss"
Signature: sig1=:e8UJ5wMiRaonlth5ERtE8GIiEH7Akcr493nQ07VPNo6y3qvjdK\
  t0fo8VHO8xXDjmtYoatGYBGJVlMfIp06eVMEyNW2I4vN7XDAz7m5v1108vGzaDljr\
  d0H8+SJ28g7bzn6h2xeL/8q+qUwahWA/JmC8aOC9iVnwbOKCc0WSrLgWQwTY6VLp4\
  2Qt7jjhYT5W7/wCvfK9A1VmHH1lJXsV873Z6hpxesd50PSmO+xaNeYvDLvVdZlhtw\
  5PCtUYzKjHqwmaQ6DEuM8udRjYsoNqp2xZKcuCO1nKc0V3RjpqMZLuuyVbHDAbCzr\
  0pg2d2VM/OC33JAU7meEjjaNz+d7LWPg==:

{"hello": "world"}

The server could choose to sign portions of this response, including several parts of the request, which gives this signature base:

NOTE: '' line wrapping per RFC 8792

"@status": 503
"content-digest": sha-512=:0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObf\
  wnHJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg==:
"content-type": application/json
"@authority";req: example.com
"@method";req: POST
"@path";req: /foo
"@query";req: ?param=Value&Pet=dog
"content-digest";req: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A\
  2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
"content-type";req: application/json
"content-length";req: 18
"@signature-params": ("@status" "content-digest" "content-type" \
  "@authority";req "@method";req "@path";req "@query";req \
  "content-digest";req "content-type";req "content-length";req)\
  ;created=1618884479;keyid="test-key-ecc-p256"

and the following signed response:

NOTE: '' line wrapping per RFC 8792

HTTP/1.1 503 Service Unavailable
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Length: 62
Content-Digest: sha-512=:0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObfwn\
  HJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg==:
Signature-Input: reqres=("@status" "content-digest" "content-type" \
  "@authority";req "@method";req "@path";req "@query";req \
  "content-digest";req "content-type";req "content-length";req)\
  ;created=1618884479;keyid="test-key-ecc-p256"
Signature: reqres=:C73J41GVKc+TYXbSobvZf0CmNcptRiWN+NY1Or0A36ISg6ym\
  dRN6ZgR2QfrtopFNzqAyv+CeWrMsNbcV2Ojsgg==:

{"busy": true, "message": "Your call is very important to us"}

Same energy as before ๐ŸŒธ The ECDSA signature algorithm here is non-deterministic too, so a different signature value gets created every time it runs. The signature value shown can be validated against the given keys, but newly generated signature values are not expected to match the example. See Section 7.3.5.

Applications signing a response to a signed request SHOULD sign all of the components of the request signature value to provide sufficient coverage and protection against a class of collision attacks, as discussed in Section 7.3.7 ๐Ÿ”ฅ The server in this example has included all components listed in the Signature-Input field of the client's signature on the request in the response signature, plus components of the response. Iconic โœจ

While it is syntactically possible to include the Signature and Signature-Input fields of the request message in the signature components of a response to a message using this mechanism, this practice is NOT RECOMMENDED ๐Ÿšซ This is because signatures of signatures do not provide transitive coverage of covered components the way you'd expect, and the practice is susceptible to several attacks as discussed in Section 7.3.7. An application that needs to signal successful processing or receipt of a signature would need to carefully specify alternative mechanisms for sending such a signal securely ๐Ÿซถ

The response signature can only ever cover what is included in the request message when using this flag. So if an application needs to include the message content of the request under the signature of its response, the client needs to include a means for covering that content, such as a Content-Digest field. See the discussion in Section 7.2.8 for more information. ๐Ÿฆ‹

The req parameter MUST NOT be used for any component in a signature that targets a request message. Non-negotiable, periodt ๐Ÿ’…

2.5. Creating the Signature Base

okay so the signature base is an ASCII string [ASCII] ๐Ÿ’– basically a neat little text blob that holds the canonicalized HTTP message components the signature covers โœจ. The input is the ordered set of covered component identifiers and their values, plus any extra signature parameters from Section 2.3 ๐ŸŽ€

Component identifiers get serialized using the strict serialization rules in [STRUCTURED-FIELDS], Section 4 ๐Ÿฆ‹. Each identifier has a component name, which is a String Item value serialized with the sf-string ABNF rule. The component identifier MAY also include defined parameters, serialized with the parameters ABNF rule ๐ŸŒธ The signature parameters line from Section 2.3 follows this same pattern, but its identifier is a String Item with a fixed value and no parameters, and its component value is always an Inner List with optional parameters ๐Ÿ’…

Note that the component name itself is wrapped in double quotes, and the parameters come after it as a semicolon-separated list. Examples are "cache-control", "@authority", "@signature-params", or "example-dictionary";key="foo" ๐Ÿซถ

The output is the ordered set of bytes that form the signature base, and it follows this ABNF ๐Ÿ‘‘

signature-base = *( signature-base-line LF ) signature-params-line
signature-base-line = component-identifier ":" SP
    ( derived-component-value / *field-content )
    ; no obs-fold nor obs-text
component-identifier = component-name parameters
component-name = sf-string
derived-component-value = *( VCHAR / SP )
signature-params-line = DQUOTE "@signature-params" DQUOTE
     ":" SP inner-list

To build the signature base, the signer or verifier joins up an entry for each component identifier in the signature's covered components (parameters included) using this algorithm. All errors produced as described MUST fail the algorithm immediately, without outputting a signature base ๐Ÿ™ˆ

  1. Let the output be an empty string.
  2. For each message component item in the covered components set (in order):
    • 2.1. If the component identifier (including its parameters) has already been added to the signature base, produce an error.
    • 2.2. Append the component identifier for the covered component, serialized according to the component-identifier ABNF rule. This serialization puts the component name in double quotes and appends any parameters outside of the quotes โœจ
    • 2.3. Append a single colon (:).
    • 2.4. Append a single space (" ").
    • 2.5. Determine the component value for the component identifier.
      • If the component identifier has a parameter that is not understood, produce an error.
      • If the component identifier has parameters that are mutually incompatible with one another, such as bs and sf, produce an error.
      • If the component identifier contains the req parameter and the target message is a request, produce an error.
      • If the component identifier contains the req parameter and the target message is a response, the context for the component value is the related request message of the target response message. Otherwise, the context for the component value is the target message.
      • If the component name starts with an "at" (@) character, derive the component's value from the message according to the specific rules defined for the derived component, as provided in Section 2.2, including processing of any known valid parameters. If the derived component name is unknown or the value cannot be derived, produce an error.
      • If the component name does not start with an "at" (@) character, canonicalize the HTTP field value as described in Section 2.1, including processing of any known valid parameters. If the field cannot be found in the message or the value cannot be obtained in the context, produce an error.
    • 2.6. Append the covered component's canonicalized component value.
    • 2.7. Append a single newline (\n).
  3. Append the signature parameters component (Section 2.3) according to the signature-params-line rule, as follows:
    • 3.1. Append the component identifier for the signature parameters, serialized according to the component-identifier rule. That's the exact value "@signature-params" (double quotes included) ๐ŸŽ€
    • 3.2. Append a single colon (:).
    • 3.3. Append a single space (" ").
    • 3.4. Append the signature parameters' canonicalized component values as defined in Section 2.3, which means Inner List Structured Field values with parameters.
  4. Produce an error if the output string contains any non-ASCII characters [ASCII].
  5. Return the output string.

If covered components reference a component identifier that can't be resolved to a component value in the message, the implementation MUST produce an error and not create a signature base. That includes, but isn't limited to, these situations ๐Ÿ˜ญ

  • The signer or verifier does not understand the derived component name.
  • The component name identifies a field that is not present in the message or whose value is malformed.
  • The component identifier includes a parameter that is unknown or does not apply to the component identifier it's attached to.
  • The component identifier says a Structured Field serialization is used (via the sf parameter), but the field is known to not be a Structured Field, or the type of Structured Field is not known to the implementation.
  • The component identifier is a Dictionary member identifier that references a field that is not present in the message, that is not a Dictionary Structured Field, or whose value is malformed.
  • The component identifier is a Dictionary member identifier or a named query parameter identifier that references a member that is not present in the component value, or whose value is malformed. For example, the identifier is "example-dict";key="c", and the value of the Example-Dict header field is a=1, b=2, which doesn't have the c value ๐Ÿ’€

Here's a non-normative example where the HTTP message being signed is this request ๐Ÿงš

NOTE: '\' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Content-Length: 18

{"hello": "world"}

The covered components are the @method, @authority, and @path derived components, followed by the Content-Digest, Content-Length, and Content-Type HTTP header fields, in that order ๐ŸŒˆ The signature parameters are a creation timestamp of 1618884473 and a key identifier of test-key-rsa-pss. There's no explicit alg parameter here, because the verifier is known by the application to use RSA-PSS based on the identified key ๐Ÿ”ฅ The signature base for this message with these parameters is:

NOTE: '\' line wrapping per RFC 8792

"@method": POST
"@authority": example.com
"@path": /foo
"content-digest": sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX\
  +TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
"content-length": 18
"content-type": application/json
"@signature-params": ("@method" "@authority" "@path" \
  "content-digest" "content-length" "content-type")\
  ;created=1618884473;keyid="test-key-rsa-pss"

Figure 1: Non-normative Example Signature Base

Note that the example signature base above doesn't include the final newline that ends the displayed example, and neither do the other example signature bases shown elsewhere in this specification ๐Ÿฆ‹


3. HTTP Message Signatures โœจ

An HTTP message signature is a signature over a string built from a subset of the components of an HTTP message, plus metadata about the signature itself ๐Ÿ’œ When it's successfully verified against an HTTP message, an HTTP message signature gives you cryptographic proof that the message is semantically equivalent to the message the signature was generated for, at least with respect to the subset of message components that was signed ๐Ÿ‘‘

3.1. Creating a Signature ๐ŸŽ€

Okay so making an HTTP message signature ๐Ÿ’… takes the signature context (aka the target message) and whatever your app needs โœจ. It spits out a signature value and a set of signature parameters, and then you add those to the message so the verifier can see them ๐Ÿซถ

In order to create a signature, a signer MUST apply the following algorithm: ๐Ÿงš

  1. The signer picks an HTTP signature algorithm and key material from the pile of potential signing algorithms ๐Ÿ”‘. The app decides what's in that pile, and it's out of scope for this doc, so don't come for me ๐Ÿ™ˆ The signer MUST choose key material that fits the algorithm and follows any rules the algorithm has, like key size or format. How the signer actually chooses the algorithm and key material is also out of scope.
  2. The signer sets the signature's creation time to the current time โฐ.
  3. If it applies, the signer sets the signature's expiration time property to when the signature is supposed to expire โŒ›. This is basically a hint for the verifier, saying when the signer stops vouching for the signature ๐Ÿ’€. How long that should be, and how it gets processed, is up to the app.
  4. The signer creates an ordered set of component identifiers for the message parts to be covered by the signature, and attaches signature metadata parameters to this set ๐Ÿ“. The serialized value of this set later becomes the Signature-Input field (see Section 4.1).
    • Once an order of covered components is chosen, the order MUST NOT change for the life of the signature ๐Ÿ”’.
    • Each covered component identifier MUST be either (1) an HTTP field (Section 2.1) in the signature context or (2) a derived component listed in Section 2.2 or in the "HTTP Signature Derived Component Names" registry.
    • Signers of a request SHOULD include some or all of the message control data in the covered components, like @method, @authority, @target-uri, or some combo of those ๐Ÿง.
    • Signers SHOULD include the created signature metadata parameter to indicate when the signature was created ๐Ÿ•ฐ๏ธ.
    • The @signature-params derived component identifier MUST NOT be present in the list of covered component identifiers ๐Ÿšซ. The derived component is required to always be the last line in the signature base ๐Ÿ‘‘, which means a signature always covers its own metadata and that metadata can't be swapped out ๐Ÿ˜ค.
    • Further guidance on what to include in this set and in what order is out of scope for this doc ๐Ÿคทโ€โ™€๏ธ.
  5. The signer creates the signature base using these parameters and the signature base creation algorithm (Section 2.5) ๐Ÿงฑ.
  6. The signer uses the HTTP_SIGN primitive function to sign the signature base with the chosen signing algorithm and the key material the signer picked ๐Ÿ–Š๏ธ. HTTP_SIGN and several concrete applications of signing algorithms are defined in Section 3.3.
  7. The byte array output of the signature function is the HTTP message signature output value that goes in the Signature field, as defined in Section 4.2 ๐ŸŽฏ.

For example, given the HTTP message and signature parameters in the example in Section 2.5, the example signature base gets signed with the test-key-rsa-pss key (see Appendix B.1.2) and the RSASSA-PSS algorithm from Section 3.3.1. That gives the following message signature output value, encoded in Base64 ๐Ÿ’–:

NOTE: '' line wrapping per RFC 8792

HIbjHC5rS0BYaa9v4QfD4193TORw7u9edguPh0AW3dMq9WImrlFrCGUDih47vAxi4L2\
YRZ3XMJc1uOKk/J0ZmZ+wcta4nKIgBkKq0rM9hs3CQyxXGxHLMCy8uqK488o+9jrptQ\
+xFPHK7a9sRL1IXNaagCNN3ZxJsYapFj+JXbmaI5rtAdSfSvzPuBCh+ARHBmWuNo1Uz\
VVdHXrl8ePL4cccqlazIJdC4QEjrF+Sn4IxBQzTZsL9y9TP5FsZYzHvDqbInkTNigBc\
E9cKOYNFCn4D/WM7F6TNuZO9EgtzepLWcjTymlHzK7aXq6Am6sfOrpIC49yXjj3ae6H\
RalVc/g==

Figure 2: Non-normative Example Signature Value

Note that the RSA-PSS algorithm in use here is non-deterministic ๐ŸŽฒ, meaning a different signature value gets created every time the algorithm runs. The signature value shown here can be validated against the given keys, but newly generated signature values aren't expected to match the example ๐Ÿ™ƒ. See Section 7.3.5.

3.2. Verifying a Signature ๐Ÿ•ต๏ธโ€โ™€๏ธ

Verifying an HTTP message signature takes the signature context (including the target message, particularly its Signature and Signature-Input fields) and the requirements for the application as its input ๐Ÿ“ฅ. The output is either a positive verification or an error ๐Ÿ’….

In order to verify a signature, a verifier MUST apply the following algorithm: ๐Ÿงš

  1. Parse the Signature and Signature-Input fields as described in Sections 4.1 and 4.2, and extract the signatures to be verified and their labels ๐Ÿ”.
    • 1.1. If there is more than one signature value present, determine which signature should be processed for this message based on the policy and configuration of the verifier. If an applicable signature is not found, produce an error.
    • 1.2. If the chosen Signature field value does not have a corresponding Signature-Input field value (i.e., one with the same label), produce an error.
  2. Parse the values of the chosen Signature-Input field as a parameterized Inner List to get the ordered list of covered components and the signature parameters for the signature to be verified ๐Ÿ“œ.
  3. Parse the value of the corresponding Signature field to get the byte array value of the signature to be verified ๐Ÿงฉ.
  4. Examine the signature parameters to confirm that the signature meets the requirements described in this document, as well as any additional requirements defined by the application, such as which message components are required to be covered by the signature (Section 3.2.1) โœ….
  5. Determine the verification key material for this signature ๐Ÿ—๏ธ. If the key material is known through external means, such as static configuration or external protocol negotiation, the verifier uses the applicable technique to get the key material from that external knowledge. If the key is identified in the signature parameters, the verifier dereferences the key identifier to the appropriate key material to use with the signature ๐Ÿ”“. The verifier has to determine the trustworthiness of the key material for the context in which the signature is presented ๐Ÿง. If a key is identified that the verifier does not know or trust for this request, or that does not match something preconfigured, the verification MUST fail ๐Ÿšจ.
  6. Determine the algorithm to apply for verification ๐Ÿงฎ:
    • 6.1. Start with the set of allowable algorithms known to the application. If any of the following steps select an algorithm that is not in this set, the signature validation fails.
    • 6.2. If the algorithm is known through external means, such as static configuration or external protocol negotiation, the verifier uses that algorithm.
    • 6.3. If the algorithm can be determined from the keying material, such as through an algorithm field on the key value itself, the verifier uses that algorithm.
    • 6.4. If the algorithm is explicitly stated in the signature parameters using a value from the "HTTP Signature Algorithms" registry, the verifier uses the referenced algorithm.
    • 6.5. If the algorithm is specified in more than one location (e.g., a combination of static configuration, the algorithm signature parameter, and the key material itself), the resolved algorithms MUST be the same. If the algorithms are not the same, the verifier MUST fail the verification ๐Ÿ™…โ€โ™€๏ธ.
  7. Use the received HTTP message and the parsed signature parameters to recreate the signature base, using the algorithm defined in Section 2.5 ๐Ÿ”. The value of the @signature-params input is the value of the Signature-Input field for this signature, serialized according to the rules described in Section 2.3. Note that this does not include the signature's label from the Signature-Input field ๐Ÿท๏ธ.
  8. If the key material is appropriate for the algorithm, apply the appropriate HTTP_VERIFY cryptographic verification algorithm to the signature, recalculated signature base, key material, and signature value ๐Ÿ”. The HTTP_VERIFY primitive and several concrete algorithms are defined in Section 3.3.
  9. The results of the verification algorithm function are the final results of the cryptographic verification function ๐Ÿ.

If any of the above steps fail or produce an error, the signature validation fails. Yikes ๐Ÿ˜ฌ

For example, verifying the signature with the label sig1 of the following message with the test-key-rsa-pss key (see Appendix B.1.2) and the RSASSA-PSS algorithm from Section 3.3.1:

NOTE: '' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Content-Length: 18
Signature-Input: sig1=("@method" "@authority" "@path" \
  "content-digest" "content-length" "content-type")\
  ;created=1618884473;keyid="test-key-rsa-pss"
Signature: sig1=:HIbjHC5rS0BYaa9v4QfD4193TORw7u9edguPh0AW3dMq9WImrl\
  FrCGUDih47vAxi4L2YRZ3XMJc1uOKk/J0ZmZ+wcta4nKIgBkKq0rM9hs3CQyxXGxH\
  LMCy8uqK488o+9jrptQ+xFPHK7a9sRL1IXNaagCNN3ZxJsYapFj+JXbmaI5rtAdSf\
  SvzPuBCh+ARHBmWuNo1UzVVdHXrl8ePL4cccqlazIJdC4QEjrF+Sn4IxBQzTZsL9y\
  9TP5FsZYzHvDqbInkTNigBcE9cKOYNFCn4D/WM7F6TNuZO9EgtzepLWcjTymlHzK7\
  aXq6Am6sfOrpIC49yXjj3ae6HRalVc/g==:

{"hello": "world"}

With the additional requirements that at least the method, authority, path, content-digest, content-length, and content-type entries be signed, and that the signature creation timestamp be recent enough at the time of verification, the verification passes ๐ŸŽ‰

3.2.1. Enforcing Application Requirements โœจ

okay so the verification rules in this doc are a baseline ๐Ÿงฑ that works for basically every use case. Applications using HTTP message signatures MAY impose requirements above and beyond those specified by this document, as appropriate for their use case. ๐Ÿ’…

Here are some non-normative examples of extra requirements an app might define. These are just vibes, not rules ๐Ÿซถ

  • Requiring a specific set of header fields to be signed (e.g., Authorization, Content-Digest). ๐ŸŽ€
  • Enforcing a maximum signature age from the time of the created timestamp. โฐ
  • Rejecting signatures past the expiration time in the expires timestamp. Note that the expiration time is a hint from the signer, and a verifier can always reject a signature ahead of its expiration time. ๐Ÿ™ˆ
  • Prohibiting certain signature metadata parameters, such as runtime algorithm signaling with the alg parameter when the algorithm is determined from the key information. ๐Ÿšซ
  • Ensuring successful dereferencing of the keyid parameter to valid and appropriate key material. ๐Ÿ”‘
  • Prohibiting the use of certain algorithms or mandating the use of a specific algorithm. ๐Ÿง
  • Requiring keys to be of a certain size (e.g., 2048 bits vs. 1024 bits). ๐Ÿ’ช
  • Enforcing uniqueness of the nonce parameter. ๐ŸŒธ
  • Requiring an application-specific value for the tag parameter. ๐Ÿท๏ธ

Application-specific requirements are expected and encouraged, so go off ๐Ÿ”ฅ When an application defines additional requirements, it MUST enforce them during the signature verification process, and signature verification MUST fail if the signature does not conform to the application's requirements. No exceptions, bestie ๐Ÿ˜ญ

Applications MUST enforce the requirements defined in this document. Regardless of use case, applications MUST NOT accept signatures that do not conform to these requirements. Periodt ๐Ÿ’…โœจ


3.3. Signature Algorithms

An HTTP message signature MUST use a cryptographic digital signature or MAC method that is appropriate for the key material, environment, and needs of the signer and verifier. This spec does not strictly limit the available signature algorithms, so any signature algorithm that meets these basic requirements MAY be used by an application of HTTP message signatures. It's giving flexibility ๐Ÿฆ‹

For each signing method, HTTP_SIGN takes as its input the signature base defined in Section 2.5 as a byte array (M) and the signing key material (Ks), and outputs the resultant signature as a byte array (S):

HTTP_SIGN (M, Ks)  ->  S

For each verification method, HTTP_VERIFY takes as its input the regenerated signature base defined in Section 2.5 as a byte array (M), the verification key material (Kv), and the presented signature to be verified as a byte array (S), and outputs the verification result (V) as a Boolean:

HTTP_VERIFY (M, Kv, S) -> V

The following sections cover several common signature algorithms and show how these cryptographic primitives map to the HTTP_SIGN and HTTP_VERIFY definitions above. You can tell which method to use in a few ways: through the explicit algorithm (alg) signature parameter (Section 2.3), by reference to the key material, or through mutual agreement between the signer and verifier. ๐Ÿ” Signature algorithms selected using the alg parameter MUST use values from the "HTTP Signature Algorithms" registry (Section 6.2). ๐Ÿ“‹


3.3.1. RSASSA-PSS Using SHA-512

To sign with this algorithm, the signer applies the RSASSA-PSS-SIGN (K, M) function defined in [RFC8017] with the signer's private signing key (K) and the signature base (M) (Section 2.5). The mask generation function is MGF1 as specified in [RFC8017] with a hash function of SHA-512 [RFC6234]. The salt length (sLen) is 64 bytes. The hash function (Hash) SHA-512 [RFC6234] is applied to the signature base to create the digest content that the digital signature is applied to. The resulting signed content byte array (S) is the HTTP message signature output used in Section 3.1. โœ๏ธ

To verify with this algorithm, the verifier applies the RSASSA-PSS-VERIFY ((n, e), M, S) function [RFC8017] using the public key portion of the verification key material (n, e) and the signature base (M) recreated as described in Section 3.2. The mask generation function is MGF1 as specified in [RFC8017] with a hash function of SHA-512 [RFC6234]. The salt length (sLen) is 64 bytes. The hash function (Hash) SHA-512 [RFC6234] is applied to the signature base to create the digest content that the verification function is applied to. The verifier extracts the HTTP message signature to be verified (S) as described in Section 3.2. The results of the verification function indicate whether the signature presented is valid. โœ…

Note that the output of the RSASSA-PSS algorithm is non-deterministic. That means it is not correct to recalculate a new signature on the signature base and compare the results to an existing signature. Instead, the verification algorithm defined here needs to be used. See Section 7.3.5. ๐Ÿง

The use of this algorithm can be indicated at runtime using the rsa-pss-sha512 value for the alg signature parameter.


3.3.2. RSASSA-PKCS1-v1_5 Using SHA-256

To sign with this algorithm, the signer applies the RSASSA-PKCS1-V1_5-SIGN (K, M) function defined in [RFC8017] with the signer's private signing key (K) and the signature base (M) (Section 2.5). The hash SHA-256 [RFC6234] is applied to the signature base to create the digest content that the digital signature is applied to. The resulting signed content byte array (S) is the HTTP message signature output used in Section 3.1. ๐Ÿ–Š๏ธ

To verify with this algorithm, the verifier applies the RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S) function [RFC8017] using the public key portion of the verification key material (n, e) and the signature base (M) recreated as described in Section 3.2. The hash function SHA-256 [RFC6234] is applied to the signature base to create the digest content that the verification function is applied to. The verifier extracts the HTTP message signature to be verified (S) as described in Section 3.2. The results of the verification function indicate whether the signature presented is valid. ๐Ÿ”

The use of this algorithm can be indicated at runtime using the rsa-v1_5-sha256 value for the alg signature parameter. ๐Ÿ’–


3.3.3. HMAC Using SHA-256

To sign and verify with this algorithm, the signer applies the HMAC function [RFC2104] with the shared signing key (K) and the signature base (text) (Section 2.5). The hash function SHA-256 [RFC6234] is applied to the signature base to create the digest content that the HMAC is applied to, giving the signature result. ๐Ÿค

For signing, the resulting value is the HTTP message signature output used in Section 3.1.

For verification, the verifier extracts the HTTP message signature to be verified (S) as described in Section 3.2. The output of the HMAC function is compared bytewise to the value of the HTTP message signature, and the results of the comparison determine the validity of the signature presented. ๐Ÿงพ

The use of this algorithm can be indicated at runtime using the hmac-sha256 value for the alg signature parameter. ๐ŸŒˆ


3.3.4. ECDSA Using Curve P-256 DSS and SHA-256

To sign with this algorithm, the signer applies the ECDSA signature algorithm defined in [FIPS186-5] using curve P-256 with the signer's private signing key and the signature base (Section 2.5). The hash SHA-256 [RFC6234] is applied to the signature base to create the digest content that the digital signature is applied to (M). The signature algorithm returns two integer values: r and s. Both are encoded as big-endian unsigned integers, zero-padded to 32 octets each. These encoded values are concatenated into a single 64-octet array, with the encoded value of r followed by the encoded value of s. The resulting concatenation of (r, s) is a byte array of the HTTP message signature output used in Section 3.1. ๐Ÿงฉ

To verify with this algorithm, the verifier applies the ECDSA signature algorithm defined in [FIPS186-5] using the public key portion of the verification key material and the signature base recreated as described in Section 3.2. The hash function SHA-256 [RFC6234] is applied to the signature base to create the digest content that the signature verification function is applied to (M). The verifier extracts the HTTP message signature to be verified (S) as described in Section 3.2. This value is a 64-octet array made of the encoded values of r and s concatenated in order. Both are encoded as big-endian unsigned integers, zero-padded to 32 octets each. The resulting signature value (r, s) is used as input to the signature verification function. The results of the verification function indicate whether the signature presented is valid. ๐Ÿ”Ž

Note that the output of ECDSA signature algorithms is non-deterministic. That means it is not correct to recalculate a new signature on the signature base and compare the results to an existing signature. Instead, the verification algorithm defined here needs to be used. See Section 7.3.5. ๐Ÿ™ˆ

The use of this algorithm can be indicated at runtime using the ecdsa-p256-sha256 value for the alg signature parameter. ๐Ÿฆ„


3.3.5. ECDSA Using Curve P-384 DSS and SHA-384

To sign with this algorithm, the signer applies the ECDSA signature algorithm defined in [FIPS186-5] using curve P-384 with the signer's private signing key and the signature base (Section 2.5). The hash SHA-384 [RFC6234] is applied to the signature base to create the digest content that the digital signature is applied to (M). The signature algorithm returns two integer values: r and s. Both are encoded as big-endian unsigned integers, zero-padded to 48 octets each. These encoded values are concatenated into a single 96-octet array, with the encoded value of r followed by the encoded value of s. The resulting concatenation of (r, s) is a byte array of the HTTP message signature output used in Section 3.1. ๐Ÿ’Ž

To verify with this algorithm, the verifier applies the ECDSA signature algorithm defined in [FIPS186-5] using the public key portion of the verification key material and the signature base recreated as described in Section 3.2. The hash function SHA-384 [RFC6234] is applied to the signature base to create the digest content that the signature verification function is applied to (M). The verifier extracts the HTTP message signature to be verified (S) as described in Section 3.2. This value is a 96-octet array made of the encoded values of r and s concatenated in order. Both are encoded as big-endian unsigned integers, zero-padded to 48 octets each. The resulting signature value (r, s) is used as input to the signature verification function. The results of the verification function indicate whether the signature presented is valid. ๐Ÿ‘‘

Note that the output of ECDSA signature algorithms is non-deterministic. That means it is not correct to recalculate a new signature on the signature base and compare the results to an existing signature. Instead, the verification algorithm defined here needs to be used. See Section 7.3.5. ๐Ÿซ 

The use of this algorithm can be indicated at runtime using the ecdsa-p384-sha384 value for the alg signature parameter. ๐Ÿ“

3.3.6. EdDSA Using Curve edwards25519 ๐Ÿ’Žโœจ

okay so bestie, this one is the Ed25519 signing moment ๐Ÿฆ‹ to sign, the signer runs the Ed25519 algorithm from Section 5.1.6 of [RFC8032] using their private signing key and the signature base (Section 2.5) ๐Ÿ’… the signature base is the input message (M) and there is NO prehash function, so it goes straight in, no cap ๐Ÿ™ˆ the output is a 64-octet concatenation of R and S, exactly as Section 5.1.6 of [RFC8032] says, and that byte array is what goes into the HTTP message signature output used in Section 3.1 ๐ŸŽ€

to verify, it's giving mirror image ๐Ÿชž the verifier runs the Ed25519 algorithm from Section 5.1.7 of [RFC8032] using the public key portion of the verification key material (A) and the signature base recreated as described in Section 3.2 ๐Ÿ’œ again the signature base is the input message (M) with no prehash function, and the signature being checked gets processed as the 64-octet concatenation of R and S from Section 5.1.7 of [RFC8032] ๐ŸŒธ the results of that verification function tell you whether the signature is valid โœจ

the ed25519 value for the alg signature parameter is how you signal this algorithm at runtime ๐Ÿงš

3.3.7. JSON Web Signature (JWS) Algorithms ๐ŸŒˆ

okay so if the signing algorithm is a JOSE signing algorithm from the "JSON Web Signature and Encryption Algorithms" registry established by [RFC7518], the JWS algorithm definition decides the signature and hashing algorithms for both signing and verification ๐Ÿ‘‘

for both signing and verification, the HTTP message's signature base (Section 2.5) is used as the entire "JWS Signing Input" ๐Ÿ’– the JOSE Header [JWS] [RFC7517] is NOT used ๐Ÿ™…โ€โ™€๏ธ and the signature base isn't Base64 encoded first before the algorithm runs ๐Ÿ˜ญ the output of the JWS Signature is taken as a byte array before the Base64url encoding that JOSE usually does ๐Ÿฆ‹

โš ๏ธ the JWS algorithm MUST NOT be "none", and it MUST NOT be any algorithm with a JOSE Implementation Requirement of "Prohibited". no exceptions, bestie ๐Ÿ’€

JWA values from the "JSON Web Signature and Encryption Algorithms" registry are NOT included as signature parameters ๐Ÿซถ typically the JWS algorithm gets signaled using JSON Web Keys (JWKs) or other mechanisms common to JOSE implementations ๐Ÿ“ in fact, JWA values are not registered in the "HTTP Signature Algorithms" registry (Section 6.2), so the explicit alg signature parameter is not used at all when you're using JOSE signing algorithms ๐ŸŽ€


4. Including a Message Signature in a Message โœจ

okay so HTTP message signatures live inside an HTTP message through two fields, Signature-Input and Signature, both defined in this spec ๐Ÿ’…

Signature-Input says which covered components and parameters describe how the signature was made, while Signature holds the actual signature value ๐ŸŒธ each field MAY contain multiple labeled values ๐Ÿฆ‹

every HTTP message signature gets a label inside the message, and that label MUST be unique within the message ๐Ÿ’œ it MUST also show up in both the Signature-Input field and the Signature field ๐Ÿ™ˆ the signer picks the label, except when a specific label is dictated by protocol negotiations like the ones described in Section 5 ๐Ÿงš

an HTTP message signature MUST use both the Signature-Input field and the Signature field, and each field MUST contain the same labels ๐Ÿ‘‘ if a label is in one field but not the other, that's an error, periodt ๐Ÿ˜ญ

4.1. The Signature-Input HTTP Field ๐ŸŽ€

the Signature-Input field is a Dictionary Structured Field (defined in Section 3.2 of [STRUCTURED-FIELDS]) holding the metadata for one or more message signatures made from components in the HTTP message โœจ each member describes one message signature ๐ŸŒˆ the member's key is the label that uniquely identifies that signature in the message, and the member's value is the covered components ordered set serialized as an Inner List, including all signature metadata parameters for that label ๐Ÿ’–

NOTE: '' line wrapping per RFC 8792

Signature-Input: sig1=("@method" "@target-uri" "@authority" \
  "content-digest" "cache-control");\
  created=1618884475;keyid="test-key-rsa-pss"

to make validation easy, the Signature-Input field value MUST contain the same serialized value used when generating the signature base's @signature-params value defined in Section 2.3 ๐Ÿ’… heads up, in a Structured Field value, list order and parameter order have to be preserved ๐Ÿ™ˆ

the signer MAY include the Signature-Input field as a trailer, which helps when signing a message after its content has been processed ๐Ÿฆ‹ but intermediaries are allowed to drop trailers as per [HTTP], so it is RECOMMENDED that Signature-Input only be included as a header field, so signatures don't get inadvertently stripped from a message ๐Ÿ˜ญ๐Ÿซถ

multiple Signature-Input fields MAY be included in a single HTTP message ๐ŸŒธ the signature labels MUST be unique across all field values ๐Ÿ’œ

4.2. The Signature HTTP Field ๐Ÿ’œ

the Signature field is a Dictionary Structured Field (defined in Section 3.2 of [STRUCTURED-FIELDS]) containing one or more message signatures generated from the signature context of the target message โœจ the member's key is the label that uniquely identifies that signature in the HTTP message, and the member's value is a Byte Sequence holding the signature value for the message signature with that label ๐Ÿงš

NOTE: '' line wrapping per RFC 8792

Signature: sig1=:P0wLUszWQjoi54udOtydf9IWTfNhy+r53jGFj9XZuP4uKwxyJo\
  1RSHi+oEF1FuX6O29d+lbxwwBao1BAgadijW+7O/PyezlTnqAOVPWx9GlyntiCiHz\
  C87qmSQjvu1CFyFuWSjdGa3qLYYlNm7pVaJFalQiKWnUaqfT4LyttaXyoyZW84jS8\
  gyarxAiWI97mPXU+OVM64+HVBHmnEsS+lTeIsEQo36T3NFf2CujWARPQg53r58Rmp\
  Z+J9eKR2CD6IJQvacn5A4Ix5BUAVGqlyp8JYm+S/CWJi31PNUjRRCusCVRj05NrxA\
  BNFv3r5S9IXf2fYJK+eyW4AiGVMvMcOg==:

the signer MAY include the Signature field as a trailer to make signing easier after the content has been processed ๐ŸŒˆ but since intermediaries are allowed to drop trailers as per [HTTP], it is RECOMMENDED that Signature only be included as a header field, so signatures don't get inadvertently stripped from a message ๐Ÿ’…

multiple Signature fields MAY be included in a single HTTP message ๐ŸŽ€ the signature labels MUST be unique across all field values ๐Ÿ‘‘

4.3. Multiple Signatures ๐Ÿ’–

okay so multiple distinct signatures are totally allowed in one message ๐Ÿฆ‹ (they MAY be included!!). The one rule is that every distinct signature MUST have a unique label, so nobody gets confused about which signature is which ๐Ÿ’…. These signatures can all come from the same signer, or from several different signers. A signer might sign the same message components with different keys or algorithms so that verifiers with different capabilities can each check one they understand ๐Ÿงš. Or a reverse proxy might add info about the client to the fields when it forwards the request to a service host, including a signature over the client's original signature values ๐Ÿ‘‘.

The next example is non-normative, so it's just vibes and not a rule ๐ŸŒธ. It starts with a signed request from the client. A reverse proxy takes that request and checks the client's signature:

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Length: 18
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Signature-Input: sig1=("@method" "@authority" "@path" \
  "content-digest" "content-type" "content-length")\
  ;created=1618884475;keyid="test-key-ecc-p256"
Signature: sig1=:X5spyd6CFnAG5QnDyHfqoSNICd+BUP4LYMz2Q0JXlb//4Ijpzp\
  +kve2w4NIyqeAuM7jTDX+sNalzA8ESSaHD3A==:

{"hello": "world"}

NOTE: '\' line wrapping per RFC 8792

Then the proxy changes the message before it goes on to the origin server ๐Ÿ“. It changes the target host and adds the Forwarded header field from [RFC7239]:

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: origin.host.internal.example
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Length: 18
Forwarded: for=192.0.2.123;host=example.com;proto=https
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Signature-Input: sig1=("@method" "@authority" "@path" \
  "content-digest" "content-type" "content-length")\
  ;created=1618884475;keyid="test-key-ecc-p256"
Signature: sig1=:X5spyd6CFnAG5QnDyHfqoSNICd+BUP4LYMz2Q0JXlb//4Ijpzp\
  +kve2w4NIyqeAuM7jTDX+sNalzA8ESSaHD3A==:

{"hello": "world"}

NOTE: '\' line wrapping per RFC 8792

So the proxy is in a great spot to check the incoming client's signature and then make its own statement to the origin server about what kind of request it's forwarding ๐Ÿ’œ. It does this by adding its own signature over the new message before passing it along. The proxy also includes all the elements from the original message that the origin server needs for its processing โœจ. In a lot of cases the proxy will want to cover all the same components the client's signature covered, and that's what happens in the next example. Notice that the proxy signs over the new authority value, since it changed that. It also adds the Forwarded header field to its own signature. The proxy names its own key and algorithm, and here it adds an expiration to the signature. That tells downstream systems the proxy won't vouch for this signed message past a short time window โฐ. This gives a signature base of:

NOTE: '\' line wrapping per RFC 8792

"@method": POST
"@authority": origin.host.internal.example
"@path": /foo
"content-digest": sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX\
  +TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
"content-type": application/json
"content-length": 18
"forwarded": for=192.0.2.123;host=example.com;proto=https
"@signature-params": ("@method" "@authority" "@path" \
  "content-digest" "content-type" "content-length" "forwarded")\
  ;created=1618884480;keyid="test-key-rsa";alg="rsa-v1_5-sha256"\
  ;expires=1618884540

and a signature output value of:

NOTE: '\' line wrapping per RFC 8792

S6ZzPXSdAMOPjN/6KXfXWNO/f7V6cHm7BXYUh3YD/fRad4BCaRZxP+JH+8XY1I6+8Cy\
+CM5g92iHgxtRPz+MjniOaYmdkDcnL9cCpXJleXsOckpURl49GwiyUpZ10KHgOEe11s\
x3G2gxI8S0jnxQB+Pu68U9vVcasqOWAEObtNKKZd8tSFu7LB5YAv0RAGhB8tmpv7sFn\
Im9y+7X5kXQfi8NMaZaA8i2ZHwpBdg7a6CMfwnnrtflzvZdXAsD3LH2TwevU+/PBPv0\
B6NMNk93wUs/vfJvye+YuI87HU38lZHowtznbLVdp770I6VHR6WfgS9ddzirrswsE1w\
5o0LV/g==

These values get added to the HTTP request message by the proxy ๐Ÿซถ. The original signature sits under the label sig1, and the reverse proxy's signature sits under the label proxy_sig. The proxy uses the key test-key-rsa to make its signature with the rsa-v1_5-sha256 algorithm, while the client's original signature was made with the key test-key-rsa-pss and an RSA-PSS algorithm:

NOTE: '\' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: origin.host.internal.example
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Length: 18
Forwarded: for=192.0.2.123;host=example.com;proto=https
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Signature-Input: sig1=("@method" "@authority" "@path" \
    "content-digest" "content-type" "content-length")\
    ;created=1618884475;keyid="test-key-ecc-p256", \
  proxy_sig=("@method" "@authority" "@path" "content-digest" \
    "content-type" "content-length" "forwarded")\
    ;created=1618884480;keyid="test-key-rsa";alg="rsa-v1_5-sha256"\
    ;expires=1618884540
Signature: sig1=:X5spyd6CFnAG5QnDyHfqoSNICd+BUP4LYMz2Q0JXlb//4Ijpzp\
    +kve2w4NIyqeAuM7jTDX+sNalzA8ESSaHD3A==:, \
  proxy_sig=:S6ZzPXSdAMOPjN/6KXfXWNO/f7V6cHm7BXYUh3YD/fRad4BCaRZxP+\
    JH+8XY1I6+8Cy+CM5g92iHgxtRPz+MjniOaYmdkDcnL9cCpXJleXsOckpURl49G\
    wiyUpZ10KHgOEe11sx3G2gxI8S0jnxQB+Pu68U9vVcasqOWAEObtNKKZd8tSFu7\
    LB5YAv0RAGhB8tmpv7sFnIm9y+7X5kXQfi8NMaZaA8i2ZHwpBdg7a6CMfwnnrtf\
    lzvZdXAsD3LH2TwevU+/PBPv0B6NMNk93wUs/vfJvye+YuI87HU38lZHowtznbL\
    Vdp770I6VHR6WfgS9ddzirrswsE1w5o0LV/g==:

{"hello": "world"}

Now, you could also have the proxy put the client's Signature field value and Signature-Input fields from the original message into the new signature's covered components ๐Ÿ™ˆ. But this practice is NOT RECOMMENDED, because signing signature values has known weaknesses, as covered in Section 7.3.7. The proxy is able to check the client's signature, but the changes the proxy makes will break the existing signature by the time the origin server sees the message ๐Ÿ’€. In this example, the origin server could put extra info into its signature context to account for the authority change. Still, that takes extra configuration and extra care, as discussed in Section 7.4.4 โœจ. In other apps, the origin server won't be able to verify the original signature itself, but it may still want to confirm that the proxy properly checked the client's signature ๐ŸŒˆ. And if an application needs to signal that a signature was successfully processed or received, it would need to carefully specify other mechanisms for sending that signal securely ๐Ÿ”.


5. Requesting Signatures ๐ŸŽ€

A signer is totally free to attach a signature to a request or response without being asked ๐Ÿ’…. Still, it's often nice for a potential verifier to say "hey, I expect a signature from you" using the Accept-Signature field ๐Ÿฆ‹.

When the Accept-Signature field shows up in an HTTP request message, it means the client wants the server to sign the response with the identified parameters. The target message here is the response to that request ๐Ÿ‘‘. Every response from a resource that supports this kind of signature negotiation SHOULD either be uncacheable or include a Vary header field that lists Accept-Signature. That stops a cache from handing back a response with a signature meant for a different request ๐Ÿ™…โ€โ™€๏ธ.

When the Accept-Signature field is used in an HTTP response message, it means the server wants the client to sign its next request to the server with the identified parameters. In this case the target message is the client's next request ๐Ÿงš. The client can also choose to keep signing future requests to that same server in the same way โœจ.

The target message of an Accept-Signature field MUST include all labeled signatures indicated in the Accept-Signature field, and each one covers the same identified components of the Accept-Signature field ๐Ÿซถ.

The sender of an Accept-Signature field MUST include only identifiers that fit the type of the target message ๐Ÿ’…. For example, if the target message is a request, the covered components cannot include the @status component identifier ๐Ÿ™ƒ.

5.1. The Accept-Signature Field ๐Ÿฆ‹

The Accept-Signature field is a Dictionary Structured Field (defined in Section 3.2 of [STRUCTURED-FIELDS]). It holds the metadata for one or more requested message signatures, which get generated from message components of the target HTTP message ๐ŸŒธ. Each member describes one single message signature. The member's key is the label that uniquely identifies the requested message signature within the context of the target HTTP message ๐Ÿท๏ธ.

The member's value is the serialization of the desired covered components of the target message, including any allowed component metadata parameters. It uses the serialization process from Section 2.3 ๐Ÿ’–:

NOTE: '\' line wrapping per RFC 8792

Accept-Signature: sig1=("@method" "@target-uri" "@authority" \
  "content-digest" "cache-control");\
  keyid="test-key-rsa-pss";created;tag="app-123"

The list of component identifiers says exactly which component identifiers should be in the requested signature, including all the applicable component parameters ๐Ÿ“.

The signature request MAY include signature metadata parameters that tell the signer how to behave. This spec defines the following behavior:

  • created: The signer is asked to generate and include a creation time. This parameter has no associated value when it's sent as a signature request.
  • expires: The signer is asked to generate and include an expiration time. This parameter has no associated value when it's sent as a signature request.
  • nonce: The signer is asked to include the value of this parameter as the signature nonce in the target signature.
  • alg: The signer is asked to use the indicated signature algorithm from the "HTTP Signature Algorithms" registry to create the target signature.
  • keyid: The signer is asked to use the indicated key material to create the target signature.
  • tag: The signer is asked to include the value of this parameter as the signature tag in the target signature.

5.2. Processing an Accept-Signature ๐ŸŽ€

Okay so here's how the receiver handles an Accept-Signature field, bestie ๐Ÿ’… Follow the steps in order:

  1. Parse the field value as a Dictionary. ๐Ÿงš
  2. For each member of the Dictionary: โœจ
    • 2.1. The key is taken as the label of the output signature, as specified in Section 4.1. ๐Ÿท๏ธ
    • 2.2. Parse the value of the member to get the set of covered component identifiers. ๐Ÿ”
    • 2.3. Check that the covered components apply to the target message. If they don't, the process fails and returns an error. ๐Ÿ™…โ€โ™€๏ธ
    • 2.4. Process the requested parameters, like the signing algorithm and key material. If any requested parameters can't be fulfilled, or if they conflict with the ones deemed appropriate for the target message, the process fails and returns an error. ๐Ÿ’€
    • 2.5. Select and generate any extra parameters needed to finish the signature. ๐ŸŒธ
    • 2.6. Create the HTTP message signature over the target message. ๐Ÿ”
    • 2.7. Create the Signature-Input and Signature field values, and associate them with the label. ๐Ÿฆ‹
  3. Optionally, create extra Signature-Input and Signature field values, with unique labels that don't show up in the Accept-Signature field. ๐Ÿ’–
  4. Combine all the labeled Signature-Input and Signature field values, and attach both fields to the target message. ๐Ÿ‘‘

So the big takeaway: a signature applied to a target message MUST have the same label, MUST include the same set of covered components, and MUST process all requested parameters. It can also have additional parameters, which is the MAY part, so it's giving flexibility ๐ŸŒˆ.

The receiver of an Accept-Signature field MAY ignore any signature request that doesn't fit its application parameters. Not everything has to be a yes, babe ๐Ÿ™ˆ

The target message MAY include additional signatures that the Accept-Signature field didn't ask for. For example, if the signer wants to cover extra message components, it can make a second signature that includes those components plus the signature output of the requested one. Layers on layers, iconic ๐Ÿง


6. IANA Considerations ๐Ÿ“š

IANA has updated one registry and created four new registries, according to the following sections. ๐Ÿ—‚๏ธ

6.1. HTTP Field Name Registration ๐Ÿท๏ธ

IANA has updated the entries in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" like this:

     +==================+===========+=========================+
     | Field Name       | Status    | Reference               |
     +==================+===========+=========================+
     | Signature-Input  | permanent | Section 4.1 of RFC 9421 |
     +------------------+-----------+-------------------------+
     | Signature        | permanent | Section 4.2 of RFC 9421 |
     +------------------+-----------+-------------------------+
     | Accept-Signature | permanent | Section 5.1 of RFC 9421 |
     +------------------+-----------+-------------------------+

        Table 1: Updates to the Hypertext Transfer Protocol
                     (HTTP) Field Name Registry

6.2. HTTP Signature Algorithms Registry ๐Ÿ”‘

This document defines HTTP signature algorithms, and IANA has created and now maintains a brand-new registry called "HTTP Signature Algorithms" for them. The initial values are in Section 6.2.2. Future assignments and changes to existing ones go through the Specification Required registration policy [RFC8126] ๐Ÿ“

The algorithms in this registry identify some possible cryptographic algorithms that applications can use with this specification. But the entries are NOT an exhaustive list of everything possible, and they don't say anything about fitness for any particular application of this specification ๐Ÿ™…โ€โ™€๏ธ An application is free to implement whatever algorithm suits its needs, as long as the signer and verifier can agree on the algorithm's parameters in a secure and deterministic way. When an application needs to signal a particular algorithm at runtime using the alg signature parameter, this registry gives a mapping between that parameter's value and a specific algorithm. Still, the alg parameter has to be handled with caution to avoid algorithm confusion and substitution attacks, which are covered in Section 7 ๐Ÿ˜ฌ

The Status value should reflect the standardization status and the broad opinion of relevant interest groups, like the IETF or security-related Standards Development Organizations (SDOs). When an algorithm is first registered, the designated expert (DE) should set the Status field to "Active" if there's consensus that the algorithm is generally recommended as secure. If there isn't that consensus yet, like for an experimental algorithm, the DE sets it to "Provisional" instead. A "Provisional" status does NOT mean the algorithm is known to be insecure. It just means the group hasn't reached consensus on its properties ๐Ÿคทโ€โ™€๏ธ If an algorithm as registered later turns out to have flaws, the registry entry can be updated and the algorithm can be marked "Deprecated", which means it's been found to have problems. That status doesn't stop an application from using the algorithm. It's more of a warning about known issues the application should think about ๐Ÿšจ The DE can also make sure the registration includes an explanation and a reference for the Status value. This is especially important for deprecated algorithms ๐Ÿ“–

The DE is expected to do the following:

  • Make sure the algorithms referenced by a registered algorithm identifier are fully defined, with all parameters (for example salt, hash, and required key length) fixed by the defining text. ๐Ÿงฉ
  • Make sure the algorithm definition fully specifies the HTTP_SIGN and HTTP_VERIFY primitive functions, including how all the defined inputs and outputs map to the underlying cryptographic algorithm. โš™๏ธ
  • Reject any registrations that are aliases of existing registrations. ๐Ÿšซ
  • Make sure all registrations follow the template in Section 6.2.1. This includes checking that the name isn't excessively long while still being unique and recognizable. ๐Ÿ’…

This specification creates algorithm identifiers by putting major parameters into the identifier String, so the algorithm name is unique and recognizable to developers. However, algorithm identifiers in this registry are to be read as whole String values, NOT as a combo of parts. In other words, implementors should understand rsa-pss-sha512 as referring to one specific algorithm, with its hash, mask, and salt values set as defined in the text that establishes that identifier. Implementors do NOT parse out the rsa, pss, and sha512 pieces to work out the signing algorithm's parameters from the String. And registering one combination of parameters doesn't mean any other combination is registered too ๐Ÿง 

6.2.1. Registration Template ๐Ÿ“‹

Algorithm Name: An identifier for the HTTP signature algorithm. The name MUST be an ASCII string that conforms to the sf-string ABNF rule in Section 3.3.3 of [STRUCTURED-FIELDS], and it SHOULD NOT exceed 20 characters in length. The identifier MUST be unique within the context of the registry. ๐Ÿท๏ธ

Description: A brief description of the algorithm used to sign the signature base. โœ๏ธ

Status: The status of the algorithm. It MUST start with one of the following values, and it MAY contain additional explanatory text. The options are: ๐Ÿšฆ

"Active": For algorithms without known problems. The signature algorithm is fully specified, and its security properties are understood. โœ…

"Provisional": For unproven algorithms. The signature algorithm is fully specified, but its security properties are not known or proven. ๐Ÿค”

"Deprecated": For algorithms with known security issues. The signature algorithm is no longer recommended for general use and might be insecure or unsafe in some known circumstances. โš ๏ธ

Reference: Reference to the document or documents that specify the algorithm, preferably including a URI that can be used to retrieve a copy of the document(s). An indication of the relevant sections may also be included, but it is not required. ๐Ÿ”—

6.2.2. Initial Contents ๐ŸŒฑ

The table below has the initial contents of the "HTTP Signature Algorithms" registry:

    +===================+===================+========+===============+
    | Algorithm Name    | Description       | Status | Reference     |
    +===================+===================+========+===============+
    | rsa-pss-sha512    | RSASSA-PSS using  | Active | Section 3.3.1 |
    |                   | SHA-512           |        | of RFC 9421   |
    +-------------------+-------------------+--------+---------------+
    | rsa-v1_5-sha256   | RSASSA-PKCS1-v1_5 | Active | Section 3.3.2 |
    |                   | using SHA-256     |        | of RFC 9421   |
    +-------------------+-------------------+--------+---------------+
    | hmac-sha256       | HMAC using        | Active | Section 3.3.3 |
    |                   | SHA-256           |        | of RFC 9421   |
    +-------------------+-------------------+--------+---------------+
    | ecdsa-p256-sha256 | ECDSA using curve | Active | Section 3.3.4 |
    |                   | P-256 DSS and     |        | of RFC 9421   |
    |                   | SHA-256           |        |               |
    +-------------------+-------------------+--------+---------------+
    | ecdsa-p384-sha384 | ECDSA using curve | Active | Section 3.3.5 |
    |                   | P-384 DSS and     |        | of RFC 9421   |
    |                   | SHA-384           |        |               |
    +-------------------+-------------------+--------+---------------+
    | ed25519           | EdDSA using curve | Active | Section 3.3.6 |
    |                   | edwards25519      |        | of RFC 9421   |
    +-------------------+-------------------+--------+---------------+

        Table 2: Initial Contents of the HTTP Signature Algorithms
                                 Registry

6.3. HTTP Signature Metadata Parameters Registry ๐Ÿงพ

This document defines the signature parameters structure (Section 2.3), which can have parameters holding metadata about a message signature. IANA has created and now maintains a new registry called "HTTP Signature Metadata Parameters" to record and maintain the set of parameters defined for use with member values in the signature parameters structure. The initial values for this registry are in Section 6.3.2. Future assignments and changes to existing ones go through the Expert Review registration policy [RFC8126] ๐ŸŒท

The DE is expected to do the following:

  • Make sure the name follows the template in Section 6.3.1. This includes checking that the name isn't excessively long while still being unique and recognizable for its defined function. ๐Ÿ”
  • Make sure the defined functionality is clear and doesn't conflict with other registered parameters. ๐Ÿค
  • Make sure the definition of the metadata parameter includes its behavior when used as part of the normal signature process, as well as when used in an Accept-Signature field. ๐Ÿ’œ

6.3.1. Registration Template ๐ŸŽ€

Name: An identifier for the HTTP signature metadata parameter ๐Ÿ’…. The name MUST be an ASCII string that conforms to the key ABNF rule defined in Section 3.1.2 of [STRUCTURED-FIELDS] and SHOULD NOT exceed 20 characters in length. The identifier MUST be unique within the context of the registry ๐Ÿซถ.

Description: A brief description of the metadata parameter and what it represents โœจ.

Reference: Reference to the document or documents that specify the parameter, preferably including a URI that can be used to retrieve a copy of the document(s). An indication of the relevant sections may also be included but is not required ๐Ÿ“š.

6.3.2. Initial Contents ๐ŸŒธ

Okay so here's the starter pack for the "HTTP Signature Metadata Parameters" registry ๐Ÿงš. Every row is its own distinct entry, so no duplicates, bestie ๐Ÿ’œ.

+=========+===============================+=============+
| Name    | Description                   | Reference   |
+=========+===============================+=============+
| alg     | Explicitly declared signature | Section 2.3 |
|         | algorithm                     | of RFC 9421 |
+---------+-------------------------------+-------------+
| created | Timestamp of signature        | Section 2.3 |
|         | creation                      | of RFC 9421 |
+---------+-------------------------------+-------------+
| expires | Timestamp of proposed         | Section 2.3 |
|         | signature expiration          | of RFC 9421 |
+---------+-------------------------------+-------------+
| keyid   | Key identifier for the        | Section 2.3 |
|         | signing and verification keys | of RFC 9421 |
|         | used to create this signature |             |
+---------+-------------------------------+-------------+
| nonce   | A single-use nonce value      | Section 2.3 |
|         |                               | of RFC 9421 |
+---------+-------------------------------+-------------+
| tag     | An application-specific tag   | Section 2.3 |
|         | for a signature               | of RFC 9421 |
+---------+-------------------------------+-------------+

     Table 3: Initial Contents of the HTTP Signature
               Metadata Parameters Registry

6.4. HTTP Signature Derived Component Names Registry ๐Ÿฆ‹

This document defines a method for canonicalizing HTTP message components, and that includes components you can derive from the context of the target message outside of the HTTP fields ๐Ÿ”ฅ. These derived components get identified by a unique String, which is called the component name. Component names for derived components always start with the "at" (@) symbol, so they never get mixed up with HTTP field names ๐Ÿ’–. IANA has created and now maintains a new registry titled "HTTP Signature Derived Component Names" to record and maintain the set of non-field component names and the methods used to produce their associated component values ๐Ÿ‘‘. The initial values for this registry are in Section 6.4.2. Future assignments and modifications to existing assignments are made through the Expert Review registration policy [RFC8126] โœจ.

The DE is expected to do the following:

  • Ensure that the name follows the template presented in Section 6.4.1; this includes ensuring that the length of the name is not excessive while still being unique and recognizable for its defined function ๐Ÿ’….
  • Ensure that the component value represented by the registration request can be deterministically derived from the target HTTP message ๐ŸŽ€.
  • Ensure that any parameters defined for the registration request are clearly documented, along with their effects on the component value ๐Ÿ“š.

The DE should ensure that a registration is sufficiently distinct from existing derived component definitions to warrant its registration ๐Ÿซถ. And when a registered item's status gets set to "Deprecated", the DE should ensure that a reason for the deprecation is documented, along with instructions for moving away from the deprecated functionality ๐Ÿ’€.

6.4.1. Registration Template ๐ŸŒˆ

Name: A name for the HTTP derived component ๐Ÿ“. The name MUST begin with the "at" (@) character, followed by an ASCII string consisting only of lowercase characters ("a"-"z"), digits ("0"-"9"), and hyphens ("-"), and SHOULD NOT exceed 20 characters in length. The name MUST be unique within the context of the registry ๐Ÿ’œ.

Description: A description of the derived component ๐Ÿฆ‹.

Status: A brief text description of the status of the algorithm. The description MUST begin with one of "Active" or "Deprecated" and MAY provide further context or explanation as to the reason for the status ๐Ÿ”ฅ. A value of "Deprecated" indicates that the derived component name is no longer recommended for use ๐Ÿ™ˆ.

Target: The valid message targets for the derived parameter. MUST be one of the values "Request", "Response", or "Request, Response". The semantics of these entries are defined in Section 2.2 ๐Ÿ‘‘.

Reference: Reference to the document or documents that specify the derived component, preferably including a URI that can be used to retrieve a copy of the document(s). An indication of the relevant sections may also be included but is not required ๐Ÿ“š.

6.4.2. Initial Contents ๐Ÿงš

Here's the initial contents of the "HTTP Signature Derived Component Names" registry, served fresh ๐Ÿ’…โœจ.

+===================+==============+========+==========+===========+
| Name              | Description  | Status | Target   | Reference |
+===================+==============+========+==========+===========+
| @signature-params | Reserved for | Active | Request, | Section   |
|                   | signature    |        | Response | 2.3 of    |
|                   | parameters   |        |          | RFC 9421  |
|                   | line in      |        |          |           |
|                   | signature    |        |          |           |
|                   | base         |        |          |           |
+-------------------+--------------+--------+----------+-----------+
| @method           | The HTTP     | Active | Request  | Section   |
|                   | request      |        |          | 2.2.1 of  |
|                   | method       |        |          | RFC 9421  |
+-------------------+--------------+--------+----------+-----------+
| @authority        | The HTTP     | Active | Request  | Section   |
|                   | authority,   |        |          | 2.2.3 of  |
|                   | or target    |        |          | RFC 9421  |
|                   | host         |        |          |           |
+-------------------+--------------+--------+----------+-----------+
| @scheme           | The URI      | Active | Request  | Section   |
|                   | scheme of    |        |          | 2.2.4 of  |
|                   | the request  |        |          | RFC 9421  |
|                   | URI          |        |          |           |
+-------------------+--------------+--------+----------+-----------+
| @target-uri       | The full     | Active | Request  | Section   |
|                   | target URI   |        |          | 2.2.2 of  |
|                   | of the       |        |          | RFC 9421  |
|                   | request      |        |          |           |
+-------------------+--------------+--------+----------+-----------+
| @request-target   | The request  | Active | Request  | Section   |
|                   | target of    |        |          | 2.2.5 of  |
|                   | the request  |        |          | RFC 9421  |
+-------------------+--------------+--------+----------+-----------+
| @path             | The full     | Active | Request  | Section   |
|                   | path of the  |        |          | 2.2.6 of  |
|                   | request URI  |        |          | RFC 9421  |
+-------------------+--------------+--------+----------+-----------+
| @query            | The full     | Active | Request  | Section   |
|                   | query of the |        |          | 2.2.7 of  |
|                   | request URI  |        |          | RFC 9421  |
+-------------------+--------------+--------+----------+-----------+
| @query-param      | A single     | Active | Request  | Section   |
|                   | named query  |        |          | 2.2.8 of  |
|                   | parameter    |        |          | RFC 9421  |
+-------------------+--------------+--------+----------+-----------+
| @status           | The status   | Active | Response | Section   |
|                   | code of the  |        |          | 2.2.9 of  |
|                   | response     |        |          | RFC 9421  |
+-------------------+--------------+--------+----------+-----------+

      Table 4: Initial Contents of the HTTP Signature Derived
                      Component Names Registry

6.5. HTTP Signature Component Parameters Registry ๐Ÿ’–

This document defines several kinds of component identifiers, and some of them can be parameterized in specific circumstances to give unique modified behavior ๐ŸŒธ. IANA has created and now maintains a new registry titled "HTTP Signature Component Parameters" to record and maintain the set of parameter names, the component identifiers they're associated with, and the modifications these parameters make to the component value ๐Ÿฆ‹. Definitions of parameters MUST define the targets they apply to (such as specific field types, derived components, or contexts) ๐Ÿ”ฅ. The initial values for this registry are in Section 6.5.2. Future assignments and modifications to existing assignments are made through the Expert Review registration policy [RFC8126] ๐Ÿ‘‘.

The DE is expected to do the following:

  • Ensure that the name follows the template presented in Section 6.5.1; this includes ensuring that the length of the name is not excessive while still being unique and recognizable for its defined function ๐Ÿ’….
  • Ensure that the definition of the field sufficiently defines any interactions or incompatibilities with other existing parameters known at the time of the registration request ๐Ÿซถ.
  • Ensure that the component value defined by the component identifier with the parameter applied can be deterministically derived from the target HTTP message in cases where the parameter changes the component value ๐ŸŽ€.

6.5.1. Registration Template ๐Ÿ’œ

Name: A name for the parameter. The name MUST be an ASCII string that conforms to the key ABNF rule defined in Section 3.1.2 of [STRUCTURED-FIELDS] and SHOULD NOT exceed 20 characters in length. The name MUST be unique within the context of the registry ๐Ÿ“.

Description: A description of the parameter's function โœจ.

Reference: Reference to the document or documents that specify the derived component, preferably including a URI that can be used to retrieve a copy of the document(s). An indication of the relevant sections may also be included but is not required ๐Ÿ“š.

6.5.2. Initial Contents โœจ

Okay so here's the starter pack for the "HTTP Signature Component Parameters" registry ๐Ÿ’… Six entries, each one tied to the section that explains it. Table 5 has the lineup:

     +======+=======================================+===============+
     | Name | Description                           | Reference     |
     +======+=======================================+===============+
     | sf   | Strict Structured Field serialization | Section 2.1.1 |
     |      |                                       | of RFC 9421   |
     +------+---------------------------------------+---------------+
     | key  | Single key value of Dictionary        | Section 2.1.2 |
     |      | Structured Fields                     | of RFC 9421   |
     +------+---------------------------------------+---------------+
     | bs   | Byte Sequence wrapping indicator      | Section 2.1.3 |
     |      |                                       | of RFC 9421   |
     +------+---------------------------------------+---------------+
     | tr   | Trailer                               | Section 2.1.4 |
     |      |                                       | of RFC 9421   |
     +------+---------------------------------------+---------------+
     | req  | Related request indicator             | Section 2.4   |
     |      |                                       | of RFC 9421   |
     +------+---------------------------------------+---------------+
     | name | Single named query parameter          | Section 2.2.8 |
     |      |                                       | of RFC 9421   |
     +------+---------------------------------------+---------------+

        Table 5: Initial Contents of the HTTP Signature Component
                           Parameters Registry

7. Security Considerations ๐Ÿ”’

Okay, this section is the serious one, bestie, so let's be real about it. A message only counts as covered by a signature when all of these are true:

  • A signature is expected or allowed on the message by the verifier.
  • The signature exists on the message.
  • The signature is verified against the identified key material and algorithm.
  • The key material and algorithm are appropriate for the context of the message.
  • The signature is within expected time boundaries.
  • The signature covers the expected content, including any critical components.
  • The list of covered components is applicable to the context of the message.

Section 1.4 already has its own list of application requirements. On top of that, the stuff below gives you the context and discussion you need for creating and verifying signatures on HTTP messages. Read it all, it's giving main character energy ๐ŸŽ€

7.1. General Considerations

7.1.1. Skipping Signature Verification ๐Ÿ™ˆ

Real talk: HTTP message signatures only give you security if the verifier actually checks them ๐Ÿ˜ญ The message is still a totally valid HTTP message even without the Signature or Signature-Input fields, so a verifier could run the verification function, ignore the answer, and process the message anyway. People do this for reasons like relaxed rules in a dev environment, or a quick pause on enforcement while they debug the whole system. These pauses are sneaky, too, because positive-example testing won't catch them. A good signature always gets a valid response, whether or not anyone actually checked it.

To catch this, test verifiers with both valid and invalid signatures, and make sure the invalid ones fail the way they should ๐Ÿงš

7.1.2. Use of TLS ๐Ÿ”

Using HTTP message signatures does not get rid of the need for TLS or something equivalent protecting data in transit. Signatures give you integrity over the covered message components, but they don't give you any confidentiality between the parties.

TLS does give that confidentiality between its endpoints, and it also keeps the signature data itself away from an attacker who's trying to capture it. That matters a lot for stopping signature replay (Section 7.2.2) ๐Ÿ’œ

When TLS is in play, it needs to be deployed following the recommendations in [BCP195].

7.2. Message Processing and Selection ๐Ÿ’Œ

7.2.1. Insufficient Coverage ๐Ÿšจ

Any part of the message that the signature doesn't cover is up for grabs. An attacker can change it without breaking the signature. They can add or edit a header field or some other component that changes how the message gets processed but isn't covered. That altered message would still pass signature verification, and then once the verifier processes the whole thing, the unsigned stuff the attacker snuck in can undermine the trust the valid signature was supposed to give, and flip the outcome ๐Ÿ˜ค

So the fix: an application of this specification should require as much of the message as it reasonably can to be signed, within the limits of the application and deployment. The verifier should only trust components that were actually signed. Verifiers could also strip out any sensitive unsigned parts of the message before they keep processing it โœจ

7.2.2. Signature Replay ๐Ÿ”

HTTP message signatures let you sign just sub-portions of a message, which means two different HTTP messages could end up validating against the same signature. The worst version of this is a signature that covers no message components at all. If an attacker grabbed one, they could stick it on any HTTP message and replay it whenever they wanted ๐Ÿ’€ Even with decent component coverage, one signature could be applied to two similar messages, so an attacker could replay a message with its signature still intact.

To fight this, the signer first needs to cover enough of the message to tell it apart from other messages. The signature can also use the nonce signature parameter, which is a unique value per message. If a nonce ever repeats, the verifier can spot a replayed signature. The signer can additionally include a created timestamp and an expires time after which it considers the signature expired, which limits how useful a captured signature is. ๐Ÿ•ฐ๏ธ

If a verifier wants a fresh signature from a signer, it can send the Accept-Signature header field with a new nonce parameter. An attacker who is just replaying an old signature can't produce a new one with the nonce they didn't choose ๐Ÿ‘‘

7.2.3. Choosing Message Components ๐Ÿงฉ

Applications have to decide which message components get covered by the signature. Depending on the app, some components might be changed by intermediaries before the signature gets verified. If you cover those components, those changes will break the signature, and that's by design.

This document gives you flexibility in choosing what gets signed, so each application can pick the right parts of the message and skip the problematic ones ๐Ÿฆ‹ For example, a web app framework that rewrites query parameters might avoid the @query derived component and sub-index the query value with @query-param derived components instead.

Some components are expected to get changed by intermediaries and really shouldn't be signed in most situations. The Via and Forwarded header fields are a classic case, since proxies and other middleboxes are supposed to manipulate them, even replacing or dropping existing values entirely. These fields should not be covered by the signature, except in very limited, tightly coupled scenarios.

More considerations for picking signature aspects are in Section 1.4 ๐ŸŒธ

7.2.4. Choosing Signature Parameters and Derived Components over HTTP Fields ๐Ÿ“

Some HTTP fields have values and meanings that look a lot like signature parameters or derived components. Usually it's better to sign the non-field alternative. Specifically, the following fields should usually stay out of the signature unless the application really needs them:

"date" The date header field value is the timestamp of the HTTP message. But the signature's own creation time is encoded in the created signature parameter. Those two values can be different, depending on how the signature and the message get created and serialized. Apps checking valid time windows should use the created signature parameter for those calculations. An application could also cap how much skew there is between the date field and the created signature parameter, which limits how far a generated signature can be applied to different HTTP messages. See also Sections 7.2.2 and 7.2.1.

"host" The host header field is specific to HTTP/1.1, and its job is covered by the @authority derived component, defined in Section 2.2.3. To keep the value consistent across HTTP versions, applications should always use the @authority derived component. See also Section 7.5.4.

7.2.5. Signature Labels ๐Ÿท๏ธ

Signature values get identified in the Signature and Signature-Input field values by unique labels. Those labels are picked only when the signature values get attached to the message, and the signing process doesn't account for them. An intermediary is allowed to relabel an existing signature while processing the message.

So apps shouldn't depend on specific labels being there, and they shouldn't attach any meaning to the labels themselves. If you need to convey extra meaning and have it covered by the signature, use additional signature parameters instead. The tag parameter is especially handy for this, since it can carry an application-specific value, as described in Section 7.2.7 โœจ

7.2.6. Multiple Signature Confusion ๐Ÿ‘ฏ

Since one message can carry multiple signatures (Section 4.3), an attacker could attach their own signature to a captured message without touching the existing ones. That new signature might be totally valid under the attacker's key, or it might be invalid for any number of reasons. Every one of those situations needs handling ๐Ÿ˜ณ

A verifier processing a set of valid signatures has to account for all of the signers, identified by their signing keys. Only signatures from expected signers should be accepted, no matter whether the signature is cryptographically valid on its own.

A verifier processing multiple signatures on a message also has to decide what to do when one or more of them aren't valid. If a message is accepted as long as at least one signature is valid, the verifier could drop all the invalid signatures before processing the message further. On the other hand, if the verifier rejects a message because of a single invalid signature, an attacker could deny service to perfectly good messages just by injecting invalid signatures alongside the valid ones ๐Ÿฅบ

7.2.7. Collision of Application-Specific Signature Tag ๐Ÿ‘ฏโ€โ™€๏ธ

Okay so multiple apps and protocols can totally sign the same message at the same time. Honestly that's a feature, not a bug (see Section 4.3 โœจ). But a naive verifier can get super confused juggling several signatures, accepting or rejecting a message because of some signature that has nothing to do with it ๐Ÿ™ˆ. So to help an application pick out the signatures that are meant for it, the app can declare a specific value for the tag signature parameter, defined in Section 2.3 ๐Ÿท๏ธ. For example, a signature aimed at an application gateway could require tag="app-gateway" as part of its signature parameters.

Using tag doesn't stop an attacker from using the same value to target an application, since the parameter's value is public and totally unrestricted ๐Ÿ˜ฌ. So a verifier should only use a value of the tag parameter to limit which signatures it checks. Every signature still needs to be examined to make sure sufficient coverage is provided, as discussed in Section 7.2.1 ๐Ÿ”.

7.2.8. Message Content ๐Ÿ“ฆ

Heads up: on its own, this specification does not provide coverage for the content of an HTTP message under the signature, in either a request or a response ๐Ÿ™…. BUT [DIGEST] defines a set of fields that let a cryptographic digest of the content be represented in a field. Once that field is created, it can be included just like any other field as defined in Section 2.1 ๐Ÿ’–.

For example, in the following response message:

NOTE: '' line wrapping per RFC 8792

HTTP/1.1 200 OK
Content-Type: application/json

{"hello": "world"}

The digest of the content can be added to the Content-Digest field as follows:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Digest: \
  sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:

{"hello": "world"}

This field can be included in a signature base just like any other field, along with the basic signature parameters:

"@status": 200
"content-digest": \
  sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
"@signature-input": ("@status" "content-digest")

From here, the signing process proceeds as usual โœ๏ธ.

Upon verification, it is super important that the verifier validates not only the signature but also the value of the Content-Digest field itself against the actual received content ๐Ÿšจ. Otherwise, an attacker could swap out the message content, leave the Content-Digest field value untouched, and still pass the signature. Since only the field value is covered by the signature directly, checking only the signature is not enough protection against that substitution attack ๐Ÿ’€.

As [DIGEST] discusses, the value of the Content-Digest field depends on the content encoding of the message. If an intermediary changes the content encoding, the resulting Content-Digest value would change, and that would invalidate the signature. Any intermediary doing that would need to apply a new signature with the updated Content-Digest field value, similar to the reverse proxy use case in Section 4.3 ๐Ÿ”.

Apps that use the req parameter (Section 2.4) also need to be aware of its limitations ๐Ÿ™ƒ. Specifically, if a client doesn't include something like a Content-Digest header field in the request, the server is unable to include a signature that covers the request's content.


7.3. Cryptographic Considerations ๐Ÿ”

7.3.1. Cryptography and Signature Collision ๐Ÿงฉ

This document doesn't define any of its own cryptographic primitives. It relies on other specifications to define those elements. If the signature algorithm or key used to process the signature base is vulnerable to any attacks, the resulting signature will be susceptible to those same attacks too.

A common attack against signature systems is forcing a signature collision, where the same signature value successfully verifies against multiple different inputs. Since this specification relies on reconstructing the signature base from an HTTP message, and the list of components signed is fixed in the signature, it's difficult but not impossible for an attacker to pull off a collision. An attacker would need to manipulate the HTTP message and its covered message components to make the collision effective.

To counter this, only vetted keys and signature algorithms should be used to sign HTTP messages. The "HTTP Signature Algorithms" registry is one source of trusted signature algorithms for applications to apply to their messages ๐Ÿ“š.

While it's possible for an attacker to substitute the signature parameters value or the signature value separately, the signature base generation algorithm (Section 2.5) always covers the signature parameters as the final value in the signature base, using a deterministic serialization method. This step strongly binds the signature base with the signature value, which makes it much more difficult for an attacker to perform a partial substitution on the signature base ๐Ÿ”—.

7.3.2. Key Theft ๐Ÿ—๏ธ

A foundational assumption of signature-based cryptographic systems is that the signing key is not compromised by an attacker. If the keys used to sign the message are exfiltrated or stolen, the attacker will be able to generate their own signatures using those keys. As a consequence, signers have to protect any signing key material from exfiltration, capture, and use by an attacker ๐Ÿ˜ค.

To combat this, signers can rotate keys over time to limit the amount of time that stolen keys are useful. Signers can also use key escrow and storage systems to limit the attack surface against keys. Furthermore, the use of asymmetric signing algorithms exposes key material less than the use of symmetric signing algorithms (Section 7.3.3) ๐Ÿ’œ.

7.3.3. Symmetric Cryptography ๐Ÿชž

This document allows both asymmetric and symmetric cryptography to be applied to HTTP messages. By their nature, symmetric cryptographic methods require the same key material to be known by both the signer and verifier. This effectively means that a verifier is capable of generating a valid signature, since it has access to the same key material. An attacker that is able to compromise a verifier would then be able to impersonate a signer ๐Ÿ˜ฑ.

Where possible, asymmetric methods or secure key agreement mechanisms should be used to avoid this type of attack. When symmetric methods are used, distribution of the key material needs to be protected by the overall system. One technique for this is the use of separate cryptographic modules that separate the verification process (and therefore the key material) from other code, minimizing the vulnerable attack surface. Another technique is the use of key derivation functions that allow the signer and verifier to agree on unique keys for each message without having to share the key values directly ๐Ÿ”.

Additionally, if symmetric algorithms are allowed within a system, special care must be taken to avoid key downgrade attacks (Section 7.3.6) โš ๏ธ.

7.3.4. Key Specification Mixup ๐ŸŽญ

The existence of a valid signature on an HTTP message is not sufficient to prove that the message has been signed by the appropriate party. It's up to the verifier to ensure that a given key and algorithm are appropriate for the message in question. If the verifier doesn't do that step, an attacker could substitute their own signature, using their own key, on a message and force a verifier to accept and process it ๐Ÿ˜ˆ. To combat this, the verifier needs to ensure not only that the signature can be validated for a message, but also that the key and algorithm used are appropriate.

7.3.5. Non-deterministic Signature Primitives ๐ŸŽฒ

Some cryptographic primitives, such as RSA-PSS and ECDSA, have non-deterministic outputs, which include some amount of entropy within the algorithm. For such algorithms, multiple signatures generated in succession will not match. A lazy implementation of a verifier could ignore this distinction and simply check for the same value by re-signing the signature base. That implementation would work for deterministic algorithms such as HMAC and EdDSA, but it would fail to verify valid signatures made using non-deterministic algorithms. It's therefore important that a verifier always use the correctly defined verification function for the algorithm in question, and not do a simple comparison ๐Ÿง.

7.3.6. Key and Algorithm Specification Downgrades ๐Ÿ“‰

Applications of this specification need to protect against key specification downgrade attacks. For example, the same RSA key can be used for both RSA-PSS and RSA v1.5 signatures. If an application expects a key to only be used with RSA-PSS, it needs to reject signatures for any key that uses the weaker RSA 1.5 specification ๐Ÿšซ.

Another example of a downgrade attack would be when an asymmetric algorithm is expected, such as RSA-PSS, but an attacker substitutes a signature using a symmetric algorithm, such as HMAC. A naive verifier implementation could use the value of the public RSA key as the input to the HMAC verification function. Since the public key is known to the attacker, this would allow the attacker to create a valid HMAC signature against this known key. To prevent this, the verifier needs to ensure that both the key material and the algorithm are appropriate for the usage in question. Additionally, while this specification does allow runtime specification of the algorithm using the alg signature parameter, applications are encouraged to use other mechanisms, such as static configuration or a higher-protocol-level algorithm specification instead, preventing an attacker from substituting the algorithm specified ๐Ÿ›ก๏ธ.

7.3.7. Signing Signature Values โœจ

Okay bestie, this one is a total plot twist ๐Ÿ˜ญ When you use the req parameter (Section 2.4) or multiple signatures (Section 4.3) on a message, you can sign the value of an existing Signature field. So your new signature covers the bytes of the old signature output. You'd think that would transitively cover the stuff under the original signature in a checkable way, right? Nope! The attacks in [JACKSON2019] can be used to fake a signature output value on a totally unrelated message ๐Ÿ’€

Let's walk through it. Alice wants to send a signed request to Bob, and Bob wants to send back a signed response that proves he's answering Alice's incoming message ๐ŸŽ€ Meanwhile Mallory wants to intercept the traffic and swap Alice's message for her own, without Alice ever finding out ๐Ÿ‘€

  1. Alice makes a message Req_A and applies a signature Sig_A using her private key Key_A_Sign.
  2. Alice believes she's sending Req_A to Bob.
  3. Mallory intercepts Req_A and reads the value Sig_A from it.
  4. Mallory generates a different message Req_M to send to Bob instead.
  5. Mallory crafts a signing key Key_M_Sign so she can make a valid signature Sig_M over her request Req_M with it, but the byte value of Sig_M exactly equals that of Sig_A. Sneaky ๐Ÿ™ˆ
  6. Mallory sends Req_M with Sig_M to Bob.
  7. Bob validates Sig_M against Mallory's verification key Key_M_Verify. At no time does Bob think he's responding to Alice.
  8. Bob responds to Req_M with the response message Res_B and creates the signature Sig_B over it using his key Key_B_Sign. Bob includes the value of Sig_M under Sig_B's covered components, but doesn't include anything else from the request message.
  9. Mallory receives the response Res_B from Bob, including the Sig_B value. Mallory replays this response to Alice.
  10. Alice reads Res_B from Mallory and verifies Sig_B using Bob's verification key Key_B_Verify. Alice includes the bytes of her original signature Sig_A in the signature base, and the signature verifies โœ”๏ธ
  11. Alice is led to believe that Bob responded to her message, and she thinks she has cryptographic proof of it. In reality, Bob responded to Mallory's malicious request, and Alice is none the wiser ๐Ÿ˜ญ
To fix this, Bob can sign more portions of the request message than just the Signature field, so his signature can tell Alice's message apart from Mallory's. Applications using this feature, especially for non-repudiation purposes, can require that any components required in the original signature also be covered separately in the second signature. For signed messages, requiring coverage of the corresponding Signature-Input field of the first signature makes sure unique items like nonces and timestamps are covered well enough by the second signature too ๐Ÿ”

7.4. Matching Signature Parameters to the Target Message ๐ŸŽฏ

7.4.1. Modification of Required Message Parameters

Here's a sneaky denial-of-service move: an attacker can just modify a perfectly benign signature parameter or signed message component, and the service breaks ๐Ÿ˜ค Rejecting the modified message is the right call, but if signatures keep failing, the verifier might turn off signature checking just to make systems work again (see Section 7.1.1), or the application might quietly lower its requirements for the signed component. Yikes.

If these failures are common in an application, the signer and verifier should compare their generated signature bases with each other to figure out which part of the message is being modified. If an expected modification is found, the signer and verifier can agree on an alternative set of requirements that will pass. However, the signer and verifier should not drop the requirement to sign the modified component when it's suspected that an attacker is the one modifying it. Don't give up your coverage just because something is annoying ๐Ÿ’…

7.4.2. Matching Values of Covered Components to Values in the Target Message

The verifier needs to make sure the signed message components actually match the ones in the message itself. For example, the @method derived component requires that the value in the signature base be the same as the HTTP method used when presenting this message. This specification encourages this by requiring the verifier to derive the signature base from the message. But if somebody gets lazy and caches or passes along a raw signature base to a processing subsystem, downstream verifiers could end up accepting a message that doesn't match the signature it came with ๐Ÿ˜ฌ

To counter this, the component that generates the signature base needs to be trusted by both the signer and the verifier within a system ๐Ÿซถ

7.4.3. Message Component Source and Context

The signature context for deriving message component values includes the target HTTP message itself, any associated messages (like the request that triggered a response), and any additional information the signer or verifier has access to. Both signers and verifiers need to think carefully about where every piece of information comes from, and take care not to pull information from untrusted sources. Otherwise, an attacker could use a loosely defined message context to inject their own values into the signature base string, overriding or corrupting the intended values ๐Ÿ•ต๏ธโ€โ™€๏ธ

For example, in most situations the target URI of the message is as defined in [HTTP], Section 7.1. But say there's an application that needs to sign the @authority of the incoming request, and the app is sitting behind a reverse proxy. That app would expect the @authority value to change, so it could be configured to know the external target URI as the client on the other side of the proxy sees it. This application would use that configured value as its target URI for deriving message component values like @authority, instead of using the target URI of the incoming message.

This approach isn't without problems, though, because a misconfigured system could accept signed requests that were meant for different components in the system. For this scenario, an intermediary could instead add its own signature to be verified by the application directly, as shown in Section 4.3. This alternative takes a more active intermediary, but it relies less on the target application knowing external configuration values ๐Ÿงš

As another example, Section 2.4 defines a method for signing response messages and also including portions of the request message that triggered the response. In this case, the context for calculating component values is the combination of the response and request messages, not just the single message the signature is applied to. For this feature, the req flag lets both signers explicitly say which part of the context is being used to source a component identifier's value. Implementations need to make sure only the intended message is being referred to for each component. Otherwise, an attacker could try to subvert a signature by messing with one side or the other ๐Ÿ™…โ€โ™€๏ธ

7.4.4. Multiple Message Component Contexts

It's possible that the context for deriving message component values is different for each signature present in a single message. This is especially common when proxies change messages and add signatures over the changed values, alongside any existing signatures. For example, a reverse proxy can swap a public hostname in a request to a service for the hostname of the individual service host it's forwarding the request to. If both the client and the reverse proxy add signatures covering @authority, the service host will see two signatures on the request, each signing a different value for the @authority component. That's the change to that component showing up as the message travels from the client to the service host ๐Ÿ—บ๏ธ

In a setup like this, it's common for the internal service to verify only one of the signatures, or to use externally configured information as discussed in Section 7.4.3. But a verifier processing both signatures has to use a different message component context for each signature, since the @authority value will be different for each one. Verifiers like this need to know both the reverse proxy's context for incoming messages and the target service's context for messages coming from the reverse proxy. The verifier has to be extra careful to apply the correct context to the correct signature. Otherwise, an attacker who understands this complicated setup could confuse the verifier's inputs ๐ŸŒ€

Such verifiers also need to make sure that any differences in message component contexts between signatures are expected and permitted. For example, in the scenario above, the reverse proxy could include the original hostname in a Forwarded header field, and sign @authority, forwarded, and the client's entry in the Signature field. The verifier can then use the hostname from the Forwarded header field to confirm that the hostname was transformed the way it should be ๐Ÿ’–


7.5. HTTP Processing ๐ŸŒธ

7.5.1. Processing Invalid HTTP Field Names as Derived Component Names

The definition of HTTP field names doesn't allow the @ character anywhere in the name. Since every derived component name starts with @, these namespaces should be completely separate. However, some HTTP implementations aren't strict enough about which characters they accept in field names. In those implementations, a sender (or attacker) could inject a header field starting with @ and have it passed through to the application code ๐Ÿ˜ฑ These invalid header fields could override part of the derived message content and substitute an arbitrary value. That's a possible opening for an attacker to mount a signature collision attack (Section 7.3.1), or another functional substitution attack, like reusing the signature from a GET request on a crafted POST request.

To combat this, when selecting values for a message component, if the component name starts with the @ character, it needs to be processed as a derived component and never processed as an HTTP field. Only if the component name does not start with the @ character can it be taken from the fields of the message. The algorithm discussed in Section 2.5 gives a safe order of operations ๐Ÿ”’โœจ

7.5.2. Semantically Equivalent Field Values โœจ

Okay so the signature base generation algorithm (Section 2.5) uses the value of an HTTP field as its component value ๐Ÿ’… In the common case, that just means the actual bytes of the field value are the component value for both the signer and the verifier. But some field values can be transformed in semantically equivalent ways that change the bytes themselves, ngl so tricky ๐Ÿซ  For example, a field definition can declare some or all of its values to be case insensitive, or to have special handling of internal whitespace characters. Other fields have expected transformations from intermediaries, like removing comments in the Via header field. In those cases, a verifier could get tripped up by using the equivalent transformed value, which would differ from the byte value the signer used. The verifier would have a really hard time spotting this class of error, since the field value is still totally acceptable for the application, but the actual bytes required by the signature base would not match. ๐Ÿ˜ญ

When processing these fields, the signer and verifier have to agree on how to handle such transformations, if at all. One option is to not sign problematic fields, but you gotta make sure there's still sufficient signature coverage (Section 7.2.1) for the application ๐Ÿ™ˆ Another option is to define an application-specific canonicalization value for the field before it's added to the HTTP message, like always removing internal comments before signing, or always transforming values to lowercase. Since these transformations get applied before the field is used as input to the signature base generation algorithm, the signature base will still simply contain the byte value of the field as it appears within the message. If the transformations were applied after the value is extracted from the message but before it's added to the signature base, different attack surfaces, such as value substitution attacks, could be launched against the application ๐Ÿ‘€ All application-specific additional rules are outside the scope of this specification, and by their very nature these transformations would harm interoperability of the implementation outside of this specific application. It is recommended that applications avoid the use of such additional rules wherever possible. ๐ŸŒˆ

7.5.3. Parsing Structured Field Values ๐Ÿงฉ

Several parts of this specification rely on parsing Structured Field values [STRUCTURED-FIELDS]. In particular, that means strict serialization of HTTP Structured Field values (Section 2.1.1), referencing members of a Dictionary Structured Field (Section 2.1.2), and processing the @signature-input value when verifying a signature (Section 3.2). Structured Field values are designed to be relatively simple to parse, but a naive or broken implementation of such a parser could expose subtle attack surfaces.

For example, if a buggy parser of the @signature-input value doesn't enforce proper closing of quotes around string values within the list of component identifiers, an attacker could take advantage of that and inject additional content into the signature base by manipulating the Signature-Input field value on a message. ๐Ÿ˜ˆ

To counteract this, implementations should use fully compliant and trusted parsers for all Structured Field processing, on both the signer side and the verifier side. ๐Ÿ›ก๏ธ

7.5.4. HTTP Versions and Component Ambiguity ๐Ÿ”„

Some message components are expressed differently across HTTP versions. For example, the authority of the request target is sent using the Host header field in HTTP/1.1, but with the :authority pseudo-header in HTTP/2. If a signer sends an HTTP/1.1 message and signs the Host header field, but the message is translated to HTTP/2 before it reaches the verifier, the signature will not validate, as the Host header field could be dropped.

It's for this reason that HTTP message signatures define a set of derived components that each define a single way to get the value in question, like the @authority derived component (Section 2.2.3) in lieu of the Host header field. Applications should therefore prefer derived components for such options where possible. ๐Ÿ’œ

7.5.5. Canonicalization Attacks ๐Ÿ’€

Any ambiguity in the generation of the signature base could give an attacker leverage to substitute or break a signature on a message. Some message component values, particularly HTTP field values, are potentially susceptible to broken implementations that could lead to unexpected and insecure behavior. Naive implementations of this specification might implement HTTP field processing by taking the single value of a field and using it as the direct component value without processing it appropriately.

For example, if the handling of obs-fold field values doesn't remove the internal line folding and whitespace, additional newlines could be introduced into the signature base by the signer, giving an attacker a place to mount a signature collision (Section 7.3.1) attack. Alternatively, if header fields that appear multiple times aren't joined into a single string value, as required by this specification, similar attacks can be mounted, since a signed component value would show up in the signature base more than once and could be substituted or otherwise attacked that way. ๐Ÿซฃ

To counter this, the entire field value processing algorithm needs to be implemented by all implementations of signers and verifiers. ๐Ÿ”

7.5.6. Non-List Field Values ๐Ÿ“ฆ

When an HTTP field occurs multiple times in a single message, those values need to be combined into a single one-line string value to be included in the HTTP signature base, as described in Section 2.5. Not all HTTP fields can be combined into a single value this way and still be a valid value for the field. For generating the signature base, the message component value is never meant to be read back out of the signature base string or used in the application. Therefore, it's considered best practice to treat the signature base generation algorithm separately from processing the field values by the application, particularly for fields known to have this property. If the field values being signed don't validate, the signed message should also be rejected.

If an HTTP field allows unquoted commas within its values, combining multiple field values can lead to a situation where two semantically different messages produce the same line in a signature base. For example, take the following hypothetical header field with an internal comma in its syntax, here used to define two separate lists of values:

Example-Header: value, with, lots
Example-Header: of, commas

For this header field, sending all of these values as a single field value results in a single list of values:

Example-Header: value, with, lots, of, commas

Both of these messages would create the following line in the signature base:

"example-header": value, with, lots, of, commas

Since two semantically distinct inputs can create the same output in the signature base, special care has to be taken when handling such values. ๐Ÿง

Specifically, the Set-Cookie field [COOKIE] defines an internal syntax that does not conform to the List syntax provided in [STRUCTURED-FIELDS]. In particular, some portions allow unquoted commas, and the field is typically sent as multiple separate field lines with distinct values when sending multiple cookies. When multiple Set-Cookie fields are sent in the same message, it's not generally possible to combine these into a single line and still parse and use the results, as discussed in [HTTP], Section 5.3. Therefore, all the cookies need to be processed from their separate field values, without being combined, while the signature base needs to be processed from the special combined value generated solely for this purpose. If the cookie value is invalid, the signed message ought to be rejected, as this is a possible padding attack as described in Section 7.5.7. ๐Ÿช

To deal with this, an application can choose to limit signing of problematic fields like Set-Cookie, such as including the field in a signature only when a single field value is present and the results would be unambiguous. Similar caution needs to be taken with all fields that could have non-deterministic mappings into the signature base. Signers can also make use of the bs parameter to armor such fields, as described in Section 2.1.3. ๐Ÿ›ก๏ธ

7.5.7. Padding Attacks with Multiple Field Values ๐Ÿ˜ฑ

Since HTTP field values need to be combined into a single string value to be included in the HTTP signature base (see Section 2.5), it's possible for an attacker to inject an additional value for a given field and add this to the signature base of the verifier.

In most circumstances, this causes signature validation to fail as expected, since the new signature base value won't match the one the signer used to create the signature. However, it's theoretically possible for the attacker to inject both a garbage value into a field and a desired value into another field to force a particular input. This is a variation of the collision attack described in Section 7.3.1, where the attacker accomplishes their change in the message by adding to existing field values. ๐ŸŽญ

To counter this, an application needs to validate the content of the fields covered in the signature in addition to ensuring the signature itself validates. With such protections, the attacker's padding attack would be rejected by the field value processor, even in the case where the attacker could force a signature collision. ๐Ÿšซ

7.5.8. Ambiguous Handling of Query Elements ๐Ÿซข

The HTML form parameters format defined in Section 5 ("application/x-www-form-urlencoded") of [HTMLURL] is widely deployed and supported by many application frameworks. For convenience, some of these frameworks combine query parameters found in the HTTP query with those found in the message content, particularly for POST messages with a Content-Type value of "application/x-www-form-urlencoded". The @query-param derived component identifier defined in Section 2.2.8 draws its values only from the query section of the target URI of the request. As such, it would be possible for an attacker to shadow or replace query parameters in a request by overriding a signed query parameter with an unsigned form parameter, or vice versa.

To counter this, an application needs to make sure that values used for the signature base and the application are drawn from a consistent context, in this case the query component of the target URI. Additionally, when the HTTP request has content, an application should sign the message content as well, as discussed in Section 7.2.8. ๐Ÿ’–

8. Privacy Considerations ๐ŸŒธ

8.1. Identification through Keys ๐Ÿ”‘

Okay so, if a signer uses the same key with multiple verifiers, or keeps using the same key over time with one verifier, that key becomes a breadcrumb trail ๐Ÿž. Keys are supposed to be functionally unique, so seeing the same one again is a strong sign it's the same party signing all those messages. ๐Ÿ‘€

Honestly? In lots of apps this is a total vibe โœจ. It lets HTTP message signatures help authenticate the signer to the verifier, which is iconic. ๐Ÿ’… BUT it can also lead to tracking the signer never signed up for ๐Ÿ˜ญ. To dodge that, a signer can use a different key for each verifier it talks to. Sometimes a signer can also rotate its key when sending messages to a given verifier ๐Ÿ”„. These tricks don't cancel out the need for other anti-tracking techniques where they're needed, so don't stop there, bestie. ๐Ÿซถ

8.2. Signatures do not provide confidentiality ๐Ÿ™ˆ

Plot twist: HTTP message signatures do not provide confidentiality for anything they protect ๐Ÿšจ. The whole HTTP message is readable in plaintext by anyone who can access it. That includes the value of every field and the value of the signature itself. Yikes. ๐Ÿ˜ฌ

If you want confidentiality at the transport level, TLS or something equivalent can do that job ๐Ÿ”, as discussed in Section 7.1.2.

8.3. Oracles ๐Ÿ”ฎ

This one's a real balancing act โš–๏ธ. You want to give developers helpful feedback about error conditions, but you don't want to hand an attacker extra info ๐Ÿ™…โ€โ™€๏ธ. For example, a well-meaning server might try to tell you exactly which key identifier you need to request a resource. Super helpful, right? Except if someone knows who controls that key, they can link the resource's existence to that party ๐Ÿงฉ. An attacker could then use that to target the legit owner of the resource for more attacks. Not cute. ๐Ÿ’€

8.4. Required Content ๐Ÿ“ฆ

Core design tenet alert ๐Ÿšจ: every message component covered by the signature has to be available to the verifier so it can rebuild the signature base and verify the signature. So if an application says a particular field must be signed, the verifier needs access to that field's value ๐Ÿ‘€.

Here's where it gets messy ๐Ÿ˜ฉ. In complex systems with intermediary processors, this can cause surprising behavior: an intermediary might be too scared to remove privacy-sensitive information before forwarding a message, because that could break the signature ๐Ÿ™ƒ. One way out is for the intermediary to verify the signature itself, then modify the message to strip the privacy-sensitive info ๐Ÿงน. The intermediary can then add its own signature to tell the next destination that the incoming signature was validated, as shown in the example in Section 4.3. Smart, clean, iconic. ๐Ÿ‘‘

9. References ๐Ÿ“š

9.1. Normative References ๐Ÿ’œ

[ABNF]     Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax
           Specifications: ABNF", STD 68, RFC 5234,
           DOI 10.17487/RFC5234, January 2008,
           <https://www.rfc-editor.org/info/rfc5234>.

[ASCII]    Cerf, V., "ASCII format for network interchange", STD 80,
           RFC 20, DOI 10.17487/RFC0020, October 1969,
           <https://www.rfc-editor.org/info/rfc20>.

[FIPS186-5]
           NIST, "Digital Signature Standard (DSS)",
           DOI 10.6028/NIST.FIPS.186-5, February 2023,
           <https://doi.org/10.6028/NIST.FIPS.186-5>.

[HTMLURL]  WHATWG, "URL (Living Standard)", January 2024,
           <https://url.spec.whatwg.org/>.

[HTTP]     Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
           Ed., "HTTP Semantics", STD 97, RFC 9110,
           DOI 10.17487/RFC9110, June 2022,
           <https://www.rfc-editor.org/info/rfc9110>.

[HTTP/1.1] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
           Ed., "HTTP/1.1", STD 99, RFC 9112, DOI 10.17487/RFC9112,
           June 2022, <https://www.rfc-editor.org/info/rfc9112>.

[POSIX.1]  IEEE, "The Open Group Base Specifications Issue 7, 2018
           edition", 2018,
           <https://pubs.opengroup.org/onlinepubs/9699919799/>.

[RFC2104]  Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
           Hashing for Message Authentication", RFC 2104,
           DOI 10.17487/RFC2104, February 1997,
           <https://www.rfc-editor.org/info/rfc2104>.

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC6234]  Eastlake 3rd, D. and T. Hansen, "US Secure Hash Algorithms
           (SHA and SHA-based HMAC and HKDF)", RFC 6234,
           DOI 10.17487/RFC6234, May 2011,
           <https://www.rfc-editor.org/info/rfc6234>.

[RFC7517]  Jones, M., "JSON Web Key (JWK)", RFC 7517,
           DOI 10.17487/RFC7517, May 2015,
           <https://www.rfc-editor.org/info/rfc7517>.

[RFC7518]  Jones, M., "JSON Web Algorithms (JWA)", RFC 7518,
           DOI 10.17487/RFC7518, May 2015,
           <https://www.rfc-editor.org/info/rfc7518>.

[RFC8017]  Moriarty, K., Ed., Kaliski, B., Jonsson, J., and A. Rusch,
           "PKCS #1: RSA Cryptography Specifications Version 2.2",
           RFC 8017, DOI 10.17487/RFC8017, November 2016,
           <https://www.rfc-editor.org/info/rfc8017>.

[RFC8032]  Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital
           Signature Algorithm (EdDSA)", RFC 8032,
           DOI 10.17487/RFC8032, January 2017,
           <https://www.rfc-editor.org/info/rfc8032>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[STRUCTURED-FIELDS]
           Nottingham, M. and P. Kamp, "Structured Field Values for
           HTTP", RFC 8941, DOI 10.17487/RFC8941, February 2021,
           <https://www.rfc-editor.org/info/rfc8941>.

[URI]      Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
           Resource Identifier (URI): Generic Syntax", STD 66,
           RFC 3986, DOI 10.17487/RFC3986, January 2005,
           <https://www.rfc-editor.org/info/rfc3986>.

9.2. Informative References ๐Ÿฆ‹

[AWS-SIGv4]
           Amazon Simple Storage Service, "Authenticating Requests
           (AWS Signature Version 4)", March 2006,
           <https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html>.

[BCP195]   Moriarty, K. and S. Farrell, "Deprecating TLS 1.0 and TLS
           1.1", BCP 195, RFC 8996, March 2021.

           Sheffer, Y., Saint-Andre, P., and T. Fossati,
           "Recommendations for Secure Use of Transport Layer
           Security (TLS) and Datagram Transport Layer Security
           (DTLS)", BCP 195, RFC 9325, November 2022.

           <https://www.rfc-editor.org/info/bcp195>

[CLIENT-CERT]
           Campbell, B. and M. Bishop, "Client-Cert HTTP Header
           Field", RFC 9440, DOI 10.17487/RFC9440, July 2023,
           <https://www.rfc-editor.org/info/rfc9440>.

[COOKIE]   Barth, A., "HTTP State Management Mechanism", RFC 6265,
           DOI 10.17487/RFC6265, April 2011,
           <https://www.rfc-editor.org/info/rfc6265>.

[DIGEST]   Polli, R. and L. Pardue, "Digest Fields", RFC 9530,
           DOI 10.17487/RFC9530, February 2024,
           <https://www.rfc-editor.org/info/rfc9530>.

[JACKSON2019]
           Jackson, D., Cremers, C., Cohn-Gordon, K., and R. Sasse,
           "Seems Legit: Automated Analysis of Subtle Attacks on
           Protocols that Use Signatures", CCS '19: Proceedings of
           the 2019 ACM SIGSAC Conference on Computer and
           Communications Security, pp. 2165-2180,
           DOI 10.1145/3319535.3339813, November 2019,
           <https://dl.acm.org/doi/10.1145/3319535.3339813>.

[JWS]      Jones, M., Bradley, J., and N. Sakimura, "JSON Web
           Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May
           2015, <https://www.rfc-editor.org/info/rfc7515>.

[RFC7239]  Petersson, A. and M. Nilsson, "Forwarded HTTP Extension",
           RFC 7239, DOI 10.17487/RFC7239, June 2014,
           <https://www.rfc-editor.org/info/rfc7239>.

[RFC7468]  Josefsson, S. and S. Leonard, "Textual Encodings of PKIX,
           PKCS, and CMS Structures", RFC 7468, DOI 10.17487/RFC7468,
           April 2015, <https://www.rfc-editor.org/info/rfc7468>.

[RFC7807]  Nottingham, M. and E. Wilde, "Problem Details for HTTP
           APIs", RFC 7807, DOI 10.17487/RFC7807, March 2016,
           <https://www.rfc-editor.org/info/rfc7807>.

[RFC8126]  Cotton, M., Leiba, B., and T. Narten, "Guidelines for
           Writing an IANA Considerations Section in RFCs", BCP 26,
           RFC 8126, DOI 10.17487/RFC8126, June 2017,
           <https://www.rfc-editor.org/info/rfc8126>.

[RFC8792]  Watsen, K., Auerswald, E., Farrel, A., and Q. Wu,
           "Handling Long Lines in Content of Internet-Drafts and
           RFCs", RFC 8792, DOI 10.17487/RFC8792, June 2020,
           <https://www.rfc-editor.org/info/rfc8792>.

[RFC9457]  Nottingham, M., Wilde, E., and S. Dalal, "Problem Details
           for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, July 2023,
           <https://www.rfc-editor.org/info/rfc9457>.

[SIGNING-HTTP-MESSAGES]
           Cavage, M. and M. Sporny, "Signing HTTP Messages", Work in
           Progress, Internet-Draft, draft-cavage-http-signatures-12,
           21 October 2019, <https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12>.

[SIGNING-HTTP-REQS-OAUTH]
           Richer, J., Ed., Bradley, J., and H. Tschofenig, "A Method
           for Signing HTTP Requests for OAuth", Work in Progress,
           Internet-Draft, draft-ietf-oauth-signed-http-request-03, 8
           August 2016, <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03>.

[TLS]      Rescorla, E., "The Transport Layer Security (TLS) Protocol
           Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
           <https://www.rfc-editor.org/info/rfc8446>.

Appendix A. Detecting HTTP Message Signatures ๐Ÿ•ต๏ธโ€โ™€๏ธ

Okay, so people have tried to make signed HTTP messages before, loads of times ๐Ÿ™ˆ. Some of those attempts were non-standardized definitions of the Signature field this spec uses too. The recommendation is that developers who want to support this specification, other published documents, or other historical drafts do it carefully and deliberately ๐Ÿง. Incompatibilities between this spec and other documents, or between different versions of other drafts, could lead to unexpected problems. Nobody wants that drama ๐Ÿ˜ญ.

It is recommended that implementors first detect and validate the Signature-Input field defined in this specification. That way you know the mechanism described here is actually in use and not some alternative ๐Ÿ•ต๏ธ. If the Signature-Input field is present, then all Signature fields can be parsed and interpreted in the context of this specification. Main character energy, literally. ๐Ÿ’–๐Ÿฆ‹โœจ

Appendix B. Examples โœจ

Okay bestie, this appendix is the testing playground ๐Ÿงช๐ŸŽ€ These examples are non-normative, which means they're here to help you test your HTTP message signature implementation. Signed messages are included so you can build the signature base with the stated parameters, then make signatures using the listed algorithms and keys ๐Ÿ’…

The private keys can generate signatures, but some of the algorithms are non-deterministic, so your signature bytes won't match the examples exactly. That's expected, not a bug ๐Ÿ™ˆ The public keys can validate every signed example, though โœจ

B.1. Example Keys

This section has the cryptographic keys used in the example signatures all through this document ๐Ÿ”‘ These keys MUST NOT be used for any purpose other than testing. Please don't put them in production, bestie ๐Ÿ’€

The key identifiers are used throughout the examples. We assume the signer and verifier can unambiguously look up every key identifier here, and that the keys and algorithms fit the context where the signature shows up ๐Ÿซถ

The components for each private key are in PEM format [RFC7468]. You can display them with this OpenSSL command:

openssl pkey -text

This command was tested with all the example keys on OpenSSL version 1.1.1m. Heads up: some systems can't make or use all of these keys directly and might need extra processing ๐Ÿง  All of the keys are also available in JWK format.

B.1.1. Example RSA Key

This is a 2048-bit RSA public and private key pair, which this document calls test-key-rsa. It's PEM encoded with no encryption ๐ŸŒธ

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAhAKYdtoeoy8zcAcR874L8cnZxKzAGwd7v36APp7Pv6Q2jdsPBRrw
WEBnez6d0UDKDwGbc6nxfEXAy5mbhgajzrw3MOEt8uA5txSKobBpKDeBLOsdJKFq
MGmXCQvEG7YemcxDTRPxAleIAgYYRjTSd/QBwVW9OwNFhekro3RtlinV0a75jfZg
kne/YiktSvLG34lw2zqXBDTC5NHROUqGTlML4PlNZS5Ri2U4aCNx2rUPRcKIlE0P
uKxI4T+HIaFpv8+rdV6eUgOrB2xeI1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZSFlQ
PSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ10wIDAQAB
-----END RSA PUBLIC KEY-----

-----BEGIN RSA PRIVATE KEY-----
MIIEqAIBAAKCAQEAhAKYdtoeoy8zcAcR874L8cnZxKzAGwd7v36APp7Pv6Q2jdsP
BRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbhgajzrw3MOEt8uA5txSKobBpKDeBLOsd
JKFqMGmXCQvEG7YemcxDTRPxAleIAgYYRjTSd/QBwVW9OwNFhekro3RtlinV0a75
jfZgkne/YiktSvLG34lw2zqXBDTC5NHROUqGTlML4PlNZS5Ri2U4aCNx2rUPRcKI
lE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZ
SFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ10wIDAQABAoIBAG/JZuSWdoVHbi56
vjgCgkjg3lkO1KrO3nrdm6nrgA9P9qaPjxuKoWaKO1cBQlE1pSWp/cKncYgD5WxE
CpAnRUXG2pG4zdkzCYzAh1i+c34L6oZoHsirK6oNcEnHveydfzJL5934egm6p8DW
+m1RQ70yUt4uRc0YSor+q1LGJvGQHReF0WmJBZHrhz5e63Pq7lE0gIwuBqL8SMaA
yRXtK+JGxZpImTq+NHvEWWCu09SCq0r838ceQI55SvzmTkwqtC+8AT2zFviMZkKR
Qo6SPsrqItxZWRty2izawTF0Bf5S2VAx7O+6t3wBsQ1sLptoSgX3QblELY5asI0J
YFz7LJECgYkAsqeUJmqXE3LP8tYoIjMIAKiTm9o6psPlc8CrLI9CH0UbuaA2JCOM
cCNq8SyYbTqgnWlB9ZfcAm/cFpA8tYci9m5vYK8HNxQr+8FS3Qo8N9RJ8d0U5Csw
DzMYfRghAfUGwmlWj5hp1pQzAuhwbOXFtxKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1
mwJ5AL0pYF0G7x81prlARURwHo0Yf52kEw1dxpx+JXER7hQRWQki5/NsUEtv+8RT
qn2m6qte5DXLyn83b1qRscSdnCCwKtKWUug5q2ZbwVOCJCtmRwmnP131lWRYfj67
B/xJ1ZA6X3GEf4sNReNAtaucPEelgR2nsN0gKQKBiGoqHWbK1qYvBxX2X3kbPDkv
9C+celgZd2PW7aGYLCHq7nPbmfDV0yHcWjOhXZ8jRMjmANVR/eLQ2EfsRLdW69bn
f3ZD7JS1fwGnO3exGmHO3HZG+6AvberKYVYNHahNFEw5TsAcQWDLRpkGybBcxqZo
81YCqlqidwfeO5YtlO7etx1xLyqa2NsCeG9A86UjG+aeNnXEIDk1PDK+EuiThIUa
/2IxKzJKWl1BKr2d4xAfR0ZnEYuRrbeDQYgTImOlfW6/GuYIxKYgEKCFHFqJATAG
IxHrq1PDOiSwXd2GmVVYyEmhZnbcp8CxaEMQoevxAta0ssMK3w6UsDtvUvYvF22m
qQKBiD5GwESzsFPy3Ga0MvZpn3D6EJQLgsnrtUPZx+z2Ep2x0xc5orneB5fGyF1P
WtP+fG5Q6Dpdz3LRfm+KwBCWFKQjg7uTxcjerhBWEYPmEMKYwTJF5PBG9/ddvHLQ
EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRDs15M38eG2cYwB1PZpDHScDnDA0=
-----END RSA PRIVATE KEY-----

Here's the same public and private key pair in JWK format ๐ŸŽ€

NOTE: '\' line wrapping per RFC 8792

{
  "kty": "RSA",
  "kid": "test-key-rsa",
  "p": "sqeUJmqXE3LP8tYoIjMIAKiTm9o6psPlc8CrLI9CH0UbuaA2JCOMcCNq8Sy\
  YbTqgnWlB9ZfcAm_cFpA8tYci9m5vYK8HNxQr-8FS3Qo8N9RJ8d0U5CswDzMYfRgh\
  AfUGwmlWj5hp1pQzAuhwbOXFtxKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1mw",
  "q": "vSlgXQbvHzWmuUBFRHAejRh_naQTDV3GnH4lcRHuFBFZCSLn82xQS2_7xFO\
  qfabqq17kNcvKfzdvWpGxxJ2cILAq0pZS6DmrZlvBU4IkK2ZHCac_XfWVZFh-PrsH\
  _EnVkDpfcYR_iw1F40C1q5w8R6WBHaew3SAp",
  "d": "b8lm5JZ2hUduLnq-OAKCSODeWQ7Uqs7eet2bqeuAD0_2po-PG4qhZoo7VwF\
  CUTWlJan9wqdxiAPlbEQKkCdFRcbakbjN2TMJjMCHWL5zfgvqhmgeyKsrqg1wSce9\
  7J1_Mkvn3fh6CbqnwNb6bVFDvTJS3i5FzRhKiv6rUsYm8ZAdF4XRaYkFkeuHPl7rc\
  -ruUTSAjC4GovxIxoDJFe0r4kbFmkiZOr40e8RZYK7T1IKrSvzfxx5AjnlK_OZOTC\
  q0L7wBPbMW-IxmQpFCjpI-yuoi3FlZG3LaLNrBMXQF_lLZUDHs77q3fAGxDWwum2h\
  KBfdBuUQtjlqwjQlgXPsskQ",
  "e": "AQAB",
  "qi": "PkbARLOwU_LcZrQy9mmfcPoQlAuCyeu1Q9nH7PYSnbHTFzmiud4Hl8bIXU\
  9a0_58blDoOl3PctF-b4rAEJYUpCODu5PFyN6uEFYRg-YQwpjBMkXk8Eb39128ctA\
  RB40Lx8caDhRdTyaEedIG3cQDXSpAl9EOzXkzfx4bZxjAHU9mkMdJwOcMDQ",
  "dp": "aiodZsrWpi8HFfZfeRs8OS_0L5x6WBl3Y9btoZgsIeruc9uZ8NXTIdxaM6\
  FdnyNEyOYA1VH94tDYR-xEt1br1ud_dkPslLV_Aac7d7EaYc7cdkb7oC9t6sphVg0\
  dqE0UTDlOwBxBYMtGmQbJsFzGpmjzVgKqWqJ3B947li2U7t63HXEvKprY2w",
  "dq": "b0DzpSMb5p42dcQgOTU8Mr4S6JOEhRr_YjErMkpaXUEqvZ3jEB9HRmcRi5\
  Gtt4NBiBMiY6V9br8a5gjEpiAQoIUcWokBMAYjEeurU8M6JLBd3YaZVVjISaFmdty\
  nwLFoQxCh6_EC1rSywwrfDpSwO29S9i8Xbaap",
  "n": "hAKYdtoeoy8zcAcR874L8cnZxKzAGwd7v36APp7Pv6Q2jdsPBRrwWEBnez6\
  d0UDKDwGbc6nxfEXAy5mbhgajzrw3MOEt8uA5txSKobBpKDeBLOsdJKFqMGmXCQvE\
  G7YemcxDTRPxAleIAgYYRjTSd_QBwVW9OwNFhekro3RtlinV0a75jfZgkne_YiktS\
  vLG34lw2zqXBDTC5NHROUqGTlML4PlNZS5Ri2U4aCNx2rUPRcKIlE0PuKxI4T-HIa\
  Fpv8-rdV6eUgOrB2xeI1dSFFn_nnv5OoZJEIB-VmuKn3DCUcCZSFlQPSXSfBDiUGh\
  wOw76WuSSsf1D4b_vLoJ10w"
}

B.1.2. Example RSA-PSS Key

Next up is another 2048-bit RSA public and private key pair, this one called test-key-rsa-pss ๐Ÿฆ‹ This key is PKCS #8 encoded in PEM format with no encryption ๐ŸŒˆ

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr4tmm3r20Wd/PbqvP1s2
+QEtvpuRaV8Yq40gjUR8y2Rjxa6dpG2GXHbPfvMs8ct+Lh1GH45x28Rw3Ry53mm+
oAXjyQ86OnDkZ5N8lYbggD4O3w6M6pAvLkhk95AndTrifbIFPNU8PPMO7OyrFAHq
gDsznjPFmTOtCEcN2Z1FpWgchwuYLPL+Wokqltd11nqqzi+bJ9cvSKADYdUAAN5W
Utzdpiy6LbTgSxP7ociU4Tn0g5I6aDZJ7A8Lzo0KSyZYoA485mqcO0GVAdVw9lq4
aOT9v6d+nb4bnNkQVklLQ3fVAvJm+xdDOp9LCNCN48V2pnDOkFV6+U9nV5oyc6XI
2wIDAQAB
-----END PUBLIC KEY-----

-----BEGIN PRIVATE KEY-----
MIIEvgIBADALBgkqhkiG9w0BAQoEggSqMIIEpgIBAAKCAQEAr4tmm3r20Wd/Pbqv
P1s2+QEtvpuRaV8Yq40gjUR8y2Rjxa6dpG2GXHbPfvMs8ct+Lh1GH45x28Rw3Ry5
3mm+oAXjyQ86OnDkZ5N8lYbggD4O3w6M6pAvLkhk95AndTrifbIFPNU8PPMO7Oyr
FAHqgDsznjPFmTOtCEcN2Z1FpWgchwuYLPL+Wokqltd11nqqzi+bJ9cvSKADYdUA
AN5WUtzdpiy6LbTgSxP7ociU4Tn0g5I6aDZJ7A8Lzo0KSyZYoA485mqcO0GVAdVw
9lq4aOT9v6d+nb4bnNkQVklLQ3fVAvJm+xdDOp9LCNCN48V2pnDOkFV6+U9nV5oy
c6XI2wIDAQABAoIBAQCUB8ip+kJiiZVKF8AqfB/aUP0jTAqOQewK1kKJ/iQCXBCq
pbo360gvdt05H5VZ/RDVkEgO2k73VSsbulqezKs8RFs2tEmU+JgTI9MeQJPWcP6X
aKy6LIYs0E2cWgp8GADgoBs8llBq0UhX0KffglIeek3n7Z6Gt4YFge2TAcW2WbN4
XfK7lupFyo6HHyWRiYHMMARQXLJeOSdTn5aMBP0PO4bQyk5ORxTUSeOciPJUFktQ
HkvGbym7KryEfwH8Tks0L7WhzyP60PL3xS9FNOJi9m+zztwYIXGDQuKM2GDsITeD
2mI2oHoPMyAD0wdI7BwSVW18p1h+jgfc4dlexKYRAoGBAOVfuiEiOchGghV5vn5N
RDNscAFnpHj1QgMr6/UG05RTgmcLfVsI1I4bSkbrIuVKviGGf7atlkROALOG/xRx
DLadgBEeNyHL5lz6ihQaFJLVQ0u3U4SB67J0YtVO3R6lXcIjBDHuY8SjYJ7Ci6Z6
vuDcoaEujnlrtUhaMxvSfcUJAoGBAMPsCHXte1uWNAqYad2WdLjPDlKtQJK1diCm
rqmB2g8QE99hDOHItjDBEdpyFBKOIP+NpVtM2KLhRajjcL9Ph8jrID6XUqikQuVi
4J9FV2m42jXMuioTT13idAILanYg8D3idvy/3isDVkON0X3UAVKrgMEne0hJpkPL
FYqgetvDAoGBAKLQ6JZMbSe0pPIJkSamQhsehgL5Rs51iX4m1z7+sYFAJfhvN3Q/
OGIHDRp6HjMUcxHpHw7U+S1TETxePwKLnLKj6hw8jnX2/nZRgWHzgVcY+sPsReRx
NJVf+Cfh6yOtznfX00p+JWOXdSY8glSSHJwRAMog+hFGW1AYdt7w80XBAoGBAImR
NUugqapgaEA8TrFxkJmngXYaAqpA0iYRA7kv3S4QavPBUGtFJHBNULzitydkNtVZ
3w6hgce0h9YThTo/nKc+OZDZbgfN9s7cQ75x0PQCAO4fx2P91Q+mDzDUVTeG30mE
t2m3S0dGe47JiJxifV9P3wNBNrZGSIF3mrORBVNDAoGBAI0QKn2Iv7Sgo4T/XjND
dl2kZTXqGAk8dOhpUiw/HdM3OGWbhHj2NdCzBliOmPyQtAr770GITWvbAI+IRYyF
S7Fnk6ZVVVHsxjtaHy1uJGFlaZzKR4AGNaUTOJMs6NadzCmGPAxNQQOCqoUjn4XR
rOjr9w349JooGXhOxbu8nOxX
-----END PRIVATE KEY-----

And the same public and private key pair in JWK format ๐Ÿ’œ

NOTE: '\' line wrapping per RFC 8792

{
  "kty": "RSA",
  "kid": "test-key-rsa-pss",
  "p": "5V-6ISI5yEaCFXm-fk1EM2xwAWekePVCAyvr9QbTlFOCZwt9WwjUjhtKRus\
  i5Uq-IYZ_tq2WRE4As4b_FHEMtp2AER43IcvmXPqKFBoUktVDS7dThIHrsnRi1U7d\
  HqVdwiMEMe5jxKNgnsKLpnq-4NyhoS6OeWu1SFozG9J9xQk",
  "q": "w-wIde17W5Y0Cphp3ZZ0uM8OUq1AkrV2IKauqYHaDxAT32EM4ci2MMER2nI\
  UEo4g_42lW0zYouFFqONwv0-HyOsgPpdSqKRC5WLgn0VXabjaNcy6KhNPXeJ0Agtq\
  diDwPeJ2_L_eKwNWQ43RfdQBUquAwSd7SEmmQ8sViqB628M",
  "d": "lAfIqfpCYomVShfAKnwf2lD9I0wKjkHsCtZCif4kAlwQqqW6N-tIL3bdOR-\
  VWf0Q1ZBIDtpO91UrG7pansyrPERbNrRJlPiYEyPTHkCT1nD-l2isuiyGLNBNnFoK\
  fBgA4KAbPJZQatFIV9Cn34JSHnpN5-2ehreGBYHtkwHFtlmzeF3yu5bqRcqOhx8lk\
  YmBzDAEUFyyXjknU5-WjAT9DzuG0MpOTkcU1EnjnIjyVBZLUB5Lxm8puyq8hH8B_E\
  5LNC-1oc8j-tDy98UvRTTiYvZvs87cGCFxg0LijNhg7CE3g9piNqB6DzMgA9MHSOw\
  cElVtfKdYfo4H3OHZXsSmEQ",
  "e": "AQAB",
  "qi": "jRAqfYi_tKCjhP9eM0N2XaRlNeoYCTx06GlSLD8d0zc4ZZuEePY10LMGWI\
  6Y_JC0CvvvQYhNa9sAj4hFjIVLsWeTplVVUezGO1ofLW4kYWVpnMpHgAY1pRM4kyz\
  o1p3MKYY8DE1BA4KqhSOfhdGs6Ov3Dfj0migZeE7Fu7yc7Fc",
  "dp": "otDolkxtJ7Sk8gmRJqZCGx6GAvlGznWJfibXPv6xgUAl-G83dD84YgcNGn\
  oeMxRzEekfDtT5LVMRPF4_AoucsqPqHDyOdfb-dlGBYfOBVxj6w-xF5HE0lV_4J-H\
  rI63Od9fTSn4lY5d1JjyCVJIcnBEAyiD6EUZbUBh23vDzRcE",
  "dq": "iZE1S6CpqmBoQDxOsXGQmaeBdhoCqkDSJhEDuS_dLhBq88FQa0UkcE1QvO\
  K3J2Q21VnfDqGBx7SH1hOFOj-cpz45kNluB832ztxDvnHQ9AIA7h_HY_3VD6YPMNR\
  VN4bfSYS3abdLR0Z7jsmInGJ9X0_fA0E2tkZIgXeas5EFU0M",
  "n": "r4tmm3r20Wd_PbqvP1s2-QEtvpuRaV8Yq40gjUR8y2Rjxa6dpG2GXHbPfvM\
  s8ct-Lh1GH45x28Rw3Ry53mm-oAXjyQ86OnDkZ5N8lYbggD4O3w6M6pAvLkhk95An\
  dTrifbIFPNU8PPMO7OyrFAHqgDsznjPFmTOtCEcN2Z1FpWgchwuYLPL-Wokqltd11\
  nqqzi-bJ9cvSKADYdUAAN5WUtzdpiy6LbTgSxP7ociU4Tn0g5I6aDZJ7A8Lzo0KSy\
  ZYoA485mqcO0GVAdVw9lq4aOT9v6d-nb4bnNkQVklLQ3fVAvJm-xdDOp9LCNCN48V\
  2pnDOkFV6-U9nV5oyc6XI2w"
}

B.1.3. Example ECC P-256 Test Key

Last but not least, this is a public and private elliptical curve key pair over the curve P-256 ๐ŸŒŸ It's called test-key-ecc-p256 here, and it's PEM encoded with no encryption ๐Ÿงš

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqIVYZVLCrPZHGHjP17CTW0/+D9Lf
w0EkjqF7xB4FivAxzic30tMM4GF+hR6Dxh71Z50VGGdldkkDXZCnTNnoXQ==
-----END PUBLIC KEY-----

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIFKbhfNZfpDsW43+0+JjUr9K+bTeuxopu653+hBaXGA7oAoGCCqGSM49
AwEHoUQDQgAEqIVYZVLCrPZHGHjP17CTW0/+D9Lfw0EkjqF7xB4FivAxzic30tMM
4GF+hR6Dxh71Z50VGGdldkkDXZCnTNnoXQ==
-----END EC PRIVATE KEY-----

The same public and private key pair in JWK format ๐Ÿซถ

{
  "kty": "EC",
  "crv": "P-256",
  "kid": "test-key-ecc-p256",
  "d": "UpuF81l-kOxbjf7T4mNSv0r5tN67Gim7rnf6EFpcYDs",
  "x": "qIVYZVLCrPZHGHjP17CTW0_-D9Lfw0EkjqF7xB4FivA",
  "y": "Mc4nN9LTDOBhfoUeg8Ye9WedFRhnZXZJA12Qp0zZ6F0"
}

B.1.4. Example Ed25519 Test Key

Okay bestie, this one is an elliptical curve key over the Edwards curve ed25519 ๐Ÿฆ‹. In this doc we're calling it test-key-ed25519. It's PKCS #8 encoded in PEM format with no encryption ๐Ÿ”, so no password drama ๐Ÿ’€.

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
-----END PUBLIC KEY-----

-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
-----END PRIVATE KEY-----

The same public and private key pair in JWK format, which is giving neat and tidy ๐ŸŽ€:

{
  "kty": "OKP",
  "crv": "Ed25519",
  "kid": "test-key-ed25519",
  "d": "n4Ni-HpISpVObnQMW0wOhCKROaIKqKtW_2ZYb2p9KcU",
  "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"
}

B.1.5. Example Shared Secret

Here's our shared secret ๐Ÿคซ. It's 64 randomly generated bytes encoded in Base64, and this doc calls it test-shared-secret.

NOTE: '\' line wrapping per RFC 8792

uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBt\
  bmHhIDi6pcl8jsasjlTMtDQ==

B.2. Test Cases โœจ

This section gives non-normative examples ๐Ÿงช. You can use them as test cases to check that your implementation is correct. They're all built on the following HTTP messages.

For requests, we use this test-request message:

NOTE: '\' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Content-Length: 18

{"hello": "world"}

For responses, we use this test-response message:

NOTE: '\' line wrapping per RFC 8792

HTTP/1.1 200 OK
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Digest: sha-512=:mEWXIS7MaLRuGgxOBdODa3xqM1XdEvxoYhvlCFJ41Q\
  JgJc4GTsPp29l5oGX69wWdXymyU0rjJuahq4l5aGgfLQ==:
Content-Length: 23

{"message": "good dog"}

B.2.1. Minimal Signature Using rsa-pss-sha512 ๐ŸŒธ

This one is a minimal signature using the rsa-pss-sha512 algorithm over test-request. It doesn't cover any of the components of the HTTP message ๐Ÿ™ˆ. It does give you a timestamped signature that proves possession of the key, plus a nonce from the signer.

The corresponding signature base is:

NOTE: '\' line wrapping per RFC 8792

"@signature-params": ();created=1618884473;keyid="test-key-rsa-pss"\
  ;nonce="b3k2pp5k7z-50gnwp.yemd"

This gives you the following Signature-Input and Signature header fields, added to the message under the signature label sig-b21:

NOTE: '\' line wrapping per RFC 8792

Signature-Input: sig-b21=();created=1618884473\
  ;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd"
Signature: sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopem\
  LJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG\
  52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx\
  2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6\
  UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3\
  +7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:
Heads up: since the covered components list is empty, an attacker could apply this signature to an unrelated HTTP message. The nonce parameter is there to stop the same signature being replayed more than once. But if an attacker intercepts the signature and stops it reaching the verifier, they could apply it to another message. So using an empty covered components set is discouraged. See Section 7.2.1 for more discussion. ๐Ÿ’€

The RSA-PSS algorithm used here is non-deterministic ๐ŸŽฒ, so you get a different signature value every time you run it. The signature value shown here can be validated against the given keys. Newly generated signature values aren't expected to match the example. See Section 7.3.5.


B.2.2. Selective Covered Components Using rsa-pss-sha512 ๐ŸŽ€

This example covers some extra components in test-request using the rsa-pss-sha512 algorithm: the authority, the Content-Digest header field, and one named query parameter ๐Ÿ’…. It also adds a tag parameter with the application-specific value header-example.

The corresponding signature base is:

NOTE: '\' line wrapping per RFC 8792

"@authority": example.com
"content-digest": sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX\
  +TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
"@query-param";name="Pet": dog
"@signature-params": ("@authority" "content-digest" \
  "@query-param";name="Pet")\
  ;created=1618884473;keyid="test-key-rsa-pss"\
  ;tag="header-example"

This gives you the following Signature-Input and Signature header fields, added to the message under the label sig-b22:

NOTE: '\' line wrapping per RFC 8792

Signature-Input: sig-b22=("@authority" "content-digest" \
  "@query-param";name="Pet");created=1618884473\
  ;keyid="test-key-rsa-pss";tag="header-example"
Signature: sig-b22=:LjbtqUbfmvjj5C5kr1Ugj4PmLYvx9wVjZvD9GsTT4F7GrcQ\
  EdJzgI9qHxICagShLRiLMlAJjtq6N4CDfKtjvuJyE5qH7KT8UCMkSowOB4+ECxCmT\
  8rtAmj/0PIXxi0A0nxKyB09RNrCQibbUjsLS/2YyFYXEu4TRJQzRw1rLEuEfY17SA\
  RYhpTlaqwZVtR8NV7+4UKkjqpcAoFqWFQh62s7Cl+H2fjBSpqfZUJcsIk4N6wiKYd\
  4je2U/lankenQ99PZfB4jY3I5rSV2DSBVkSFsURIjYErOs0tFTQosMTAoxk//0RoK\
  UqiYY8Bh0aaUEb0rQl3/XaVe4bXTugEjHSw==:

The RSA-PSS algorithm used here is non-deterministic ๐ŸŽฒ, so a different signature value comes out every time it runs. The signature value shown can be validated against the given keys. Newly generated values aren't expected to match the example. See Section 7.3.5.


B.2.3. Full Coverage Using rsa-pss-sha512 ๐Ÿฆ‹

This example covers every applicable message component in test-request, including the content type and length, plus a bunch of derived components ๐Ÿคฉ. It again uses the rsa-pss-sha512 algorithm. The Host header field isn't covered, because the @authority derived component is included instead.

The corresponding signature base is:

NOTE: '\' line wrapping per RFC 8792

"date": Tue, 20 Apr 2021 02:07:55 GMT
"@method": POST
"@path": /foo
"@query": ?param=Value&Pet=dog
"@authority": example.com
"content-type": application/json
"content-digest": sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX\
  +TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
"content-length": 18
"@signature-params": ("date" "@method" "@path" "@query" \
  "@authority" "content-type" "content-digest" "content-length")\
  ;created=1618884473;keyid="test-key-rsa-pss"

This gives you the following Signature-Input and Signature header fields, added to the message under the label sig-b23:

NOTE: '\' line wrapping per RFC 8792

Signature-Input: sig-b23=("date" "@method" "@path" "@query" \
  "@authority" "content-type" "content-digest" "content-length")\
  ;created=1618884473;keyid="test-key-rsa-pss"
Signature: sig-b23=:bbN8oArOxYoyylQQUU6QYwrTuaxLwjAC9fbY2F6SVWvh0yB\
  iMIRGOnMYwZ/5MR6fb0Kh1rIRASVxFkeGt683+qRpRRU5p2voTp768ZrCUb38K0fU\
  xN0O0iC59DzYx8DFll5GmydPxSmme9v6ULbMFkl+V5B1TP/yPViV7KsLNmvKiLJH1\
  pFkh/aYA2HXXZzNBXmIkoQoLd7YfW91kE9o/CCoC1xMy7JA1ipwvKvfrs65ldmlu9\
  bpG6A9BmzhuzF8Eim5f8ui9eH8LZH896+QIF61ka39VBrohr9iyMUJpvRX2Zbhl5Z\
  JzSRxpJyoEZAFL2FUo5fTIztsDZKEgM4cUA==:

Note that the value of the Date header field and the value of the created signature parameter don't have to match ๐Ÿ•ฐ๏ธ. The Date header field gets added when the HTTP message is created, and the created parameter gets filled in when the signature over that message is created. Those two times can differ. If the Date header field is covered by the signature, it's up to the verifier to decide whether its value has to match the created parameter. See Section 7.2.4 for more discussion.

The RSA-PSS algorithm used here is non-deterministic ๐ŸŽฒ, so a different signature value comes out every time it runs. The signature value shown can be validated against the given keys, but newly generated values aren't expected to match the example. See Section 7.3.5.


B.2.4. Signing a Response Using ecdsa-p256-sha256 ๐ŸŒˆ

This example covers parts of the test-response message using the ecdsa-p256-sha256 algorithm and the key test-key-ecc-p256 ๐Ÿ”‘.

The corresponding signature base is:

NOTE: '\' line wrapping per RFC 8792

"@status": 200
"content-type": application/json
"content-digest": sha-512=:mEWXIS7MaLRuGgxOBdODa3xqM1XdEvxoYhvlCFJ4\
  1QJgJc4GTsPp29l5oGX69wWdXymyU0rjJuahq4l5aGgfLQ==:
"content-length": 23
"@signature-params": ("@status" "content-type" "content-digest" \
  "content-length");created=1618884473;keyid="test-key-ecc-p256"

This gives you the following Signature-Input and Signature header fields, added to the message under the label sig-b24:

NOTE: '\' line wrapping per RFC 8792

Signature-Input: sig-b24=("@status" "content-type" \
  "content-digest" "content-length");created=1618884473\
  ;keyid="test-key-ecc-p256"
Signature: sig-b24=:wNmSUAhwb5LxtOtOpNa6W5xj067m5hFrj0XQ4fvpaCLx0NK\
  ocgPquLgyahnzDnDAUy5eCdlYUEkLIj+32oiasw==:

The ECDSA signature algorithm used here is non-deterministic ๐ŸŽฒ, so you get a different signature value every time it runs. The signature value shown can be validated against the given keys, but newly generated signature values aren't expected to match the example. See Section 7.3.5.


B.2.5. Signing a Request Using hmac-sha256 ๐Ÿ”

This example covers parts of the test-request message using the hmac-sha256 algorithm and the secret test-shared-secret.

The corresponding signature base is:

NOTE: '\' line wrapping per RFC 8792

"date": Tue, 20 Apr 2021 02:07:55 GMT
"@authority": example.com
"content-type": application/json
"@signature-params": ("date" "@authority" "content-type")\
  ;created=1618884473;keyid="test-shared-secret"

This gives you the following Signature-Input and Signature header fields, added to the message under the label sig-b25:

NOTE: '\' line wrapping per RFC 8792

Signature-Input: sig-b25=("date" "@authority" "content-type")\
  ;created=1618884473;keyid="test-shared-secret"
Signature: sig-b25=:pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8=:
Before you use symmetric signatures for real, check out the security trade-offs in Section 7.3.3. Seriously, read it first ๐Ÿ™.

B.2.6. Signing a Request Using ed25519 ๐Ÿ’œ

This example covers parts of the test-request message using the Ed25519 algorithm and the key test-key-ed25519 โœจ.

The corresponding signature base is:

NOTE: '\' line wrapping per RFC 8792

"date": Tue, 20 Apr 2021 02:07:55 GMT
"@method": POST
"@path": /foo
"@authority": example.com
"content-type": application/json
"content-length": 18
"@signature-params": ("date" "@method" "@path" "@authority" \
  "content-type" "content-length");created=1618884473\
  ;keyid="test-key-ed25519"

This gives you the following Signature-Input and Signature header fields, added to the message under the label sig-b26:

NOTE: '\' line wrapping per RFC 8792

Signature-Input: sig-b26=("date" "@method" "@path" "@authority" \
  "content-type" "content-length");created=1618884473\
  ;keyid="test-key-ed25519"
Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1\
  u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:

B.3. TLS-Terminating Proxies

okay so this one is giving reverse proxy glow-up ๐Ÿ’…โœจ there's a TLS-terminating reverse proxy sitting in front of the resource, and the client doesn't sign the request at all ๐Ÿ™ˆ instead it uses mutual TLS to make its call. the terminating proxy validates the TLS stream and injects a Client-Cert header field according to [CLIENT-CERT], and then applies a signature to that field. bestie, signing that header is the whole move ๐Ÿซถ because now the proxy can attest to its own validation of the original request's TLS parameters, AND it can authenticate itself to the backend independently of whatever the client did. iconic. ๐Ÿ‘‘

the client makes this request to the TLS-terminating proxy over mutual TLS:

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Length: 18

{"hello": "world"}

the proxy processes the TLS connection, pulls the client's TLS certificate out into a Client-Cert header field, and passes it along to the internal service hosted at service.internal.example. the result is this unsigned request:

NOTE: '' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: service.internal.example
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Length: 18
Client-Cert: :MIIBqDCCAU6gAwIBAgIBBzAKBggqhkjOPQQDAjA6MRswGQYDVQQKD\
  BJMZXQncyBBdXRoZW50aWNhdGUxGzAZBgNVBAMMEkxBIEludGVybWVkaWF0ZSBDQT\
  AeFw0yMDAxMTQyMjU1MzNaFw0yMTAxMjMyMjU1MzNaMA0xCzAJBgNVBAMMAkJDMFk\
  wEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8YnXXfaUgmnMtOXU/IncWalRhebrXmck\
  C8vdgJ1p5Be5F/3YC8OthxM4+k1M6aEAEFcGzkJiNy6J84y7uzo9M6NyMHAwCQYDV\
  R0TBAIwADAfBgNVHSMEGDAWgBRm3WjLa38lbEYCuiCPct0ZaSED2DAOBgNVHQ8BAf\
  8EBAMCBsAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0RAQH/BBMwEYEPYmRjQGV\
  4YW1wbGUuY29tMAoGCCqGSM49BAMCA0gAMEUCIBHda/r1vaL6G3VliL4/Di6YK0Q6\
  bMjeSkC3dFCOOB8TAiEAx/kHSB4urmiZ0NX5r5XarmPk0wmuydBVoU4hBVZ1yhk=:

{"hello": "world"}

so without a signature, the internal service would just have to trust that the incoming connection has the right info ๐Ÿ˜ฌ but once the Client-Cert field and other parts of the internal request are signed, the internal service can be sure the correct party (the trusted proxy) processed the request and handed it to the correct service. ๐ŸŽ€ the proxy's signature base is:

NOTE: '' line wrapping per RFC 8792

"@path": /foo
"@query": ?param=Value&Pet=dog
"@method": POST
"@authority": service.internal.example
"client-cert": :MIIBqDCCAU6gAwIBAgIBBzAKBggqhkjOPQQDAjA6MRswGQYDVQ\
  KDBJMZXQncyBBdXRoZW50aWNhdGUxGzAZBgNVBAMMEkxBIEludGVybWVkaWF0ZSBD\
  QTAeFw0yMDAxMTQyMjU1MzNaFw0yMTAxMjMyMjU1MzNaMA0xCzAJBgNVBAMMAkJDM\
  FkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8YnXXfaUgmnMtOXU/IncWalRhebrXm\
  ckC8vdgJ1p5Be5F/3YC8OthxM4+k1M6aEAEFcGzkJiNy6J84y7uzo9M6NyMHAwCQY\
  DVR0TBAIwADAfBgNVHSMEGDAWgBRm3WjLa38lbEYCuiCPct0ZaSED2DAOBgNVHQ8B\
  Af8EBAMCBsAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0RAQH/BBMwEYEPYmRjQ\
  GV4YW1wbGUuY29tMAoGCCqGSM49BAMCA0gAMEUCIBHda/r1vaL6G3VliL4/Di6YK0\
  Q6bMjeSkC3dFCOOB8TAiEAx/kHSB4urmiZ0NX5r5XarmPk0wmuydBVoU4hBVZ1yhk=:
"@signature-params": ("@path" "@query" "@method" "@authority" \
  "client-cert");created=1618884473;keyid="test-key-ecc-p256"

this gives the following signature:

NOTE: '' line wrapping per RFC 8792

xVMHVpawaAC/0SbHrKRs9i8I3eOs5RtTMGCWXm/9nvZzoHsIg6Mce9315T6xoklyy0y\
zhD9ah4JHRwMLOgmizw==

and that gives the signed request the proxy sends to the internal service, with the proxy's signature under the label ttrp: ๐Ÿ’–

NOTE: '' line wrapping per RFC 8792

POST /foo?param=Value&Pet=dog HTTP/1.1
Host: service.internal.example
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Length: 18
Client-Cert: :MIIBqDCCAU6gAwIBAgIBBzAKBggqhkjOPQQDAjA6MRswGQYDVQQKD\
  BJMZXQncyBBdXRoZW50aWNhdGUxGzAZBgNVBAMMEkxBIEludGVybWVkaWF0ZSBDQT\
  AeFw0yMDAxMTQyMjU1MzNaFw0yMTAxMjMyMjU1MzNaMA0xCzAJBgNVBAMMAkJDMFk\
  wEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8YnXXfaUgmnMtOXU/IncWalRhebrXmck\
  C8vdgJ1p5Be5F/3YC8OthxM4+k1M6aEAEFcGzkJiNy6J84y7uzo9M6NyMHAwCQYDV\
  R0TBAIwADAfBgNVHSMEGDAWgBRm3WjLa38lbEYCuiCPct0ZaSED2DAOBgNVHQ8BAf\
  8EBAMCBsAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0RAQH/BBMwEYEPYmRjQGV\
  4YW1wbGUuY29tMAoGCCqGSM49BAMCA0gAMEUCIBHda/r1vaL6G3VliL4/Di6YK0Q6\
  bMjeSkC3dFCOOB8TAiEAx/kHSB4urmiZ0NX5r5XarmPk0wmuydBVoU4hBVZ1yhk=:
Signature-Input: ttrp=("@path" "@query" "@method" "@authority" \
  "client-cert");created=1618884473;keyid="test-key-ecc-p256"
Signature: ttrp=:xVMHVpawaAC/0SbHrKRs9i8I3eOs5RtTMGCWXm/9nvZzoHsIg6\
  Mce9315T6xoklyy0yzhD9ah4JHRwMLOgmizw==:

{"hello": "world"}

the internal service can validate the proxy's signature, and so it can trust that the client's certificate was processed properly. ๐Ÿฆ‹


B.4. HTTP Message Transformations

okay this one is my favourite ๐Ÿ“ HTTP lets intermediaries and applications transform an HTTP message without changing what the message actually means, and HTTP message signatures are built to hold up against lots of those transformations in different situations. ๐ŸŒˆ

for example, this HTTP request has been signed with the Ed25519 algorithm and the key test-key-ed25519:

NOTE: '' line wrapping per RFC 8792

GET /demo?name1=Value1&Name2=value2 HTTP/1.1
Host: example.org
Date: Fri, 15 Jul 2022 14:24:55 GMT
Accept: application/json
Accept: */*
Signature-Input: transform=("@method" "@path" "@authority" \
  "accept");created=1618884473;keyid="test-key-ed25519"
Signature: transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5J\
  Z1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:

the signature base string for this message is:

"@method": GET
"@path": /demo
"@authority": example.org
"accept": application/json, */*
"@signature-params": ("@method" "@path" "@authority" "accept")\
  ;created=1618884473;keyid="test-key-ed25519"

next up, this message has been altered by adding the Accept-Language header field and a query parameter. โœจ but neither the Accept-Language header field nor the query is covered by the signature, so the same signature is still valid. ๐Ÿ’…

NOTE: '' line wrapping per RFC 8792

GET /demo?name1=Value1&Name2=value2&param=added HTTP/1.1
Host: example.org
Date: Fri, 15 Jul 2022 14:24:55 GMT
Accept: application/json
Accept: */*
Accept-Language: en-US,en;q=0.5
Signature-Input: transform=("@method" "@path" "@authority" \
  "accept");created=1618884473;keyid="test-key-ed25519"
Signature: transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5J\
  Z1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:

then this one has the Date header field removed, a Referer header field added, and the Accept header field collapsed into a single line. ๐Ÿ™ˆ the Date and Referer header fields aren't covered by the signature, and collapsing the Accept header field is an allowed transformation that the canonicalization algorithm for HTTP field values already accounts for. so the same signature is still valid. ๐Ÿซถ

NOTE: '' line wrapping per RFC 8792

GET /demo?name1=Value1&Name2=value2 HTTP/1.1
Host: example.org
Referer: https://developer.example.org/demo
Accept: application/json, */*
Signature-Input: transform=("@method" "@path" "@authority" \
  "accept");created=1618884473;keyid="test-key-ed25519"
Signature: transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5J\
  Z1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:

this message has its field values reordered, but the individual Accept header fields are not reordered. the same signature is still valid. ๐ŸŒธ

NOTE: '' line wrapping per RFC 8792

GET /demo?name1=Value1&Name2=value2 HTTP/1.1
Accept: application/json
Accept: */*
Date: Fri, 15 Jul 2022 14:24:55 GMT
Host: example.org
Signature-Input: transform=("@method" "@path" "@authority" \
  "accept");created=1618884473;keyid="test-key-ed25519"
Signature: transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5J\
  Z1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:

now the plot twist ๐Ÿ˜ญ this message changes the method to POST and the authority to "example.com" (inside the Host header field). both the method and the authority are covered by the signature, so the same signature is NOT still valid. ๐Ÿ’€

NOTE: '' line wrapping per RFC 8792

POST /demo?name1=Value1&Name2=value2 HTTP/1.1
Host: example.com
Date: Fri, 15 Jul 2022 14:24:55 GMT
Accept: application/json
Accept: */*
Signature-Input: transform=("@method" "@path" "@authority" \
  "accept");created=1618884473;keyid="test-key-ed25519"
Signature: transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5J\
  Z1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:

and last but not least, this one swaps the order of the two Accept header fields. ๐Ÿ‘‘ the order of fields with the same name is semantically significant in HTTP, so that changes the value used in the signature base, and the same signature is NOT still valid. ๐Ÿšซ

NOTE: '' line wrapping per RFC 8792

GET /demo?name1=Value1&Name2=value2 HTTP/1.1
Host: example.org
Date: Fri, 15 Jul 2022 14:24:55 GMT
Accept: */*
Accept: application/json
Signature-Input: transform=("@method" "@path" "@authority" \
  "accept");created=1618884473;keyid="test-key-ed25519"
Signature: transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5J\
  Z1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:

Acknowledgements

big thank you to the editors' crew ๐Ÿซถ this spec was initially based on [SIGNING-HTTP-MESSAGES]. the editors want to thank the authors of [SIGNING-HTTP-MESSAGES], Mark Cavage and Manu Sporny, for their work on that Internet-Draft and their continuing contributions. this spec also includes contributions from [SIGNING-HTTP-REQS-OAUTH] and other similar efforts. ๐Ÿ’–

the editors also want to thank these people (listed in alphabetical order) for feedback, insight, and implementation of this document and its predecessors: Mark Adamcin, Mark Allen, Paul Annesley, Karl Bรถhlmark, Stรฉphane Bortzmeyer, Sarven Capadisli, Liam Dennehy, Stephen Farrell, Phillip Hallam-Baker, Tyler Ham, Eric Holmes, Andrey Kislyuk, Adam Knight, Dave Lehn, Ilari Liusvaara, Dave Longley, James H. Manger, Kathleen Moriarty, Yoav Nir, Mark Nottingham, Adrian Palmer, Lucas Pardue, Roberto Polli, Julian Reschke, Michael Richardson, Wojciech Rygielski, Rich Salz, Adam Scarr, Cory J. Slep, Dirk Stein, Henry Story, Lukasz Szewc, Chris Webber, and Jeffrey Yasskin. ๐ŸŒˆ


Authors' Addresses

Annabelle Backman (editor) Amazon P.O. Box 81226 Seattle, WA 98108-1226 United States of America Email: richanna@amazon.com URI: https://www.amazon.com/

Justin Richer (editor) Bespoke Engineering Email: ietf@justin.richer.org URI: https://bspk.io/

Manu Sporny Digital Bazaar 203 Roanoke Street W. Blacksburg, VA 24060 United States of America Email: msporny@digitalbazaar.com

๐Ÿ“ฆ where this page comes from