RFC 9421
HTTP Message Signatures
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 Notice ๐
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-InputandSignaturefields (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
Connectionheader 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]) andForwarded(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
Authorizationfield to be covered so the credentials are protected, and require the signature parameters to include acreatedparameter (Section 2.3). An API that cares about semantically relevant content could require theContent-Digestfield defined in [DIGEST] to be present and covered, and also require atagparameter (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
keyidparameter 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
algparameter 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:
- 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.
- 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.
- 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. - 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:
- Let the input be the ordered set of values for a field, in the order they appear in the message.
- Create an empty List for accumulating processed field values.
- 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.
- The intermediate result is a List of Byte Sequence values.
- 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
reqparameter 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:
- 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.
- 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].
- 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:
- Let the output be an empty string. ๐ซง
- Determine an order for the component identifiers of the covered components, not including the
@signature-paramscomponent 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). ๐ - 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. ๐งฉ
- Determine an order for any signature parameters. Once this order is chosen, it cannot be changed. ๐
- 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. โ๏ธ
- 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 ๐
- Let the output be an empty string.
- 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).
- 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.
- Produce an error if the output string contains any non-ASCII characters [ASCII].
- 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 isa=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: ๐ง
- 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.
- The signer sets the signature's creation time to the current time โฐ.
- 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.
- 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 ๐คทโโ๏ธ.
- The signer creates the signature base using these parameters and the signature base creation algorithm (Section 2.5) ๐งฑ.
- 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.
- 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: ๐ง
- 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.
- 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 ๐.
- Parse the value of the corresponding Signature field to get the byte array value of the signature to be verified ๐งฉ.
- 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) โ .
- 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 ๐จ.
- 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 ๐ โโ๏ธ.
- 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 ๐ท๏ธ.
- 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.
- 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
createdtimestamp. โฐ - Rejecting signatures past the expiration time in the
expirestimestamp. 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
algparameter when the algorithm is determined from the key information. ๐ซ - Ensuring successful dereferencing of the
keyidparameter 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
nonceparameter. ๐ธ - Requiring an application-specific value for the
tagparameter. ๐ท๏ธ
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:
- Parse the field value as a Dictionary. ๐ง
- 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. ๐ฆ
- Optionally, create extra Signature-Input and Signature field values, with unique labels that don't show up in the Accept-Signature field. ๐
- 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_SIGNandHTTP_VERIFYprimitive 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 ๐
- Alice makes a message
Req_Aand applies a signatureSig_Ausing her private keyKey_A_Sign. - Alice believes she's sending
Req_Ato Bob. - Mallory intercepts
Req_Aand reads the valueSig_Afrom it. - Mallory generates a different message
Req_Mto send to Bob instead. - Mallory crafts a signing key
Key_M_Signso she can make a valid signatureSig_Mover her requestReq_Mwith it, but the byte value ofSig_Mexactly equals that ofSig_A. Sneaky ๐ - Mallory sends
Req_MwithSig_Mto Bob. - Bob validates
Sig_Magainst Mallory's verification keyKey_M_Verify. At no time does Bob think he's responding to Alice. - Bob responds to
Req_Mwith the response messageRes_Band creates the signatureSig_Bover it using his keyKey_B_Sign. Bob includes the value ofSig_MunderSig_B's covered components, but doesn't include anything else from the request message. - Mallory receives the response
Res_Bfrom Bob, including theSig_Bvalue. Mallory replays this response to Alice. - Alice reads
Res_Bfrom Mallory and verifiesSig_Busing Bob's verification keyKey_B_Verify. Alice includes the bytes of her original signatureSig_Ain the signature base, and the signature verifies โ๏ธ - 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 theSignaturefield, 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 correspondingSignature-Inputfield 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¶m=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
- ietf: rfc9421 2024-02
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms