互联网工程任务组 (IETF) A. Backman, Editor
请求注释: 9421 Amazon
类别:标准轨道 J. Richer, Editor
ISSN: 2070-1721 Bespoke Engineering
M. Sporny
Digital Bazaar
2024年2月

HTTP 消息签名


摘要

本文档描述了一种机制,用于对 HTTP 消息的组件创建、编码和验证数字签名或消息认证码。该机制支持签名者可能不知道完整 HTTP 消息且消息在到达验证者之前可能被(例如通过中间体)转换的用例。本文档还描述了一种在正在进行的 HTTP 交互中请求对随后 HTTP 消息应用签名的方法。

本备忘录的状态

这是一个互联网标准轨道文档。

本文档是互联网工程任务组 (IETF) 的产物。它代表了 IETF 社区的共识。它已接受公开审查并已获互联网工程指导小组 (IESG) 批准发布。有关互联网标准的更多信息,请参阅 RFC 7841 第 2 节

有关本文档的当前状态、任何勘误以及如何提供反馈的信息,可在 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 of this document. Please review these documents carefully, as 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 are provided without warranty as described in the Revised BSD License.


1. 引言

消息完整性和真实性是许多 HTTP 应用安全运行的关键安全属性。应用开发者通常依赖传输层通过在 TLS 上运行其应用来提供这些属性 [TLS]。然而,TLS 仅在单个 TLS 连接上保证这些属性,并且客户端与应用之间的路径可能由多个独立的 TLS 连接组成(例如,当应用托管在终止 TLS 的网关之后,或客户端位于 TLS 检查设备之后)。在这种情况下,TLS 无法在客户端与应用之间提供端到端的消息完整性或真实性。此外,某些运行环境使得使用 TLS(例如浏览器中呈现客户端证书)或使用提供消息真实性所需功能变得不切实际。另外,一些应用需要将高级别的、应用特定的密钥绑定到 HTTP 消息上,这与所使用的任何 TLS 证书分离。因此,尽管 TLS 能满足许多基于 HTTP 的应用的消息完整性和真实性需求,但它并非通用解决方案。

此外,由于库、中间体或应用框架在签名或验证时可能会更改或隐藏消息的部分内容,许多应用需要在对“线上的” HTTP 消息并不完全可见的情况下生成和验证签名。这些应用需要一种手段来保护对应用最重要的消息部分,而无需破坏分层和抽象。

最后,面向对象的签名机制(例如 JSON Web Signature [JWS])要求被签名的确切信息完整传输。当将此类技术应用于 HTTP 消息时,HTTP 消息的元素需要在对象载荷中重复出现,要么直接包含要么通过包含哈希来表达。这种做法增加了复杂性,因为在验证签名时需要仔细检查重复的信息的一致性。

本文档定义了一种机制,通过对 HTTP 消息的组件使用分离签名,为这些组件提供端到端的完整性和真实性。该机制允许应用仅对对其有意义且适当的消息组件创建数字签名或消息认证码 (MAC)。严格的规范化规则确保验证者即便在消息经过 HTTP 允许的多种转换后也能验证签名。

本文档所述的签名机制由三部分组成:

  • 用于不同协议元素和 HTTP 消息组件的通用命名法和规范化规则集,用于创建签名基础(第 2 节)。
  • 使用这些签名基础通过应用密码原语生成和验证针对 HTTP 消息组件的签名的算法(第 3 节)。
  • 将签名及相关元数据附加到 HTTP 消息中的机制,以及从 HTTP 消息解析附加签名和元数据的机制。为此,本文档定义了 "Signature-Input" 和 "Signature" 字段(第 4 节)。

本文档还提供了一种通过 "Accept-Signature" 字段协商在一个或多个后续消息中使用签名的机制(第 5 节)。该可选协商机制可与任意一方的机会性或应用驱动的消息签名一起使用。

本文档中定义的机制是构建应用总体安全机制的重要工具。该工具包提供了一些强大的能力,但不足以构成完整的安全方案。特别地,第 1.4 节 列出的要求和 第 7 节 中讨论的安全性考虑对所有实现者都非常重要。例如,本规范并未定义直接覆盖 HTTP 消息内容(在 [HTTP] 第 6.4 节 中定义)的手段;相反,它依赖于 Digest 规范 [DIGEST] 来提供消息内容的哈希,如在 第 7.2.8 节 中讨论的那样。

1.1. 约定与术语

本文档中关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" 和 "OPTIONAL" 应按 BCP 14 中对 RFC2119 的定义来解释(同时参见 [RFC2119][RFC8174]),仅当这些关键字全部大写时适用。

术语 "HTTP message", "HTTP request", "HTTP response", "target URI", "gateway", "header field", "intermediary", "request target", "trailer field", "sender", "method", 和 "recipient" 按 [HTTP] 中的定义使用。

为简便起见,文中使用“签名”一词同时指代数字签名(使用非对称密码学)和带键的 MAC(使用对称密码学)。类似地,动词“签名”指的是对给定签名基础生成数字签名或带键 MAC。限定术语“数字签名”专指使用非对称密码学签名操作输出的值。

本文档使用来自 [STRUCTURED-FIELDS] 第 3 节 的术语来指定数据类型:List、Inner List、Dictionary、Item、String、Integer、Byte Sequence 和 Boolean。

本文档使用 ABNF 定义若干字符串构造,并使用下列 ABNF 规则:VCHAR, SP, DQUOTE, 和 LF。本文档还使用了来自 [STRUCTURED-FIELDS] 的 ABNF 规则:sf-string, inner-list, 和 parameters。此外,本文档使用了来自 [HTTP][HTTP/1.1] 的 ABNF 规则:field-content, obs-fold, 和 obs-text

除上述术语外,本文档还使用下列术语:

HTTP Message Signature:
覆盖 HTTP 消息一个或多个部分的数字签名或带键 MAC。注意单个 HTTP 消息可以包含多个 HTTP 消息签名。
Signer:
生成或已生成 HTTP 消息签名的实体。注意多个实体可以作为签名者对给定 HTTP 消息应用各自的签名。
Verifier:
验证或已验证某个 HTTP 消息签名与 HTTP 消息相符的实体。注意 HTTP 消息签名可能会被多次验证,且可能由不同实体执行。
HTTP Message Component:
可由 HTTP 消息签名覆盖的 HTTP 消息的一部分。
Derived Component:
通过指定算法或过程从 HTTP 消息派生出的 HTTP 消息组件。见 第 2.2 节
HTTP Message Component Name:
标识 HTTP 消息组件来源的字符串,例如字段名或派生组件名。
HTTP Message Component Identifier:
HTTP 消息组件名与任何参数的组合。该组合在特定的 HTTP 消息签名及其适用的 HTTP 消息范围内唯一标识某一具体的 HTTP 消息组件。
HTTP Message Component Value:
在特定 HTTP 消息上下文中与给定组件标识符相关联的值。组件值由 HTTP 消息导出,并通常受到规范化过程处理。
Covered Components:
一个有序集合,列出字段(第 2.1 节)和派生组件(第 2.2 节)的 HTTP 消息组件标识符,表示签名覆盖的消息组件集合,且永远不包含 @signature-params 标识符本身。该集合的顺序被保留并在签名者与验证者之间传达,以便重建签名基础。
Signature Base:
签名者与验证者使用覆盖组件集合和 HTTP 消息生成的字节序列。签名基础由密码算法处理以生成或验证 HTTP 消息签名。
HTTP Message Signature Algorithm:
描述签名与验证过程的密码算法,以在 第 3.3 节 中定义的 HTTP_SIGNHTTP_VERIFY 原语为基础来描述。
Key Material:
创建或验证签名所需的密钥材料。密钥材料通常以显式的密钥标识符标识,允许签名者向验证者指示使用了哪个密钥。
Creation Time:
表示签名被生成时间点的时间戳,由签名者声明。
Expiration Time:
表示签名不再被验证者接受的时间点的时间戳,由签名者声明。
Target Message:
应用 HTTP 消息签名的目标 HTTP 消息。
Signature Context:
从中提取 HTTP 消息组件值的数据源。上下文包括目标消息以及签名者或验证者可能拥有的任何额外信息,例如请求的完整目标 URI 或与响应相关的请求消息。

术语 "UNIX timestamp" 指代 [POSIX.1] 第 4.16 节所称的“自纪元以来的秒数”。

本文档包含非规范性的部分和完整 HTTP 消息示例。有些示例使用单个反斜杠 (\) 表示长值的换行,依据 [RFC8792]。换行示例中的 \ 字符及换行后的前导空格不是值的一部分。

1.2. 要求

HTTP 允许并有时要求中间体以多种方式转换消息。这可能导致接收方收到的消息与最初发送的消息在位级上不等同。在这种情况下,接收方将无法验证基于发送方 HTTP 消息原始字节的完整性保护,因为验证数字签名或 MAC 需要签名者和验证者拥有完全相同的签名基础。既然消息的精确原始字节不能作为签名基础的可靠来源,则签名者和验证者必须从各自版本的消息中独立创建签名基础,采用对安全变更有弹性的机制,不因不改变消息含义的安全更改而失败。

由于种种原因,严格定义何为“安全变更”与“非安全变更”并不现实。应用以多种方式使用 HTTP,且可能对消息中的某一特定信息(例如消息内容、方法或特定头字段)是否相关存在分歧。因此,通用解决方案需要为签名者提供对哪些消息组件被签名的某种控制。

HTTP 应用可能运行在无法完全访问或控制 HTTP 消息的环境中(例如浏览器的 JavaScript 环境),或使用抽象化协议细节的库(例如 Java HTTP Client (HttpClient) 库)。这些应用需要能够在对 HTTP 消息的了解不完整的情况下生成和验证签名。

1.3. HTTP 消息转换

如前所述,HTTP 明确允许并在某些情况下要求实现以多种方式转换消息。实现被要求容忍这些转换。下文为非规范性且非穷尽的转换列表,作为上下文提供:

  • 不同字段名的字段重排序(见 [HTTP] 第 5.3 节)。
  • 具有相同字段名的字段合并(见 [HTTP] 第 5.2 节)。
  • 移除 Connection 头字段中列出的字段(见 [HTTP] 第 7.6.1 节)。
  • 添加指示控制选项的字段(见 [HTTP] 第 7.6.1 节)。
  • 增加或移除传输编码(见 [HTTP] 第 7.7 节)。
  • 添加诸如 Via(见 [HTTP] 第 7.6.3 节)和 Forwarded(见 [RFC7239] 第 4 节)之类的字段。
  • 不同版本 HTTP 之间的转换(例如 HTTP/1.x 与 HTTP/2 之间的转换)。
  • 对任何不区分大小写的组件(例如字段名、请求 URI 方案或主机)改变大小写(例如 "Origin" 改为 "origin")。
  • 对请求目标和授权(authority)做出同时应用且不改变消息目标 URI 的修改(参见 [HTTP] 第 7.1 节)。

另外,有些转换虽被弃用或不被允许,但在实际中仍可能发生。这些转换仍然可以在不破坏签名的情况下处理;包括如下行为:

  • 在字段值中使用、添加或移除首尾空白。
  • 在字段值中使用、添加或移除 obs-fold(见 [HTTP/1.1] 第 5.2 节)。

我们可以将这些转换识别为那些即便在签名覆盖的消息组件上执行也不应阻止签名验证的转换。此外,对签名未覆盖的组件所做的任何更改都不应阻止签名验证。

关于此类转换的示例以及它们对消息签名的影响,请参见 附录 B.4

其它转换,例如解析并重新序列化已覆盖组件的字段值或更改派生组件的值,可能导致签名不再针对目标消息验证。使用本规范的应用需要谨慎,确保所选覆盖组件能充分处理应用预期的转换。

1.4. HTTP 消息签名的应用

HTTP 消息签名被设计为一种通用工具,适用于多种情况与应用。要正确且安全地应用 HTTP 消息签名,某个应用或本规范的配置文件 MUST 至少指定以下各项:

  • 预期和必需包含在覆盖组件列表中的组件标识符集合(参见 第 2 节)和签名参数(参见 第 2.3 节)。例如,一个授权协议可以强制要求覆盖 Authorization 字段以保护授权凭证,并强制要求签名参数包含 created 参数;而期望语义相关的消息内容的 API 则可以要求存在并被覆盖的 Content-Digest 字段(见 [DIGEST])并强制要求签名参数中的 tag 参数取 API 特定的值。
  • 任何必需或预期被覆盖的组件字段或参数的预期 Structured Field 类型(参见 [STRUCTURED-FIELDS])。
  • 检索用于验证签名的密钥材料的方法。应用通常使用签名参数的 keyid 参数(参见 第 2.3 节)并定义从该参数解析密钥的规则,尽管也可以通过预注册签名者密钥等其它方式获知适当的密钥。
  • 签名者可使用且验证者可接受的允许签名算法集合。
  • 确定用于验证签名的算法是否适合所用密钥材料与消息上下文的方法。例如,该过程可以使用签名参数的 alg 参数(参见 第 2.3 节)明确说明算法、从密钥材料派生算法,或使用签名者与验证者预先协定的算法。
  • 确定用于签名的给定密钥与算法是否适合消息上下文的方法。例如,期望仅使用 ECDSA 签名的服务器应知道拒绝任何 RSA 签名,或期望非对称密码学的服务器应拒绝任何对称密码学签名。
  • 确定从 HTTP 消息及其应用上下文派生消息组件的上下文的方法。通常这是目标 HTTP 消息本身,但上下文也可能包含通过配置为应用所知的附加信息,例如外部主机名。
  • 如果需要使用 第 2.4 节 提供的机制在请求与响应之间进行绑定,则需要指定为提供此类绑定所需的请求消息和响应消息的所有元素。
  • 当签名无效、密钥材料不适当、有效性时间窗口超出规格、组件值无法计算或在签名验证过程中发生任何其它错误时,验证方向签名者返回的错误消息与代码。例如,如果签名被用作认证机制,则返回 401 (Unauthorized) 或 403 (Forbidden) HTTP 状态码可能合适。如果响应来自 HTTP API,则可返回例如 400 (Bad Request) 的状态码并包含更多细节(参见 [RFC7807][RFC9457]),例如指示使用了错误的密钥材料。

在选择这些参数时,应用必须确保验证者能获取到重建签名基础所需的所有信息。例如,处于反向代理后面的服务器若要使用派生组件 @target-uri,需要知道原始请求 URI,尽管反向代理可能改变显见的目标 URI(另见 第 7.4.3 节)。另外,在响应中使用签名的应用需要确保接收签名响应的客户端能够访问所有被签名的消息部分,包括服务器使用 req(“request-response”)参数(见 第 2.4 节)签名的任何请求部分。

关于这类配置文件的细节属于应用层面的范畴,超出本规范范围;不过第 7 节 中讨论了一些额外的考虑事项。特别地,在选择必需的组件标识符集合时,需要确保覆盖范围对应用足够,如 第 7.2.1 节第 7.2.8 节 所述。本规范仅构成应用完整安全系统的一部分。在基于此工具构建完整安全系统时,对整个系统进行安全分析非常重要。历史系统(如 AWS Signature Version 4 [AWS-SIGv4])可为如何将类似机制应用于某一应用提供启发,但参考这些历史系统并不能替代对 HTTP 消息签名应用进行的安全分析。


2. HTTP 消息组件

为了让签名者和验证者确定哪些组件包含在签名中,本文档定义了用于 HTTP 消息签名所覆盖组件的组件标识符、从 HTTP 消息派生并规范化与这些组件标识符相关联的值的一组规则,以及将这些规范化值组合成签名基础的方法。

用于派生这些值的签名上下文必须对消息的签名者和验证者都可访问。该上下文在给定签名的所有组件中必须保持一致。例如,将 @query 派生组件使用原始查询字符串而将 @query-param 派生组件使用组合的查询和表单参数就是错误的。关于消息组件上下文的更多考虑,请参见 第 7.4.3 节

组件标识符由组件名称及与该名称关联的任何参数组成。每个组件名称要么是 HTTP 字段名(参见 第 2.1 节),要么是已注册的派生组件名(参见 第 2.2 节)。组件标识符可能的参数取决于该组件标识符。所谓的 “HTTP Signature Component Parameters” 注册表(记录了所有可能的参数)在 第 6.5 节 中定义。

在单个已覆盖组件列表中,每个组件标识符必须只出现一次。若组件名称不同或同一组件名称的任何参数不同,则一个组件标识符与另一个是不同的。如果多个组件标识符具有相同的组件名称,但通过参数使其不同(例如 "foo";bar"foo";baz),则可以包含这些组件标识符。处理组件标识符(例如在验证期间解析时)时,参数的顺序必须被保留,但在比较两个组件标识符是否相等时参数的顺序并不具有意义。也就是说,"foo";bar;baz"foo";baz;bar 等价,因此不能同时出现在同一消息中,但处理系统不得将一种形式转换为另一种形式。

与组件标识符相关联的组件值由该标识符本身定义。组件值不得包含换行符(\n)。某些 HTTP 消息组件可能会经历使位级值改变但不改变组件含义的转换(例如,合并字段值)。因此,在对消息组件值进行签名之前需要对其进行规范化,以确保即使经历了中间体的此类转换也能验证签名。本文档为每个组件标识符定义了将其关联组件值转换为此类规范形式的规则。

下列各节定义了组件标识符名称、它们的参数、关联值以及这些值的规范化规则。将消息组件组合到签名基础的方法在 第 2.5 节 中定义。

2.1. HTTP 字段

HTTP 字段的组件名称是其字段名的小写形式(参见 [HTTP] 第 5.1 节)。尽管 HTTP 字段名不区分大小写,在将其用作组件名称时,实装必须使用小写字段名(例如 content-typedateetag)。

HTTP 字段的组件值是目标消息中该命名字段的字段值(见 [HTTP] 第 5.5 节)。除非被附加参数和规则(例如下文所述的 reqtr 标记)覆盖,否则字段值必须从目标消息的命名头字段中获取。对于大多数字段,字段值为 ASCII 字符串(如 [HTTP] 推荐),组件值就是该字符串。某些实现中可能存在其它编码,因此所有非 ASCII 的字段值必须在加入签名基础之前编码为 ASCII。正如 第 2.1.3 节 所述,bs 参数提供了封装此类有问题字段值的方法。

除非被附加参数与规则覆盖,HTTP 字段值必须按照 [HTTP] 第 5.2 节 中定义的方式合并为单个值以创建组件值。具体来说,以多次字段形式发送的 HTTP 字段必须通过使用单个逗号和单个空格作为分隔符("," + " ")将值连接起来来合并。注意中间体被允许在逗号之间使用任意数量的空白来合并值,如果验证者不考虑这种行为,签名可能会失败,因为签名者和验证者在各自的签名基础中看到的组件值会不同。为提高鲁棒性,建议签名的消息尽量只包含被签名字段的单个实例,尤其是对任何基于列表的字段,应使用下述算法序列化字段值。这能增加字段值通过中间体时保持不变的概率。若无法采用该方法且需要分别发送字段的多个实例,则建议签名者和验证者对任何基于列表的字段采用严格算法,收集所有单独字段值并按下述严格算法合并,以应对可能的中间体行为。当字段为 List 或 Dictionary 类型的结构化字段时,可通过要求对字段值进行严格的结构化字段序列化来更直接地实现这一效果,详见 第 2.1.1 节

注意,某些 HTTP 字段(例如 Set-Cookie [COOKIE])不遵循允许以这种方式合并字段值的语法(即从多个输入得到的合并输出并不一定是单一且不含歧义的)。尽管组件值在消息签名过程中不会被解析,仅作为签名基础的一部分(参见 第 2.5 节),在签名此类字段时仍需谨慎,因为合并后的值可能会产生歧义。针对这类有问题字段,bs 参数(见 第 2.1.3 节)提供了封装方法。关于此问题的更多讨论见 第 7.5.6 节

如果实现无法直接获得给定字段的正确合并值,则下列算法将为基于列表的字段产生规范化结果:

  1. 创建一个有序列表,包含消息中该字段每个实例的字段值,按其在消息中出现的顺序(或将出现的顺序)。
  2. 从列表中每一项删除前导和尾随空白。注意,因 HTTP 字段值不允许包含前导和尾随空白,在合规实现中这将是无操作。
  3. 移除行内任何过时的折行(obsolete line folding),并以单个空格(" ")替换,如 [HTTP/1.1] 第 5.2 节 所述。注意该行为特定于 HTTP/1.1,其他版本的 HTTP 规范不允许内部折行。
  4. 以单个逗号(",")和单个空格(" ")连接该值列表。

得到的字符串即为该字段的组件值。

注意,某些 HTTP 字段具有多种有效的序列化形式且语义等价,例如允许中间体更改大小写的不区分大小写的值。对该类字段进行签名和处理的应用必须考虑如何处理这些字段的值,以确保签名者和验证者能够派生出相同的值,详见 第 7.5.2 节

下面给出针对头字段组件值的若干非规范性示例,基于如下示例 HTTP 消息片段:

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)

下例展示了这些示例头字段对应的组件值,使用 第 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)

当存在空的 HTTP 字段时也可以对其进行签名。规范化的值为空字符串。这意味着下面这个空头字段(示例中用 (SP) 表示在空字段值前的单个尾随空格字符):

X-Empty-Header:(SP)

在签名基础生成算法(见 第 2.5 节)中会被序列化为冒号之后跟随空字符串的形式,冒号前后会加入空格。

"x-empty-header":(SP)

在特定情况下,任何 HTTP 字段组件标识符可以具有下列参数,详见各自节:

sf
一个布尔标志,指示组件值使用结构化字段值的严格编码序列化(见 第 2.1.1 节)。
key
一个字符串参数,用于从字典结构化字段中选择单个成员值(参见 第 2.1.2 节)。
bs
一个布尔标志,指示在将个别字段值合并到组件值之前使用 Byte Sequence 数据结构对其进行编码(参见 第 2.1.3 节)。
req
一个用于已签名响应的布尔标志,表明组件值是从触发该响应消息的请求中派生的,而不是直接从响应消息中获取。注意该参数也可应用于任何以请求为目标的派生组件标识符(参见 第 2.4 节)。
tr
一个布尔标志,指示字段值取自消息的 trailer 字段(见 [HTTP] 第 6.5 节)。若无该标志,字段值则取自消息的头字段(见 [HTTP] 第 6.3 节,参见 第 2.1.4 节)。

可以同时指定多个参数,但有些组合是冗余或不兼容的。例如,当对字典项使用 key 参数时,其功能已被覆盖,因为 key 要求对值进行严格序列化。bs 参数要求使用消息中字段值的原始字节,因此与要求字段值在合并后进行解析的数据结构(如 sfkey)不兼容。

可以在第 第 6.5 节 建立的 “HTTP Signature Component Parameters” 注册表中定义附加参数。

2.1.1. HTTP 结构化字段的严格序列化

如果应用已知某 HTTP 字段的值为结构化字段类型(按 [STRUCTURED-FIELDS] 或其扩展/更新定义)且已知该结构化字段的期望类型,则签名者可以在组件标识符中包含 sf 参数。若在组件标识符中包含此参数,则 HTTP 字段值必须使用 [STRUCTURED-FIELDS] 第 4 节(或其扩展/更新的适用正式序列化规则)所规定的正式序列化规则进行序列化。请注意,此过程会替换任何可选的内部空白为单个空格字符等。

若消息中出现多个字段值,这些值必须在序列化前合并为单个 List 或 Dictionary 结构。

如果应用不知道字段的类型或不知道如何序列化该类型,使用此标志将导致错误。因此,签名者只有在验证者的系统也知道该类型时才能可靠地对字段使用此标志进行签名。

例如,下列 Dictionary 字段是一个有效的序列化:

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

若在签名基础中不带参数包含其值,则为:

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

但是如果添加 sf 参数,值将重新序列化如下:

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

生成的字符串作为组件值使用;参见 第 2.1 节

2.1.2. 字典结构化字段成员

如果某字段在应用中被知晓为字典结构化字段,则可通过使用 key 参数并将字典成员键作为字符串值,来标识该字典值中的单个成员。

若消息中存在多个字段值,这些值必须在序列化前合并为单个字典结构。

字典成员的单个成员值通过对 member_value 及其参数应用 [STRUCTURED-FIELDS] 第 4.1.2 节 所述的序列化算法进行规范化,而不包括字典键本身。具体地,该值作为 Item 或 Inner List(字典成员的两种可能值)序列化,所有参数和可能的子字段都使用 [STRUCTURED-FIELDS] 第 4 节 中定义的严格序列化规则进行序列化(或其扩展/更新的适用部分)。

给定字段的每个带参数的键在签名基础中不得出现多于一次。带参数的键在签名基础中可以按任意顺序出现,而不受其在源字典中出现顺序的限制。

如果一个字典键被指定为已覆盖组件但在字典中不存在,则在生成签名基础时必须引发错误。

下列为非规范性示例,展示了在已知该字段为字典时,字典成员规范化值的示例,给出如下示例头字段:

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

下例示出该字段不同组件标识符的规范化值,使用 第 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)

注意 key="c" 的值已根据严格的 member_value 算法重新序列化,且 key="d" 的值已作为布尔值序列化。

2.1.3. 二进制封装的 HTTP 字段

如果应用已知某 HTTP 字段的值在序列化时会导致问题,尤其是在如 第 7.5.6 节 所述的将多个值合并为单行时会出问题,签名者应该在组件标识符中包含 bs 参数,以指示在合并之前需要将字段值作为二进制结构进行封装。

若在组件标识符中包含该参数,则组件值必须按下述算法计算:

  1. 设输入为消息中字段值的按出现顺序组成的有序集合。
  2. 创建一个空的 List 用于累积处理后的字段值。
  3. 对集合中的每个字段值执行:

    11.
    从字段值中删除前导和尾随空白。注意在合规实现中这通常是无操作。
    12.
    移除行内任何过时的折行,并以单个空格(" ")替换,如 [HTTP/1.1] 第 5.2 节 所述。注意此行为特定于 HTTP/1.1。
    13.
    将结果字段值的字节编码为 Byte Sequence。注意多数字段限制为 ASCII 字符,但某些实现中可能包含其它八位字节。
    14.
    将该 Byte Sequence 添加到 List 累加器中。
  4. 中间结果为 Byte Sequence 值的 List。
  5. 遵循 [STRUCTURED-FIELDS] 第 4.1.1 节 的 List 严格序列化,并返回该输出。

例如,下面这个包含内部逗号的字段使得将不同字段值安全地合并变得不可能:

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

在示例中,同一字段也可以以语义不同的单一值发送:

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

应用将把这两种版本区别对待。然而,若在签名基础中不带参数包含其值,则组件值在两种情况下会相同:

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

但是,如果加入 bs 参数,则会对两个独立实例进行编码和序列化,如下:

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

对于上面单实例字段,带 bs 参数的编码为:

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

该组件值与多实例字段的组件值不同,从而防止了可能被利用的碰撞。

2.1.4. Trailer 字段

如果签名者想将某个 trailer 字段包含在签名中,则签名者必须包含布尔参数 tr,以指示该值必须从 trailer 字段中获取,而非头字段中获取。

例如,给定如下消息:

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

签名者决定将 Trailer 头字段和 Expires trailer 字段以及状态码派生组件加入签名基础:

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

如果某字段在消息中既作为头字段又作为 trailer 可用,则两者可以都被签名,但必须分别签名。具有相同名称的头字段值与 trailer 字段值不得为签名目的而被合并。

由于 trailer 字段可能被中间体合并进头字段或完全丢弃(参见 [HTTP] 第 6.5.1 节),不建议将 trailer 包含在签名中,除非签名者知道验证者将能够按发送时那样获取这些 trailer 的值。

2.2. 派生组件

除了 HTTP 字段之外,还有若干可从控制数据、签名上下文或要签名的 HTTP 消息的其它方面派生出的组件。通过定义组件名称、可能的参数、消息目标以及其组件值的派生方法,可以将这些派生组件包含进签名基础。

派生组件名称必须以 “at”(@)字符开头。这将派生组件名称与 HTTP 字段名区分开来;根据 [HTTP] 第 5.1 节,HTTP 字段名不能包含 @ 字符。HTTP 消息签名的处理器必须将派生组件名称与字段名分开处理,详见 第 7.5.1 节

本规范定义以下派生组件:

@method
请求使用的方法(参见 第 2.2.1 节)。
@target-uri
请求的完整目标 URI(参见 第 2.2.2 节)。
@authority
请求目标 URI 的 authority(参见 第 2.2.3 节)。
@scheme
请求目标 URI 的 scheme(参见 第 2.2.4 节)。
@request-target
请求目标(参见 第 2.2.5 节)。
@path
请求目标 URI 的绝对路径部分(参见 第 2.2.6 节)。
@query
请求目标 URI 的查询部分(参见 第 2.2.7 节)。
@query-param
请求目标 URI 的已解析并编码的查询参数(参见 第 2.2.8 节)。
@status
响应的状态码(参见 第 2.2.9 节)。

附加的派生组件名称在 “HTTP Signature Derived Component Names” 注册表中定义(参见 第 6.4 节)。

派生组件值取自签名目标消息的上下文。该上下文包括关于消息本身的信息(例如控制数据)以及签名者或验证者持有的任何额外状态与上下文。特别地,当对响应进行签名时,签名者可以通过使用 req 参数(参见 第 2.4 节)将原始请求的任何派生组件包含进来。

request:
从 HTTP 请求消息派生并应用的值,详见 [HTTP] 第 3.4 节。若签名的目标消息为响应,则可使用 req 参数包含以请求为目标的派生组件(见 第 2.4 节)。
response:
从 HTTP 响应消息派生并应用的值,详见 [HTTP] 第 3.4 节
request, response:
可从请求消息或响应消息中派生并应用的值。

派生组件定义必须定义其可应用的所有目标消息类型。

派生组件值必须仅限可打印字符与空格,且不得包含任何换行字符。派生组件值不得以空白字符开始或结束。

2.2.1. 方法

@method 派生组件指请求消息使用的 HTTP 方法。组件值通过将方法值作为字符串取出并规范化而得到。注意方法名按 [HTTP]第 9.1 节)是区分大小写的。尽管规范方法名通常为大写(参见 [ASCII]),但不会对输入方法值的大小写执行任何转换。

例如,下面的请求消息:

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

将产生如下 @method 组件值:

POST

以及如下签名基础行:

"@method": POST

2.2.2. 目标 URI

@target-uri 派生组件指请求消息的目标 URI。组件值是请求的目标 URI(参见 [HTTP]第 7.1 节),由所有可用的 URI 组件组成,包括 authority。

例如,下面通过 HTTPS 发送的消息:

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

将产生如下 @target-uri 组件值:

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

以及如下签名基础行:

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

2.2.3. 授权 (Authority)

@authority 派生组件指 HTTP 请求目标 URI 的 authority 组件,如 [HTTP]第 7.2 节)所定义。在 HTTP/1.1 中,这通常由 Host 头字段传递,而在 HTTP/2 与 HTTP/3 中,则由 :authority 伪头传递。该值为请求目标的完全限定 authority(由主机和可选的端口组成)作为字符串。组件值必须[HTTP]第 4.2.3 节)中提供的规则进行规范化:主机名归一化为小写,且省略默认端口。

例如,下面的请求消息:

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

将产生如下 @authority 组件值:

www.example.com

以及如下签名基础行:

"@authority": www.example.com

建议使用 @authority 派生组件,而不是直接对 Host 头字段进行签名。详见 第 7.2.4 节

2.2.4. 方案 (Scheme)

@scheme 派生组件指请求目标 URL 的方案。组件值为小写字符串形式的方案,按 [HTTP]第 4.2 节)定义。虽然方案不区分大小写,但为包含在签名基础中,必须将其归一化为小写。

例如,下面通过明文 HTTP 发送的请求消息:

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

将产生如下 @scheme 组件值:

http

以及如下签名基础行:

"@scheme": http

2.2.5. 请求目标

@request-target 派生组件指 HTTP 请求消息的完整请求目标,如 [HTTP]第 7.1 节)所定义。请求目标的组件值将根据请求类型采取不同的形式,下面将进行说明。

对于 HTTP/1.1,该组件值等同于请求行中的请求目标部分。然而,该值在其他 HTTP 版本中更难可靠构造。因此,当可能使用除 1.1 以外的 HTTP 版本时,不建议使用该组件。

origin form 值为请求 URL 的绝对路径和查询组件的组合。

例如,下面的请求消息:

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

将产生如下 @request-target 组件值:

/path?param=value

以及如下签名基础行:

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

以下为发送到 HTTP 代理的 absolute-form 值示例,包含完全限定的目标 URI:

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

将产生如下 @request-target 组件值:

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

以及如下签名基础行:

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

以下为 CONNECT 请求的 authority-form 值示例,包含目标的主机与端口:

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

将产生如下 @request-target 组件值:

www.example.com:80

以及如下签名基础行:

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

以下为带有星号形式(含单个星号字符 *)的 OPTIONS 请求示例:

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

将产生如下 @request-target 组件值:

*

以及如下签名基础行:

"@request-target": *

2.2.6. 路径

@path 派生组件指请求消息的目标路径。组件值为请求目标的绝对路径(按 [URI] 定义),不包括查询组件且不包含末尾的问号(?)。该值按 [HTTP]第 4.2.3 节)中提供的规则进行规范化:空路径字符串规范化为单个斜杠(/)。路径组件以其在解码任何百分号编码八位字节之前的值表示,依据 [URI] 第 6.2.1 节 的简单字符串比较规则。

例如,下面的请求消息:

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

将产生如下 @path 组件值:

/path

以及如下签名基础行:

"@path": /path

2.2.7. 查询

@query 派生组件指请求消息的查询组件。组件值为整个规范化的查询字符串(包括前导问号 ?),按 [URI] 的定义读取。该值按 [URI] 第 6.2.1 节 的简单字符串比较规则读取,即不对百分号编码的八位字节进行解码。

例如,下面的请求消息:

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

将产生如下 @query 组件值:

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

以及如下签名基础行:

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

下面的请求消息:

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

将产生如下 @query 组件值:

?queryString

以及如下签名基础行:

"@query": ?queryString

与包含空路径组件类似,签名者也可以包含空查询组件以表示此组件在消息中未使用。如果请求消息中不存在查询字符串,则组件值仅为前导的问号字符:

?

生成如下签名基础行:

"@query": ?

2.2.8. 查询参数

如果请求目标 URI 的查询部分使用 HTML 表单参数格式(参见 [HTMLURL] 的第 5 节),则 @query-param 派生组件允许针对这些单独的查询参数。查询参数必须[HTMLURL] 的第 5.1 节解析,从而得到一系列 (nameString, valueString) 元组。每个组件标识符的必需 name 参数包含单个查询参数的已编码 nameString 作为字符串值。单个命名参数的组件值为该单个查询参数的已编码 valueString。可以在已覆盖组件中包含多个不同的命名查询参数。单个命名参数在已覆盖组件中可以按任意顺序出现,而不受其在查询字符串中出现顺序的限制。

name 参数的值以及单个命名参数的组件值按下列过程计算:

  1. 解析命名查询参数的 nameStringvalueString(按 [HTMLURL] 第 5.1 节),这是对百分号编码八位字节进行解码后的值。
  2. 使用 [HTMLURL] 第 5.2 节定义的 “编码后再百分号编码(percent-encode after encoding)” 过程对 nameStringvalueString 进行编码;结果为 ASCII 字符串(参见 [ASCII])。
  3. 输出该 ASCII 字符串。

注意组件值不包含任何前导问号(?)、等号(=)或分隔的 & 字符。命名查询参数若具有空的 valueString,其组件值即为空字符串。注意由于实现差异,有些查询参数解析库会丢弃此类空值。

如果某查询参数被指定为已覆盖组件但在查询参数中不存在,则在生成签名基础时必须引发错误。

例如,针对下面的请求:

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

指明命名查询参数 bazquxparam 将产生如下 @query-param 组件值:

baz: batman

qux: 一个空字符串

param: value

以及如下签名基础行,示例中用 (SP) 表示空组件值前的单个尾随空格字符:

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

该派生组件存在一些限制。具体来说,[HTMLURL] 第 5 节中的算法仅支持使用百分号转义 UTF-8 编码的查询参数。其它编码不受支持。另外,在实践中命名参数多次出现并不可靠。如果一个参数名在请求中多次出现,则不得包含该命名查询参数。如果应用中常见多个同名参数,建议使用 @query 组件标识符对整个查询字符串进行签名(见 第 2.2.7 节)。

编码过程允许查询参数在其值中包含换行或其它问题字符,或使用加号(+)表示空格等替代编码。对于下列消息中的查询参数:

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

其结果值按如下方式编码:

"@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

若不进行编码,得到的值将会是:

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

该基础字符串包含违反组件名称与组件值约束的字符,因此无效。

2.2.9. 状态码

@status 派生组件指响应消息的三位数字 HTTP 状态码,如 [HTTP]第 15 节)定义。组件值是该 HTTP 状态码的三位整数序列化形式,不包括任何描述性文本。

例如,下面的响应消息:

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

将产生如下 @status 组件值:

200

以及如下签名基础行:

"@status": 200

@status 组件标识符不得在请求消息中使用。

2.3. 签名参数

HTTP 消息签名具有元数据属性,用以提供关于签名生成与验证的信息,这些元数据由已覆盖组件的有序集合与参数的有序集合组成,参数包含签名创建时间戳、用于验证的密钥材料标识符以及其它辅助信息。该元数据在签名基础中由一个特殊的消息组件表示;该特殊消息组件的处理方式与其它消息组件略有不同。具体地,签名参数消息组件必须作为签名基础的最后一行出现(参见 第 2.5 节),并且该组件标识符不得在任何签名的已覆盖组件集合中列出,包括其自身。

签名参数组件的名称为 @signature-params

签名参数组件值为该签名的签名参数的序列化表示,包含已覆盖组件的有序集合及其所有关联参数。这些参数可以包括下列任一项:

created:
创建时间,UNIX 时间戳形式的整数类型。不支持子秒精度。建议包含此参数。
expires:
过期时间,UNIX 时间戳形式的整数类型。不支持子秒精度。
nonce:
为该签名生成的随机唯一值,作为字符串值。
alg:
来自 “HTTP Signature Algorithms” 注册表的 HTTP 消息签名算法,作为字符串值。
keyid:
用于标识密钥材料的标识符,作为字符串值。
tag:
签名的应用特定标签,作为字符串值。应用使用该值帮助标识与特定应用或协议相关的签名。

附加参数可以在 “HTTP Signature Metadata Parameters” 注册表中定义(参见 第 6.3 节)。注意参数本身没有通用顺序,但一旦为给定参数集合选择了某种顺序,就不能在不改变签名参数值的情况下更改该顺序。

签名参数组件值按 [STRUCTURED-FIELDS] 第 4 节 的规则作为带参数的 Inner List 进行序列化,方法如下:

  1. 令输出为空字符串。
  2. 确定已覆盖组件的组件标识符的顺序(不包括 @signature-params 本身)。一旦选择该顺序就不能更改。此顺序必须与用于创建签名基础的顺序相同(参见 第 2.5 节)。
  3. 将已覆盖组件的组件标识符(包括所有参数)按有序 Inner List 的字符串值序列化,依据 [STRUCTURED-FIELDS] 第 4.1.1.1 节,然后将其附加到输出。注意组件标识符可以包含自身的参数,这些参数是有序集合。一旦为某组件的参数选择了顺序,该顺序就不得更改。
  4. 确定任意签名参数的顺序。一旦选择该顺序就不得更改。
  5. [STRUCTURED-FIELDS] 第 4.1.1.2 节 的规则将参数按顺序附加到 Inner List 中,跳过对于该消息签名不可用或未使用的参数。
  6. 输出即包含签名参数组件值。

注意,这里使用 [STRUCTURED-FIELDS] 第 4.1.1.1 节 的 Inner List 序列化来表示已覆盖组件值,而不是使用 第 4.1.1 节 的 List 序列化,以便与该值在 Signature-Input 字段中的并行性(参见 第 4.1 节)更好地对应。

下例展示了某示例消息签名参数的序列化组件值:

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

注意 HTTP 消息可能包含多个签名(参见 第 4.3 节),但每个签名的签名参数条目仅包含用于该单一签名的签名参数。

2.4. 在响应消息中对请求组件进行签名

当一个请求消息导致生成签名响应消息时,签名者可以通过在组件标识符中添加 req 参数,将请求消息的部分内容包含到签名基础中。

req
一个布尔标志,指示组件值是从触发该响应消息的请求中派生的,而不是直接从响应消息中获取。

该参数可以应用于 HTTP 字段以及以请求为目标的派生组件,两者语义相同。使用该参数的消息组件的组件值按通常方式计算,但数据从请求消息中提取,而不是从所签名的目标响应消息中提取。

注意,相同的组件名称在同一签名基础中可以既带有 req 参数也不带该参数,分别表示来自请求消息与响应消息的同名组件。

req 参数可以与适用于组件标识符的其它参数组合使用,例如字典字段的 key 参数。

例如,当为下列请求提供响应时:

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"}

将产生如下未签名的响应消息:

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"}

服务器使用其自身的密钥对响应签名,覆盖了响应的若干字段。除此之外,服务器还将若干来自最初触发该响应的请求消息的组件包含进来。此示例中,服务器在响应的已覆盖组件中包含了请求的 method、authority、path 以及 content-digest。请求与响应的 Content-Digest 均被包含在响应签名内。对于本示例的应用,查询被认为与响应无关,因此未被覆盖。其它应用会根据其需要做出不同决定,详见 第 1.4 节

该示例的签名基础为:

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"

签名后的响应消息为:

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"}

注意此处使用的 ECDSA 签名算法为非确定性算法,这意味着每次运行算法都会生成不同的签名值。此处给出的签名值可用来对示例中的密钥进行验证,但新生成的签名值不应与本示例匹配。参见 第 7.3.5 节

由于请求中的组件值不会在响应消息中重复出现,请求者必须将原始消息组件值保留足够长的时间,以便验证使用这些组件标识符参数的响应签名。在大多数情况下,这意味着请求者需要保留原始请求消息,因为签名者可能会根据应用需要在响应中包含请求的任意部分。由于中间体可能在请求被服务器处理前修改请求消息,应用需要谨慎避免对这样的被修改值进行签名,否则客户端将无法验证由此生成的签名。

服务器也可以针对已签名请求创建已签名响应。对于下列已签名请求示例:

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"}

服务器可以选择签名该响应的部分内容,包括请求的若干部分,从而得到如下签名基础:

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"

以及如下已签名的响应:

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"}

注意此处使用的 ECDSA 签名算法为非确定性算法,每次运行将产生不同签名值。此处提供的签名值可使用给定密钥进行验证,但新生成的签名值不应与示例一致。参见 第 7.3.5 节

为已签名请求生成响应的应用应该签名请求签名值的所有组件,以提供足够的覆盖和防护,防范一类碰撞攻击,详见 第 7.3.7 节。本示例中的服务器在响应签名中包含了客户端请求签名中 Signature-Input 字段所列的所有组件,此外还包含了响应的组件。

尽管在语法上可以将请求消息的 Signature 与 Signature-Input 字段包含到响应签名组件中,但此做法不建议。原因在于“签名的签名”并不会像直观期望的那样提供传递性的覆盖,且此做法容易遭受多种攻击,详见 第 7.3.7 节。需要以安全方式表示签名已被成功处理或接收的应用必须仔细指定替代机制。

响应签名只能覆盖在使用该标志时包含在请求消息中的内容。因此,如果应用需要在响应签名中包含请求的消息内容,客户端需通过某种手段覆盖该内容,例如添加 Content-Digest 字段。有关更多信息,见 第 7.2.8 节

req 参数不得用于以请求消息为目标的签名中的任何组件。

2.5. 创建签名基础

签名基础是一个 ASCII 字符串 [ASCII],包含由签名覆盖的经规范化的 HTTP 消息组件。签名基础创建算法的输入是已覆盖组件标识符的有序集合及其关联值,以及在 第 2.3 节 中讨论的任何附加签名参数。

组件标识符使用 [STRUCTURED-FIELDS] 第 4 节定义的严格序列化规则进行序列化。组件标识符具有一个组件名称,该名称是使用 sf-string ABNF 规则序列化的 String Item 值。组件标识符 MAY 还可以包括使用 parameters ABNF 规则序列化的已定义参数。第 2.3 节 中定义的签名参数行遵循相同模式,但组件标识符是一个具有固定值且无参数的 String Item,而组件值始终是带有可选参数的 Inner List。

注意,这意味着组件名称本身的序列化被双引号包围,参数随后以分号分隔列表的形式附加,例如 "cache-control""@authority""@signature-params",或 "example-dictionary";key="foo"

输出是构成签名基础的字节的有序集合,符合下列 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

为创建签名基础,签名者或验证者按照下述算法对签名的已覆盖组件中每个组件标识符(包括其参数)串联条目。所有在下文描述的错误MUST立即导致算法失败,并且不输出签名基础。

  1. 令输出为空字符串。
  2. 对于已覆盖组件集合中的每个消息组件项(按顺序):

    11.
    如果该组件标识符(包括其参数)已被添加到签名基础中,则产生错误。
    12.
    将已覆盖组件的组件标识符按 component-identifier ABNF 规则序列化后附加。注意该序列化将组件名称置于双引号内,并在引号外追加任何参数。
    13.
    附加一个冒号(:)。
    14.
    附加一个空格(" ")。
    15.

    确定该组件标识符的组件值。

    • 如果组件标识符具有一个未知的参数,则产生错误。
    • 如果组件标识符具有彼此互不兼容的参数(例如 bssf),则产生错误。
    • 如果组件标识符包含 req 参数并且目标消息是请求,则产生错误。
    • 如果组件标识符包含 req 参数并且目标消息是响应,则该组件值的上下文为触发该响应消息的相关请求消息。否则,组件值的上下文为目标消息本身。
    • 如果组件名称以 "at"(@)字符开头,则按为该派生组件在 第 2.2 节 中定义的特定规则从消息中派生该组件的值,并处理任何已知的有效参数。如果派生组件名未知或无法派生值,则产生错误。
    • 如果组件名称不以 "at"(@)字符开头,则按 第 2.1 节 所述对 HTTP 字段值进行规范化,包括处理任何已知的有效参数。如果在上下文中无法找到该字段或无法获取该值,则产生错误。
    16.
    附加该已覆盖组件的规范化组件值。
    17.
    附加一个换行符(\n)。
  3. 按下述方式,根据 signature-params-line 规则附加签名参数组件(第 2.3 节):

    11.
    将签名参数的组件标识符按 component-identifier 规则序列化后附加,即确切的值 "@signature-params"(包括双引号)。
    12.
    附加一个冒号(:)。
    13.
    附加一个空格(" ")。
    14.
    附加签名参数的规范化组件值,如 第 2.3 节 中定义,即带参数的 Inner List 结构化字段值。
  4. 如果输出字符串包含任何非 ASCII 字符 [ASCII],则产生错误。
  5. 返回输出字符串。

如果已覆盖组件引用一个无法解析为消息中组件值的组件标识符,实现MUST产生错误并且不得创建签名基础。此类情形包括但不限于下列情况:

  • 签名者或验证者不理解该派生组件名称。
  • 组件名称标识的字段在消息中不存在或其值格式错误。
  • 组件标识符包含一个未知的或不适用于其所附组件标识符的参数。
  • 组件标识符指示使用结构化字段序列化(通过 sf 参数),但该字段被已知不是结构化字段或实现不知其结构化字段类型。
  • 组件标识符是字典成员标识符,但引用的字段在消息中不存在、不是字典结构化字段、或其值格式错误。
  • 组件标识符是字典成员标识符或命名查询参数标识符,但引用的成员在组件值中不存在或其值格式错误。例如,标识符为 "example-dict";key="c",而 Example-Dict 头字段的值为 a=1, b=2,其中没有 c 值。

在下列非规范性示例中,被签名的 HTTP 消息为如下请求:

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"}

已覆盖组件由派生组件 @method@authority@path 组成,后接 HTTP 头字段 Content-DigestContent-LengthContent-Type,按该顺序。签名参数包括创建时间戳 1618884473 与密钥标识符 test-key-rsa-pss。注意此处未给出显式的 alg 参数,因为应用已知验证者将基于所标识的密钥使用 RSA-PSS 算法。具有这些参数的该消息的签名基础为:

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"

图 1:非规范性示例签名基础

注意,上述示例签名基础并不包含结束所示示例的最后换行符,规范中其它位置展示的示例签名基础亦是如此。


3. HTTP 消息签名

HTTP 消息签名是在从 HTTP 消息的若干组件以及关于签名自身的元数据生成的字符串上计算的签名。当成功针对某个 HTTP 消息验证时,HTTP 消息签名就为该消息与用于生成签名的消息在所签名的那部分消息组件上提供了密码学上的语义等价性证明。

3.1. 创建签名

创建 HTTP 消息签名的过程以签名上下文(包括目标消息)和应用要求为输入。输出是签名值和一组签名参数,这些信息可以通过将它们添加到消息中来传达给验证者。

为了创建签名,签名者MUST应用下述算法:

  1. 签名者从潜在签名算法集合中为签名选择一个 HTTP 签名算法和用于签名的密钥材料。潜在算法集合由应用决定,超出本文档范围。签名者MUST选择适合签名算法并满足算法定义的任何要求(例如密钥大小或格式)的密钥材料。签名者选择算法与密钥材料的机制超出本文档范围。
  2. 签名者将签名的创建时间设置为当前时间。
  3. 如适用,签名者将签名的过期时间属性设置为签名到期的时间。过期时间是对验证者的提示,表明签名者在该时刻之后不再为签名担保。适当的过期长度以及对该参数的处理要求由应用具体决定。
  4. 签名者创建表示将由签名覆盖的消息组件的组件标识符的有序集合,并向该集合附加签名元数据参数。该集合的序列化值随后将用作 Signature-Input 字段的值,如 第 4.1 节 所述。

    • 一旦选择了已覆盖组件的顺序,该顺序在签名的整个生命周期内MUST NOT改变。
    • 每个已覆盖组件标识符MUST要么是签名上下文中的一个 HTTP 字段(参见 第 2.1 节),要么是第 2.2 节 或 “HTTP Signature Derived Component Names” 注册表中列出的一个派生组件。
    • 请求的签名者SHOULD在已覆盖组件中包含部分或全部消息控制数据,例如 @method@authority@target-uri 或它们的某种组合。
    • 签名者SHOULD包含 created 签名元数据参数以指示签名创建时间。
    • @signature-params 派生组件标识符MUST NOT出现在已覆盖组件标识符列表中。该派生组件必须始终作为签名基础的最后一行,以确保签名始终覆盖其自身的元数据且元数据不得被替换。
    • 关于在该集合中包含哪些内容及其顺序的进一步指导超出本文档范围。
  5. 签名者使用这些参数和签名基础创建算法(参见 第 2.5 节)创建签名基础。
  6. 签名者使用 HTTP_SIGN 原语函数,用签名者选择的签名算法和所选的密钥材料对签名基础进行签名。HTTP_SIGN 原语和若干具体签名算法的应用在 第 3.3 节 中定义。
  7. 签名函数的字节数组输出即为要包含在 第 4.2 节 中 Signature 字段的 HTTP 消息签名输出值。

例如,基于 第 2.5 节 中的示例 HTTP 消息和签名参数,示例签名基础用 test-key-rsa-pss 密钥(见 附录 B.1.2)和 第 3.3.1 节 中描述的 RSASSA-PSS 算法进行签名,得到以下以 Base64 编码的消息签名输出值:

NOTE: '\' line wrapping per RFC 8792

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

图 2:非规范性示例签名值

注意,此处使用的 RSA-PSS 算法为非确定性算法,这意味着每次运行算法都会生成不同的签名值。所提供的签名值可以针对给定密钥进行验证,但新生成的签名值不应与示例匹配。参见 第 7.3.5 节

3.2. 验证签名

验证 HTTP 消息签名的过程以签名上下文(包括目标消息,特别是其 Signature 与 Signature-Input 字段)和应用要求为输入。验证的输出要么为成功验证,要么为错误。

为了验证签名,验证者MUST应用下述算法:

  1. Sections 第 4.1 节第 4.2 节 所述解析 Signature 与 Signature-Input 字段,并提取要验证的签名及其标签。

    11.
    如果存在多个签名值,基于验证者的策略与配置确定应处理哪一个签名。如果找不到适用的签名,则产生错误。
    12.
    如果所选的 Signature 字段值没有相应的 Signature-Input 字段值(即具有相同标签的项),则产生错误。
  2. 将所选 Signature-Input 字段的值解析为带参数的 Inner List,以获取要验证的已覆盖组件的有序列表和该签名的签名参数。
  3. 解析对应的 Signature 字段的值以获取要验证的签名字节数组值。
  4. 检查签名参数,以确认签名满足本文档以及应用定义的任何附加要求,例如哪些消息组件必须由签名覆盖(参见 第 3.2.1 节)。
  5. 确定该签名的验证密钥材料。如果密钥材料通过静态配置或外部协议协商等外部方式已知,验证者将使用适用技术从这些外部知识中获取密钥材料。如果签名参数中标识了密钥,验证者将解引用该密钥标识符以获得适用于该签名的密钥材料。验证者必须确定该密钥材料在该签名所呈现的上下文中的可信性。如果标识了一个验证者不认识或不信任,或与预配置不匹配的密钥,则验证MUST失败。
  6. 确定用于验证的算法:

    11.
    从应用已知的允许算法集合开始。如果下列任一步选择的算法不在此集合中,则签名验证失败。
    12.
    如果算法通过静态配置或外部协议协商等外部方式已知,验证者将使用该算法。
    13.
    如果算法可以从密钥材料中确定,例如通过键值本身的算法字段,验证者将使用该算法。
    14.
    如果算法在签名参数中显式声明为来自“HTTP Signature Algorithms”注册表的值,验证者将使用所引用的算法。
    15.
    如果算法在多处指定(例如静态配置、签名参数中的 alg,以及密钥材料本身),则解析出的算法MUST一致。如果算法不一致,验证者MUST使验证失败。
  7. 使用接收到的 HTTP 消息和已解析的签名参数重建签名基础,使用 第 2.5 节 定义的算法。@signature-params 的输入值为该签名的 Signature-Input 字段的值,按 第 2.3 节 中描述的规则序列化。注意这不包括 Signature-Input 字段中的签名标签。
  8. 如果密钥材料适合该算法,应用适当的 HTTP_VERIFY 密码学验证算法,对重建的签名基础、密钥材料和签名值进行验证。HTTP_VERIFY 原语和若干具体算法在 第 3.3 节 中定义。
  9. 验证算法函数的结果即为密码学验证函数的最终结果。

如果上述任一步失败或产生错误,则签名验证失败。

例如,使用标签为 sig1 的签名、test-key-rsa-pss 密钥(见 附录 B.1.2)以及 第 3.3.1 节 中描述的 RSASSA-PSS 算法,对下列消息的签名进行验证:

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"}

在附加要求至少签名 method、authority、path、content-digest、content-length 和 content-type 项,并且签名创建时间戳在验证时足够新鲜的情形下,验证通过。

3.2.1. 强制执行应用要求

本文档中规定的验证要求旨在作为一组基线限制,通常适用于所有用例。使用 HTTP 消息签名的应用MAY为其用例适当地施加超出本文档规定的附加要求。

一些非规范性的附加要求示例包括:

  • 要求对特定集合的头字段进行签名(例如 Authorization、Content-Digest)。
  • 对从 created 时间戳起的最大签名年龄进行强制执行。
  • 拒绝超过 expires 时间戳所指示的签名过期时间。注意过期时间是签名者给出的提示,验证者总是可以在过期之前拒绝签名。
  • 禁止某些签名元数据参数,例如当算法由密钥信息确定时禁止使用 alg 参数进行运行时算法信号。
  • 确保成功解引用 keyid 参数以获得有效且适当的密钥材料。
  • 禁止使用某些算法或强制使用特定算法。
  • 要求密钥具有某种大小(例如 2048 位对比 1024 位)。
  • 强制要求 nonce 参数的唯一性。
  • 要求对 tag 参数使用应用特定的值。

鼓励并预期应用定义特定的要求。当应用定义了附加要求时,其MUST在签名验证过程中强制执行这些要求;如果签名不符合应用要求,则签名验证MUST失败。

应用MUST强制执行本文档中定义的要求。无论何种用例,应用MUST NOT接受不符合这些要求的签名。

3.3. 签名算法

HTTP 消息签名MUST使用适合密钥材料、环境以及签名者和验证者需求的密码学数字签名或 MAC 方法。本规范并不严格限制可用的签名算法,任何满足这些基本要求的签名算法MAY由 HTTP 消息签名的应用使用。

对于每个签名方法,HTTP_SIGN 以第 2.5 节 定义的签名基础作为字节数组输入(记作 M)和签名密钥材料(记作 Ks),并输出结果签名的字节数组(记作 S):

HTTP_SIGN (M, Ks)  ->  S

对于每个验证方法,HTTP_VERIFY 以重建的签名基础作为字节数组输入(M)、验证密钥材料(Kv)以及要验证的呈现签名的字节数组(S),并输出布尔型的验证结果(V):

HTTP_VERIFY (M, Kv, S) -> V

下列各节包含若干常见签名算法,并示范这些密码学原语如何映射到上述 HTTP_SIGNHTTP_VERIFY 定义。如何选择方法可通过显式算法(alg)签名参数(见 第 2.3 节)、通过引用密钥材料或由签名者与验证者之间的相互约定来传达。通过 alg 参数选择的签名算法MUST使用来自 “HTTP Signature Algorithms” 注册表(参见 第 6.2 节)的值。

3.3.1. 使用 SHA-512 的 RSASSA-PSS

要使用该算法签名,签名者应用在 [RFC8017] 中定义的 RSASSA-PSS-SIGN (K, M) 函数,使用签名者的私钥(K)和签名基础(M)(参见 第 2.5 节)。掩码生成函数为 MGF1,使用 [RFC8017] 中指定的 SHA-512 哈希函数 [RFC6234]。盐长度(sLen)为 64 字节。哈希函数(Hash)SHA-512 [RFC6234] 应用于签名基础以创建将被数字签名的摘要内容。生成的签名内容字节数组(S)即为在 第 3.1 节 中使用的 HTTP 消息签名输出。

要使用该算法进行验证,验证者应用在 [RFC8017] 中定义的 RSASSA-PSS-VERIFY ((n, e), M, S) 函数,使用验证密钥材料的公钥部分(n, e)和按 第 3.2 节 重建的签名基础(M)。掩码生成函数为 MGF1,使用 SHA-512 哈希函数。盐长度(sLen)为 64 字节。哈希函数 SHA-512 用于对签名基础创建摘要内容并应用于验证函数。验证者按 第 3.2 节 中所述提取要验证的 HTTP 消息签名(S)。验证函数的结果表明所呈现的签名是否有效。

注意 RSASSA-PSS 算法的输出是非确定性的;因此,重新计算签名基础并比较结果与现有签名是不正确的。相反,需要使用此处定义的验证算法。参见 第 7.3.5 节

运行时可使用签名参数 alg 的值 rsa-pss-sha512 来指示使用该算法。

3.3.2. 使用 SHA-256 的 RSASSA-PKCS1-v1_5

要使用该算法签名,签名者应用在 [RFC8017] 中定义的 RSASSA-PKCS1-V1_5-SIGN (K, M) 函数,使用签名者的私钥(K)和签名基础(M)。哈希函数 SHA-256 [RFC6234] 应用于签名基础以创建将被数字签名的摘要内容。生成的签名内容字节数组(S)即为在 第 3.1 节 中使用的 HTTP 消息签名输出。

要使用该算法验证,验证者应用在 [RFC8017] 中定义的 RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S) 函数,使用验证密钥材料的公钥部分(n, e)和按 第 3.2 节 重建的签名基础(M)。哈希函数 SHA-256 用于对签名基础创建摘要内容并应用于验证函数。验证者按 第 3.2 节 所述提取要验证的 HTTP 消息签名(S)。验证函数的结果表明所呈现的签名是否有效。

运行时可使用签名参数 alg 的值 rsa-v1_5-sha256 来指示使用该算法。

3.3.3. 使用 SHA-256 的 HMAC

要使用此算法进行签名和验证,签名者对签名基础(text)(参见 第 2.5 节)与共享签名密钥(K)应用 HMAC 函数(参见 [RFC2104])。哈希函数 SHA-256 [RFC6234] 应用于签名基础以创建用于 HMAC 的摘要内容,从而得到签名结果。

在签名操作中,得到的值即为用于 第 3.1 节 中所述的 HTTP 消息签名输出。

在验证操作中,验证者按 第 3.2 节 的描述提取要验证的 HTTP 消息签名(S)。将 HMAC 函数的输出按字节与该 HTTP 消息签名的值逐字节比较,比对结果决定所呈现签名的有效性。

运行时可使用签名参数 alg 的值 hmac-sha256 来指示使用该算法。

3.3.4. 使用 P-256 曲线与 SHA-256 的 ECDSA

要使用此算法进行签名,签名者按 [FIPS186-5] 中定义的 ECDSA 签名算法,使用 P-256 曲线、签名者的私钥以及签名基础(参见 第 2.5 节)。哈希函数 SHA-256 [RFC6234] 应用于签名基础以创建被数字签名的摘要内容(记作 M)。签名算法返回两个整数值:rs。这两个值都按大端无符号整数编码,分别零填充至各 32 个八位字节。将这些编码值串联为一个 64 字节数组,先为 r 的编码值,随后为 s 的编码值。由此得到的 (r, s) 串联即为在 第 3.1 节 中使用的 HTTP 消息签名输出的字节数组。

要使用此算法进行验证,验证者按 [FIPS186-5] 中定义的 ECDSA 签名验证算法,使用验证密钥材料的公钥部分以及按 第 3.2 节 重建的签名基础进行验证。哈希函数 SHA-256 [RFC6234] 应用于签名基础以创建用于验证函数的摘要内容(记作 M)。验证者按 第 3.2 节 的描述提取要验证的 HTTP 消息签名(S)。该值是一个 64 字节数组,由 rs 的编码值按顺序串联组成,两者均按大端无符号整数编码并零填充至各 32 个八位字节。得到的签名值 (r, s) 被用作签名验证函数的输入。验证函数的结果表明所呈现签名是否有效。

注意 ECDSA 签名算法的输出是非确定性的;因此,重新计算签名基础上的新签名并将结果与现有签名比较是不正确的。应使用此处定义的验证算法。参见 第 7.3.5 节

运行时可使用签名参数 alg 的值 ecdsa-p256-sha256 来指示使用该算法。

3.3.5. 使用 P-384 曲线与 SHA-384 的 ECDSA

要使用此算法进行签名,签名者按 [FIPS186-5] 中定义的 ECDSA 签名算法,使用 P-384 曲线、签名者的私钥以及签名基础(参见 第 2.5 节)。哈希函数 SHA-384 [RFC6234] 应用于签名基础以创建被数字签名的摘要内容(记作 M)。签名算法返回两个整数值:rs。这两个值都按大端无符号整数编码,分别零填充至各 48 个八位字节。将这些编码值串联为一个 96 字节数组,先为 r 的编码值,随后为 s 的编码值。由此得到的 (r, s) 串联即为在 第 3.1 节 中使用的 HTTP 消息签名输出的字节数组。

要使用此算法进行验证,验证者按 [FIPS186-5] 中定义的 ECDSA 签名验证算法,使用验证密钥材料的公钥部分以及按 第 3.2 节 重建的签名基础进行验证。哈希函数 SHA-384 [RFC6234] 应用于签名基础以创建用于验证函数的摘要内容(记作 M)。验证者按 第 3.2 节 的描述提取要验证的 HTTP 消息签名(S)。该值是一个 96 字节数组,由 rs 的编码值按顺序串联组成,两者均按大端无符号整数编码并零填充至各 48 个八位字节。得到的签名值 (r, s) 被用作签名验证函数的输入。验证函数的结果表明所呈现签名是否有效。

注意 ECDSA 签名算法的输出是非确定性的;因此,重新计算签名基础上的新签名并将结果与现有签名比较是不正确的。应使用此处定义的验证算法。参见 第 7.3.5 节

运行时可使用签名参数 alg 的值 ecdsa-p384-sha384 来指示使用该算法。

3.3.6. 使用 edwards25519 的 EdDSA

要使用此算法进行签名,签名者按 [RFC8032] 中定义的 Ed25519 算法(参见 第 5.1.6 节),使用签名者的私钥和签名基础作为输入消息(记作 M),且不使用预哈希函数。签名为按照 RFC8032 指定的 64 字节的 RS 串联,该字节数组作为在 第 3.1 节 中使用的 HTTP 消息签名输出。

要使用此算法进行验证,验证者按 [RFC8032] 中定义的 Ed25519 验证算法(参见 第 5.1.7 节),使用验证密钥材料的公钥部分(记作 A)以及按 第 3.2 节 重建的签名基础作为输入消息(记作 M),且不使用预哈希函数。要验证的签名被视为按照 RFC8032 指定的 64 字节 RS 串联。验证函数的结果表明所呈现签名是否有效。

运行时可使用签名参数 alg 的值 ed25519 来指示使用该算法。

3.3.7. JSON Web Signature (JWS) 算法

如果签名算法为 JOSE(JSON Object Signing and Encryption)签名算法,并来自由 [RFC7518] 建立的 “JSON Web Signature and Encryption Algorithms” 注册表,则 JWS 算法定义决定了签名和哈希算法在签名与验证时的应用方式。

无论签名还是验证,HTTP 消息的签名基础(参见 第 2.5 节)都作为整个 “JWS Signing Input”。不使用 JOSE 头(如 [JWS][RFC7517] 中定义的 JWK 头),且在应用算法前不先对签名基础进行 Base64 编码。JWS 签名的输出在进行 JOSE 所用的 Base64url 编码之前被视作字节数组。

JWS 算法不得为 "none",且不得为任何 JOSE 实现要求标注为 "Prohibited" 的算法。

来自 “JSON Web Signature and Encryption Algorithms” 注册表的 JWA 值不作为签名参数包含。通常,JWS 算法可通过 JSON Web Keys (JWKs) 或其它 JOSE 实现常用的机制进行信号传达。实际上,JWA 值并未注册到 “HTTP Signature Algorithms” 注册表(参见 第 6.2 节),因此在使用 JOSE 签名算法时不会使用显式的 alg 签名参数。


4. 在消息中包含消息签名

HTTP 消息签名可以通过 Signature-Input 与 Signature 字段包含在 HTTP 消息内,这两个字段在本规范中有所定义。

Signature-Input 字段标识已覆盖的组件和描述签名如何生成的参数,而 Signature 字段包含签名值。每个字段 可以 包含多个带标签的值。

HTTP 消息签名由消息内的一个标签标识。该标签在给定 HTTP 消息中必须唯一,并且必须在 Signature-Input 字段和 Signature 字段中都使用。标签由签名者选择,除非协议协商(例如第 5 节 描述的情况)另有规定。

HTTP 消息签名必须同时使用 Signature-Input 字段和 Signature 字段,且每个字段必须包含相同的标签。在一个字段中存在但在另一个字段中不存在的标签被视为错误。

4.1. Signature-Input HTTP 字段

Signature-Input 字段是一个字典结构化字段(参见 [STRUCTURED-FIELDS] 第 3.2 节),包含来自 HTTP 消息中组件的一项或多项消息签名的元数据。字典的每个成员描述一个单独的消息签名。成员的键为在 HTTP 消息内唯一标识该消息签名的标签。成员的值为作为 Inner List 序列化的已覆盖组件有序集合,并包含由该标签标识的所有签名元数据参数:

NOTE: '\' line wrapping per RFC 8792

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

为便于签名验证,Signature-Input 字段值必须包含在生成签名基础的 @signature-params 值(见 第 2.3 节)中使用的相同序列化值。注意在结构化字段值中,列表顺序和参数顺序必须被保留。

签名者可以将 Signature-Input 字段作为尾部(trailer)包含,以便在处理完消息内容后进行签名。然而,由于中间体可能丢弃 trailers(参见 [HTTP]),因此建议仅将 Signature-Input 字段作为头字段包含,以避免签名被意外从消息中剥离。

单个 HTTP 消息中可以包含多个 Signature-Input 字段。签名标签在所有字段值中必须唯一。

4.2. Signature HTTP 字段

Signature 字段是一个字典结构化字段(参见 [STRUCTURED-FIELDS] 第 3.2 节),包含从目标消息的签名上下文生成的一个或多个消息签名。成员的键为在 HTTP 消息内唯一标识该消息签名的标签。成员的值为包含由该标签标识的消息签名值的 Byte Sequence:

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==:

签名者可以将 Signature 字段作为尾部包含,以便在处理完消息内容后进行签名。然而,由于中间体可能丢弃 trailers(参见 [HTTP]),因此建议仅将 Signature 字段作为头字段包含,以避免签名被意外从消息中剥离。

单个 HTTP 消息中可以包含多个 Signature 字段。签名标签在所有字段值中必须唯一。

4.3. 多个签名

单个消息中可以包含多个不同的签名。每个不同的签名必须具有唯一的标签。这些多个签名可以由同一签名者添加,也可以来自多个不同的签名者。例如,签名者可能对相同的消息组件使用不同的密钥或算法添加多个签名,以支持具有不同能力的验证者;或者反向代理在将请求转发给服务主机时,可能在转发时在字段中包含关于客户端的信息,并对客户端的原始签名值进行签名。

下列非规范性示例以客户端的已签名请求开始。反向代理接收该请求并验证客户端的签名:

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-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"}

代理随后在将消息转发给源服务器之前更改了消息,修改了目标主机并添加了 [RFC7239] 中定义的 Forwarded 头字段:

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"
Signature: sig1=:X5spyd6CFnAG5QnDyHfqoSNICd+BUP4LYMz2Q0JXlb//4Ijpzp\
  +kve2w4NIyqeAuM7jTDX+sNalzA8ESSaHD3A==:

{"hello": "world"}

代理能够验证进入的客户端签名,并通过在将消息转发给源服务器之前对新消息添加其自己的签名来向源服务器说明其所转发请求的性质。代理还包含原始消息中与源服务器处理相关的所有元素。在许多情况下,代理会希望覆盖与客户端签名相同的所有组件,正如下面的示例所示。请注意在本例中,代理对其更改后的新 authority 值进行签名,同时将 Forwarded 头字段也加入到其自己的签名值中。代理标识其自己的密钥和算法,并在此示例中为签名添加了过期时间,以便向下游系统表明代理不会在该短时间窗口之后对该已签名消息进行担保。由此得到的签名基础为:

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

以及一个签名输出值:

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==

这些值由代理添加到 HTTP 请求消息中。原始签名以标签 sig1 包含,反向代理的签名以标签 proxy_sig 包含。代理使用密钥 test-key-rsa 并采用 rsa-v1_5-sha256 签名算法来创建其签名,而客户端的原始签名使用密钥 test-key-rsa-pss 与 RSA-PSS 签名算法:

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"}

尽管代理还可以在新签名的已覆盖组件中包含客户端原始消息的 Signature 字段值和 Signature-Input 字段,但由于在对签名值再次签名方面存在已知弱点(见 第 7.3.7 节),此做法不建议。代理能够验证客户端的签名;代理对消息所做的更改会在源服务器看到该消息时使现有签名失效。在本例中,源服务器可能在其签名上下文中拥有额外信息以解释 authority 的变化,但此做法需要额外的配置并需谨慎处理(见 第 7.4.4 节)。在其它应用中,源服务器可能无法验证原始签名本身,但仍希望验证代理已对客户端签名做出适当的验证。需要以安全方式表明签名已被成功处理或接收的应用需仔细指定替代机制以安全地发送此类信号。


5. 请求签名

虽然签名者可以在未经提示的情况下将签名附加到请求或响应,但潜在的验证者通常希望通过使用 Accept-Signature 字段来表明其期望签名者出具签名。

当在 HTTP 请求消息中发送 Accept-Signature 字段时,该字段表示客户端希望服务器使用所标识的参数对响应进行签名,并且目标消息为对此请求的响应。支持此类签名协商的资源所返回的所有响应应当不可缓存,或包含一个列出 Accept-Signature 的 Vary 头字段,以防止缓存返回针对不同请求所用签名的响应。

当在 HTTP 响应消息中使用 Accept-Signature 字段时,该字段表示服务器希望客户端在其下一次向服务器发出的请求中使用所标识的参数进行签名,且目标消息为客户端的下一次请求。客户端可以选择以后也继续以相同方式对发往相同服务器的请求进行签名。

Accept-Signature 字段的目标消息必须包含 Accept-Signature 字段中指示的所有带标签签名,且每个签名都覆盖 Accept-Signature 字段中所标识的相同组件。

Accept-Signature 字段的发送者必须仅包含适用于目标消息类型的标识符。例如,如果目标消息是请求,则已覆盖组件不能包含 @status 派生组件标识符。

5.1. Accept-Signature 字段

Accept-Signature 字段是一个字典结构化字段(在 [STRUCTURED-FIELDS] 第 3.2 节 定义),包含针对目标 HTTP 消息的一个或多个所请求消息签名的元数据。每个字典成员描述一个签名请求。成员的键是在目标 HTTP 消息上下文中唯一标识所请求消息签名的标签。

成员的值为目标消息所需已覆盖组件的序列化表示,包括任何允许的组件元数据参数,使用在 第 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"

组件标识符列表指示要包含在所请求签名中的确切组件标识符集合,包括所有适用的组件参数。

签名请求可以包含指示期望签名者行为的签名元数据参数。本规范定义了以下行为:

created:
请求签名者生成并包含创建时间。作为签名请求发送时,该参数无关联值。
expires:
请求签名者生成并包含过期时间。作为签名请求发送时,该参数无关联值。
nonce:
请求签名者在目标签名中将此参数的值作为签名的 nonce 包含。
alg:
请求签名者使用来自 “HTTP Signature Algorithms” 注册表中所指示的签名算法来创建目标签名。
keyid:
请求签名者使用所指示的密钥材料来创建目标签名。
tag:
请求签名者在目标签名中将此参数的值作为签名的 tag 包含。

5.2. 处理 Accept-Signature

Accept-Signature 字段的接收方应按下述方式满足该头字段:

  1. 将字段值解析为一个 Dictionary。
  2. 对于字典的每个成员:

    11.
    将该键作为输出签名的标签,如 第 4.1 节 所述。
    12.
    解析该成员的值以获取已覆盖组件标识符集合。
    13.
    确定已覆盖组件是否适用于目标消息。若不适用,则该过程失败并返回错误。
    14.
    处理所请求的参数,例如签名算法与密钥材料。若任何所请求参数无法满足,或所请求参数与被认为适合目标消息的参数冲突,则该过程失败并返回错误。
    15.
    选择并生成完成签名所需的任何附加参数。
    16.
    对目标消息创建 HTTP 消息签名。
    17.
    创建 Signature-Input 与 Signature 字段值,并将它们与该标签相关联。
  3. 可选地创建任何额外的、标签唯一且不在 Accept-Signature 字段中出现的 Signature-Input 与 Signature 字段值。
  4. 将所有带标签的 Signature-Input 与 Signature 字段值合并,并将这两个字段附加到目标消息上。

通过此过程,应用于目标消息的签名必须具有相同的标签,必须包含相同的已覆盖组件集合,必须处理所有被请求的参数,并且可以拥有附加参数。

Accept-Signature 字段的接收者可以忽略任何不符合应用参数的签名请求。

目标消息可以包含 Accept-Signature 字段未指定的额外签名。例如,为了覆盖额外的消息组件,签名者可以创建第二个签名,该签名包含附加组件以及所请求签名的签名输出。


6. IANA 注意事项

IANA 已根据下列各节更新了一个注册表并创建了四个新注册表。

6.1. HTTP 字段名注册

IANA 已按如下更新 “超文本传输协议 (HTTP) 字段名注册表” 中的条目:

Table 1: Updates to the Hypertext Transfer Protocol (HTTP) Field Name Registry
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

6.2. HTTP 签名算法注册表

本文档定义了 HTTP 签名算法,IANA 已为此创建并维护了名为 “HTTP Signature Algorithms” 的新注册表。该注册表的初始值列在 第 6.2.2 节。对现有条目的后续分配与修改应通过 Specification Required 注册策略进行 ([RFC8126])。

该注册表中列出的算法为应用提供了可与本规范一起使用的一些可能的密码学算法,但这些条目既不代表可能算法的穷尽列表,也不表示针对任何特定应用场景的适用性。应用可以实现任何满足其需求的算法,前提是签名者和验证者能安全且确定地就该算法参数达成一致。当应用需要在运行时通过 alg 签名参数指示特定算法时,该注册表在参数值与具体算法之间提供映射。然而,使用 alg 参数时需谨慎,以避免各种算法混淆和替换攻击,详见 第 7 节

Status 值应反映标准化状态以及相关利益群体(如 IETF 或安全相关标准化组织)的一般意见。当算法首次注册时,指定专家应在有共识认为算法总体上是安全时将 Status 字段设置为 "Active",若尚未达成该共识则设为 "Provisional"(例如用于实验性算法)。"Provisional" 并不意味着该算法已知不安全,而是表示该算法尚未就其属性达成共识。如果将来发现某注册算法存在缺陷,可更新注册表条目并将算法标注为 "Deprecated" 以表示该算法已被发现存在问题。该状态不会阻止应用使用该算法;它只是为应用提供关于算法可能已知问题的警告。指定专家还应确保注册包含关于 Status 值的解释和参考,尤其对已弃用算法非常重要。

指定专家预计将执行下列操作:

  • 确保被注册的算法标识符所引用的算法以所有参数(例如盐、哈希、所需密钥长度)完整定义。
  • 确保算法定义完整指定了 HTTP_SIGNHTTP_VERIFY 原语函数,包括如何将所有定义的输入与输出映射到底层密码学算法上。
  • 拒绝任何作为现有注册别名的注册。
  • 确保所有注册遵循 第 6.2.1 节 中给出的模板;包括确保名称长度不过长且仍然唯一且易识别。

本规范通过在标识符字符串中包含主要参数来创建算法标识符,以便使算法名称唯一且易识别。然而,该注册表中的算法标识符应被视为整体字符串值,而非各部分的组合。换言之,实现者应理解 rsa-pss-sha512 指代一整套特定参数(哈希、掩码、盐等)所定义的算法;实现者不应通过解析标识符字符串来推断签名算法的参数,且一个参数组合的注册并不意味着其它组合也被注册。

6.2.1. 注册模板

Algorithm Name:
HTTP 签名算法的标识符。名称必须为符合 sf-string ABNF 规则的 ASCII 字符串(参见 [STRUCTURED-FIELDS] 第 3.3.3 节),且不应超过 20 个字符。该标识符在注册表范围内必须唯一。
Description:
对用于对签名基础进行签名的算法的简要描述。
Status:
算法的状态。必须以下列值之一开头,并可以包含附加说明文本。选项为:
"Active":
适用于没有已知问题的算法。算法已被完全规范化,其安全属性可被理解。
"Provisional":
适用于未经证实的算法。算法已被完全规范化,但其安全属性未知或未被证明。
"Deprecated":
适用于具有已知安全问题的算法。该算法不再推荐用于通用用途,且在某些已知情况下可能不安全。
Reference:
规范该算法的文档参考,最好包含可检索该文档的 URI。也可以包含相关章节的指示,但并非必需。

6.2.2. 初始内容

下表包含 “HTTP Signature Algorithms” 注册表的初始内容。

Table 2: Initial Contents of the HTTP Signature Algorithms Registry
Algorithm Name Description Status Reference
rsa-pss-sha512 RSASSA-PSS using SHA-512 Active Section 3.3.1 of RFC 9421
rsa-v1_5-sha256 RSASSA-PKCS1-v1_5 using SHA-256 Active Section 3.3.2 of RFC 9421
hmac-sha256 HMAC using SHA-256 Active Section 3.3.3 of RFC 9421
ecdsa-p256-sha256 ECDSA using curve P-256 DSS and SHA-256 Active Section 3.3.4 of RFC 9421
ecdsa-p384-sha384 ECDSA using curve P-384 DSS and SHA-384 Active Section 3.3.5 of RFC 9421
ed25519 EdDSA using curve edwards25519 Active Section 3.3.6 of RFC 9421

6.3. HTTP 签名元数据参数注册表

本文档定义了签名参数结构(见 第 2.3 节),该结构可包含关于消息签名的元数据参数。IANA 已创建并维护一个名为 “HTTP Signature Metadata Parameters” 的新注册表,用于记录和维护在签名参数结构的成员值中定义可用的参数集合。该注册表的初始值列在 第 6.3.2 节。对现有条目的后续分配与修改应通过 Expert Review 注册策略进行 ([RFC8126])。

指定专家预计将执行下列操作:

  • 确保名称遵循 第 6.3.1 节 中给出的模板;包括确保名称长度不过长且在定义的功能范围内仍然唯一且可识别。
  • 确保所定义的功能清晰且不会与其它已注册参数冲突。
  • 确保对元数据参数的定义包括其在正常签名过程中的行为以及在 Accept-Signature 字段中使用时的行为。

6.3.1. 注册模板

Name:
HTTP 签名元数据参数的标识符。名称必须为符合 key ABNF 规则的 ASCII 字符串(参见 [STRUCTURED-FIELDS] 第 3.1.2 节),且不应超过 20 个字符。该标识符在注册表范围内必须唯一。
Description:
对元数据参数及其代表内容的简要描述。
Reference:
规范该参数的文档参考,最好包含可检索该文档的 URI。也可以包含相关章节的指示,但并非必需。

6.3.2. 初始内容

下表包含 “HTTP Signature Metadata Parameters” 注册表的初始内容。表中的每一行代表注册表中的一个独立条目。

Table 3: Initial Contents of the HTTP Signature Metadata Parameters Registry
Name Description Reference
alg 显式声明的签名算法 Section 2.3 of RFC 9421
created 签名创建的时间戳 Section 2.3 of RFC 9421
expires 建议的签名过期时间戳 Section 2.3 of RFC 9421
keyid 用于创建该签名的签名与验证密钥的标识符 Section 2.3 of RFC 9421
nonce 一次性使用的 nonce 值 Section 2.3 of RFC 9421
tag 签名的应用特定标签 Section 2.3 of RFC 9421

6.4. HTTP 签名派生组件名称注册表

本文档定义了对 HTTP 消息组件进行规范化的方法,包括可从目标消息上下文(而非 HTTP 字段)派生的组件。这些派生组件由一个唯一的字符串标识,称为组件名称。派生组件的名称始终以 "at" (@) 符号开头以将其与 HTTP 字段名区分开来。IANA 已创建并维护名为 “HTTP Signature Derived Component Names” 的新注册表,用于记录和维护非字段组件名称及用于产生其相关组件值的方法。该注册表的初始值列在 第 6.4.2 节。对现有条目的后续分配与修改应通过 Expert Review 注册策略进行 ([RFC8126])。

指定专家预计将执行下列操作:

  • 确保名称遵循 第 6.4.1 节 中给出的模板;包括确保名称长度不过长且在定义的功能范围内仍然唯一且可识别。
  • 确保注册请求所表示的组件值可以从目标 HTTP 消息确定性地派生。
  • 确保对注册请求所定义的任何参数有清晰记录,并说明其对组件值的影响。

指定专家应确保某注册项与现有派生组件定义足够区分,以证明其注册的合理性。

当将注册条目的状态设置为 "Deprecated" 时,指定专家应确保记录弃用原因以及有关如何迁移离开弃用功能的说明。

6.4.1. 注册模板

Name:
HTTP 派生组件的名称。名称必须以 "at" (@) 字符开头,随后是仅包含小写字母("a"-"z")、数字("0"-"9")和连字符("-")的 ASCII 字符串,且不应超过 20 个字符。该名称在注册表范围内必须唯一。
Description:
对派生组件的描述。
Status:
对算法状态的简短文本描述。该描述必须以 "Active" 或 "Deprecated" 之一开头,并且可以提供更多上下文或说明。值为 "Deprecated" 表示该派生组件名称不再推荐使用。
Target:
派生参数的有效消息目标。必须为 "Request"、"Response" 或 "Request, Response" 之一。这些项的语义在 第 2.2 节 中定义。
Reference:
规范该派生组件的文档参考,最好包含可检索该文档的 URI。也可以包含相关章节的指示,但并非必需。

6.4.2. 初始内容

下表包含 “HTTP Signature Derived Component Names” 注册表的初始内容。

Table 4: Initial Contents of the HTTP Signature Derived Component Names Registry
Name Description Status Target Reference
@signature-params 签名基础中签名参数行的保留名称 Active Request, Response Section 2.3 of RFC 9421
@method HTTP 请求方法 Active Request Section 2.2.1 of RFC 9421
@authority HTTP authority,或目标主机 Active Request Section 2.2.3 of RFC 9421
@scheme 请求 URI 的 scheme Active Request Section 2.2.4 of RFC 9421
@target-uri 请求的完整目标 URI Active Request Section 2.2.2 of RFC 9421
@request-target 请求的 request-target Active Request Section 2.2.5 of RFC 9421
@path 请求 URI 的完整路径 Active Request Section 2.2.6 of RFC 9421
@query 请求 URI 的完整查询 Active Request Section 2.2.7 of RFC 9421
@query-param 单个命名查询参数 Active Request Section 2.2.8 of RFC 9421
@status 响应的状态码 Active Response Section 2.2.9 of RFC 9421

6.5. HTTP 签名组件参数注册表

本文档定义了若干类型的组件标识符,其中一些在特定情况下可以带参数以提供独特的修改行为。IANA 已创建并现在维护一个名为 “HTTP Signature Component Parameters” 的新注册表,用于记录和维护参数名称、它们关联的组件标识符以及这些参数对组件值所做的修改。参数的定义必须定义其适用目标(例如特定字段类型、派生组件或上下文)。此注册表的初始值见 第 6.5.2 节。对现有条目的后续分配与修改应通过 Expert Review 注册策略(参见 [RFC8126])进行。

指定专家(DE)预计应执行下列操作:

  • 确保名称符合 第 6.5.1 节 所示模板;这包括确保名称长度不过长,同时仍对其定义的功能保持唯一且易识别。
  • 确保对该字段的定义能充分说明其与当时已知的其他参数之间的任何交互或不兼容性。
  • 确保在参数改变组件值的情况下,带参数的组件标识符所定义的组件值能够从目标 HTTP 消息确定性地派生出来。

6.5.1. 注册模板

Name:
参数名称。名称必须是符合 key ABNF 规则(参见 [STRUCTURED-FIELDS] 第 3.1.2 节)的 ASCII 字符串,且不应超过 20 个字符。该名称在注册表范围内必须唯一。
Description:
参数功能的描述。
Reference:
规范该派生组件的文档参考,优选包括可用于检索该文档副本的 URI。可以包含相关章节指示,但不是必需的。

6.5.2. 初始内容

下表包含 “HTTP Signature Component Parameters” 注册表的初始内容。

Table 5: Initial Contents of the HTTP Signature Component Parameters Registry
Name Description Reference
sf 严格的结构化字段序列化 第 2.1.1 节(RFC 9421)
key 字典结构化字段的单个键值 第 2.1.2 节(RFC 9421)
bs Byte Sequence 封装指示器 第 2.1.3 节(RFC 9421)
tr Trailer(尾部) 第 2.1.4 节(RFC 9421)
req 相关请求指示器 第 2.4 节(RFC 9421)
name 单个命名的查询参数 第 2.2.8 节(RFC 9421)

7. 安全注意事项

要使 HTTP 消息被视为被签名“覆盖”,以下所有条件必须为真:

  • 验证者预期或允许在该消息上存在签名。
  • 消息上确实存在签名。
  • 签名已针对所标识的密钥材料与算法进行验证。
  • 密钥材料和算法适合该消息的上下文。
  • 签名处于预期的时间范围内。
  • 签名覆盖预期的内容,包括任何关键组件。
  • 已覆盖组件的列表适用于该消息的上下文。

除第 1.4 节 中列出的应用要求定义之外,以下安全注意事项提供了关于在 HTTP 消息上创建与验证签名时需要考虑的讨论和背景。

7.1. 一般性注意事项

7.1.1. 跳过签名验证

HTTP 消息签名只有在验证者执行了签名验证时才提供安全性。由于附带签名的消息在没有 Signature 或 Signature-Input 字段的情况下仍然是一个有效的 HTTP 消息,因此验证者可能会忽略验证函数的输出并继续处理该消息。常见原因包括开发环境中的宽松要求,或在调试整个系统时暂时暂停强制验证。这样的临时暂停在正向示例测试中很难被发现,因为良好的签名在无论是否被检查时都会触发有效响应。

为检测此类情况,验证者应使用有效与无效签名进行测试,以确保无效签名按预期失败。

7.1.2. TLS 的使用

HTTP 消息签名并不能替代 TLS 或其等价物来保护传输中的信息。消息签名为被覆盖的消息组件提供消息完整性,但不为通信双方之间提供任何保密性。

TLS 为 TLS 端点之间提供这样的保密性。同时,TLS 也保护签名数据本身不被攻击者捕获,这对于防止签名重放(参见 第 7.2.2 节)是重要的一步。

在使用 TLS 时,需要根据 [BCP195] 中提供的建议来部署它。

7.2. 消息处理与选择

7.2.1. 覆盖不足

任何未被签名覆盖的消息部分都可能被攻击者修改而不影响签名。攻击者可以利用这一点,通过插入或修改头字段或其它消息组件来改变消息的处理方式,而这些改动并未被签名覆盖。这样被篡改的消息仍然会通过签名验证,但当验证者对整个消息进行处理时,攻击者注入的未签名内容会破坏有效签名所传达的信任,从而改变消息处理结果。

为对抗这种情况,本规范的应用应在应用与部署允许的范围内,要求尽可能多的消息部分被签名。验证者应仅信任已被签名覆盖的消息组件。验证者也可以在继续处理消息之前去除任何敏感的未签名部分。

7.2.2. 签名重放

由于 HTTP 消息签名允许对消息的子部分进行签名,两个不同的 HTTP 消息有可能对同一个签名进行验证。最极端的情况是对没有任何消息组件进行签名。如果这样的签名被截获,攻击者可以任意重放并将其附加到任意 HTTP 消息上。即便覆盖范围充分,给定签名仍可能被应用到两个相似的 HTTP 消息上,使攻击者能够保留签名并重放消息。

为对抗这类攻击,首先签名者应覆盖足够的消息部分以便将其与其它消息区分开来。此外,签名可使用 nonce 签名参数提供每条消息的唯一值,以便验证者在检测到 nonce 值重复时识别签名重放。签名者还可以提供签名创建时间戳与签名过期时间,从而限制被捕获签名的有效期。

如果验证者想要触发签名者生成新签名,它可以在 Accept-Signature 头字段中携带新的 nonce 参数。仅仅重放签名的攻击者无法使用选定的 nonce 值生成新签名。

7.2.3. 选择消息组件

HTTP 消息签名的应用需要决定哪些消息组件将被签名。根据应用的不同,有些组件可能会被中间体在签名验证之前修改。如果这些组件被覆盖,则这类修改会按设计破坏签名。

然而,本文档允许灵活地确定签名哪些组件,以便应用可以选择需要被签名的消息部分,从而避免有问题的组件。例如,一个依赖重写查询参数的 Web 应用框架可能会避免使用 @query 派生组件,而改为使用基于索引的 @query-param 派生组件来子索引查询值。

某些组件预期会被中间体修改,因此在大多数情况下不应被签名。例如 Via 与 Forwarded 头字段通常会被代理与其它中间设备修改,包括替换或完全丢弃现有值。除非在非常有限且紧耦合的场景中,这些字段不应被签名覆盖。

关于签名要素选择的更多考虑见 第 1.4 节

7.2.4. 优先选择签名参数与派生组件而非 HTTP 字段

某些 HTTP 字段的值和解释与 HTTP 签名参数或派生组件类似。在大多数情况下,更可取的是签名非字段的替代项。下列字段通常不应包含在签名中,除非应用特别要求:

"date"
Date 头字段表示 HTTP 消息的时间戳。然而,签名本身的创建时间编码在 created 签名参数中。根据签名与 HTTP 消息如何创建和序列化,这两个值可能不同。处理签名有效时间窗口的应用应使用 created 签名参数进行相关计算。应用亦可限制 Date 字段与 created 签名参数之间的偏差,以限制生成的签名被用于不同的 HTTP 消息。另见第 7.2.2 节7.2.1 节 的讨论。
"host"
Host 头字段特定于 HTTP/1.1,其功能已被派生组件 @authority(参见 第 2.2.3 节)所包含。为了在不同 HTTP 版本间保留该值,应用应始终使用 @authority 派生组件。另见第 7.5.4 节

7.2.5. 签名标签

HTTP 消息签名值在 Signature 与 Signature-Input 字段中由唯一的标签标识。这些标签仅在将签名值附加到消息时选择,并不参与签名过程。中间体在处理消息时被允许为现有签名重新标注标签。

因此,应用不应依赖特定标签的存在,也不应对标签本身赋予语义意义。相反,可以使用附加的签名参数来传达需要附加到签名并被签名覆盖的额外含义。特别地,tag 参数可以用于传递应用特定的值,如第 7.2.7 节 所述。

7.2.6. 多个签名混淆

由于一个消息可以应用多个签名(参见 第 4.3 节),攻击者有可能在抓取到的消息上附加他们自己的签名而不修改已有签名。这个新签名可能基于攻击者的密钥完全有效,也可能因为各种原因无效。这些情况都需要考虑在内。

处理一组有效签名的验证者需要考虑所有签名者——由签名密钥标识。只应接受来自预期签名者的签名,无论该签名在密码学上是否有效。

验证者在处理一组签名时还需要决定当一个或多个签名无效时的处理策略。如果当至少一个签名有效时就接受消息,那么验证者可以在继续处理消息前从请求中删除所有无效签名。反之,如果验证者因单个无效签名而拒绝消息,则攻击者可以通过在有效签名旁注入无效签名来发起拒绝服务攻击。

7.2.7. 应用特定签名标签的碰撞

多个应用与协议可能会同时在同一消息上应用 HTTP 签名。实际上,这在许多场景中是期望的特性(参见 第 4.3 节)。但是,天真的验证者在处理多重签名时可能会混淆,从而基于无关或不相关的签名接受或拒绝消息。为了帮助应用选择适用于其处理的签名,应用可以声明 tag 签名参数的特定值(如第 2.3 节 所定义)。例如,面向应用网关的签名可以要求签名参数中包含 tag="app-gateway"

使用 tag 参数并不能阻止攻击者也使用相同的值来针对目标应用,因为该参数的值是公开且不受限制的。因此,验证者应仅使用 tag 参数的值来限制需要检查的签名集合。每个签名仍然需要由验证者检查,以确保提供了足够的覆盖,如第 7.2.1 节 所讨论。

7.2.8. 消息内容

就其本身而言,本规范并不为 HTTP 消息的内容本身提供签名覆盖(无论是请求还是响应)。但是,[DIGEST] 定义了一组字段,允许在字段中表示内容的密码学摘要。一旦此字段被创建,就可以像第 2.1 节 中定义的任何其它字段一样将其包括在签名中。

例如,在下列响应消息中:

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

{"hello": "world"}

内容的摘要可以添加到 Content-Digest 字段,如下所示:

NOTE: '\' line wrapping per RFC 8792

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

{"hello": "world"}

此字段可以像任何其它字段一样与基本签名参数一同包含在签名基础中:

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

之后,签名过程照常进行。

在验证时,验证者不仅应验证签名,还应将 Content-Digest 字段的值与实际接收的内容进行校验。除非验证者执行此步骤,否则攻击者可能替换消息内容但保持 Content-Digest 字段值不变以通过签名验证。因为只有字段值被直接签名,仅检查签名本身不足以防止此类替换攻击。

[DIGEST] 所述,Content-Digest 字段的值依赖于消息的内容编码。如果中间体更改了内容编码,生成的 Content-Digest 值也会改变,这将使签名失效。任何执行此类操作的中间体需要使用更新后的 Content-Digest 值应用新的签名,类似于第 4.3 节 中讨论的反向代理用例。

使用 req 参数的应用(参见 第 2.4 节)也需要注意此功能的限制。具体而言,如果客户端未在请求中包含类似 Content-Digest 的字段,则服务器无法在其响应签名中覆盖请求的消息内容。

7.3. 密码学注意事项

7.3.1. 密码学与签名碰撞

本文档不定义任何自己的密码学原语,而是依赖其它规范来定义这些要素。如果用于处理签名基础的签名算法或密钥存在漏洞,生成的签名也将易受相同攻击影响。

针对签名系统的常见攻击之一是强制签名碰撞,使相同签名值对多个不同输入都能成功验证。由于本规范依赖于从 HTTP 消息和固定的已覆盖组件列表重构签名基础,攻击者要想成功制造此类碰撞较为困难但并非不可能。攻击者需要操纵 HTTP 消息及其被覆盖的消息组件以使碰撞生效。

为对抗这一点,应仅使用经审查的密钥与签名算法来对 HTTP 消息进行签名。“HTTP Signature Algorithms” 注册表为应用选择可信签名算法提供了来源。

尽管理论上攻击者可对签名参数值或签名值本身进行替换,但签名基础生成算法(参见 第 2.5 节)始终以确定性序列化方法将签名参数作为签名基础的最终值覆盖。此步骤强烈地将签名基础与签名值绑定,令攻击者更难进行部分替换攻击。

7.3.2. 密钥被盗

签名类加密系统的基本假设是签名密钥不会被攻击者窃取。如果用于签名消息的密钥被外泄或被盗,攻击者将能够使用这些密钥生成他们自己的签名。因此,签名者必须保护任何签名密钥材料,防止被外泄、捕获或被攻击者使用。

为防范此类情况,签名者可以随着时间轮换密钥以限制被盗密钥的有效期。签名者也可以使用密钥托管与存储系统以减少密钥的攻击面。此外,与对称签名算法相比,采用非对称签名算法可以减少需保护的密钥材料量(参见 第 7.3.3 节)。

7.3.3. 对称加密

本文档允许在 HTTP 消息中使用非对称与对称加密。对称方法要求签名者与验证者共享相同的密钥材料,这意味着验证者能够生成有效签名,因为他们拥有相同的密钥材料。若攻击者能够入侵验证者,则可能冒充签名者。

在可能的情况下,应使用非对称方法或安全的密钥协商机制以避免此类攻击。当使用对称方法时,密钥分发需要由整体系统进行保护。一种技术是使用独立的加密模块,将验证过程(及其密钥材料)与其它代码隔离,以最小化易受攻击的表面。另一种技术是使用密钥派生函数,使签名者与验证者能就每条消息协商唯一密钥而无需直接共享密钥值。

另外,如果系统允许使用对称算法,则必须特别注意避免密钥降级攻击(参见 第 7.3.6 节)。

7.3.4. 密钥规格混淆

仅凭消息上存在有效签名并不足以证明消息是由合适的当事方签署的。验证者需确保所用的密钥与算法适用于所处理的消息。如果验证者不执行此类检查,攻击者可以用自己的密钥对消息替换签名,从而强迫验证者接受并处理它。为防范此类攻击,验证者不仅需要验证签名能否被验证,还需要确认证明所用的密钥与算法在该使用场景下是恰当的。

7.3.5. 非确定性签名原语

一些密码学原语,例如 RSA-PSS 与 ECDSA,具有非确定性输出,算法内部包含一定的熵。因此,连续生成的多个签名不会相同。懒惰的验证实现可能会忽略这一点,简单地通过重新对签名基础签名并比较结果值来检查签名。这样的实现对确定性算法(如 HMAC 与 EdDSA)可能有效,但会无法验证使用非确定性算法生成的有效签名。因此,验证者应始终使用适当算法的验证函数,而非进行简单比较。

7.3.6. 密钥与算法规格降级

本规范的应用需要防范密钥规格降级攻击。例如,同一 RSA 密钥可用于 RSA-PSS 与 RSA v1.5 签名。如果应用期望密钥仅用于 RSA-PSS,则需要拒绝对使用较弱 RSA 1.5 规范的签名。

另一个降级攻击的例子是,当期望使用非对称算法(例如 RSA-PSS)时,攻击者用对称算法(如 HMAC)替换签名。天真的验证实现可能会将公钥值作为 HMAC 验证函数的输入;由于公钥对攻击者已知,这将允许攻击者基于已知密钥创建有效的 HMAC 签名。为防止此类攻击,验证者需要确保密钥材料与算法适合该用途。此外,虽然本规范允许通过 alg 签名参数在运行时指定算法,但鼓励应用使用静态配置或更高层协议的算法规范,从而阻止攻击者替换所指定的算法。

7.3.7. 对签名值进行签名

在对消息应用 req 参数(参见 第 2.4 节)或对消息应用多重签名(参见 第 4.3 节)时,可能会对现有 Signature 字段的值进行签名,从而在新签名中覆盖现有签名输出的字节。虽然这看似会以可验证的方式递归覆盖原签名下的组件,但 [JACKSON2019] 中描述的攻击可以被用来在不相关的消息上冒充签名输出值。

在此示例中,Alice 打算将带签名的请求发送给 Bob,而 Bob 希望为 Alice 返回的响应提供一份包含 Bob 正在回应 Alice 的密码学证明。Mallory 想拦截此流量并用自己的消息替换 Alice 的消息,而不让 Alice 察觉拦截行为。

  1. Alice 创建消息 Req_A 并使用她的私钥 Key_A_Sign 应用签名 Sig_A
  2. Alice 认为她正在将 Req_A 发送给 Bob。
  3. Mallory 拦截 Req_A 并读取该消息中的 Sig_A 值。
  4. Mallory 生成另一条消息 Req_M 并发送给 Bob。
  5. Mallory 构造一个签名密钥 Key_M_Sign,使她能用该密钥为她的请求 Req_M 生成有效签名 Sig_M,但 Sig_M 的字节值恰好等于 Sig_A
  6. Mallory 将带有 Sig_MReq_M 发送给 Bob。
  7. Bob 使用 Mallory 的验证密钥 Key_M_Verify 验证 Sig_M。在整个过程中,Bob 并不认为自己是在响应 Alice。
  8. Bob 对 Req_M 响应并生成响应消息 Res_B,使用他的密钥 Key_B_Sign 对该消息创建签名 Sig_B。Bob 在 Sig_B 的覆盖组件中包含了 Sig_M 的值,但不会包含来自请求消息的其它内容。
  9. Mallory 接收到 Bob 返回的响应 Res_B(含签名 Sig_B),并将该响应重放给 Alice。
  10. Alice 从 Mallory 那里读取 Res_B 并使用 Bob 的验证密钥 Key_B_Verify 验证 Sig_B。Alice 在签名基础中包含了她原始的签名字节 Sig_A,并且签名验证通过。
  11. Alice 被误导以为 Bob 已经对她的消息做出响应并认为自己拥有对此事件的密码学证明,但事实上 Bob 响应的是 Mallory 的恶意请求,Alice 却浑然不知。

为缓解这一问题,Bob 可以在第二次签名中覆盖比仅 Signature 字段更多的请求消息部分,以更充分地区分 Alice 的消息与 Mallory 的消息。使用此功能的应用(尤其是用于不可否认性的场景)可以规定第二次签名也必须单独覆盖原始签名中所需的组件。对于已签名的消息,要求覆盖首个签名的对应 Signature-Input 字段能确保诸如 nonce 与时间戳等唯一项也被第二次签名充分覆盖。

7.4. 将签名参数与目标消息匹配

7.4.1. 所需消息参数的修改

攻击者可能通过修改原本无害的签名参数或已签名的消息组件来有效地发起拒绝服务。尽管拒绝被修改的消息是期望的行为,但持续发生的签名失败可能导致 (1) 验证者为了让系统恢复工作而关闭签名检查(参见 第 7.1.1 节),或 (2) 应用最小化与该签名组件相关的要求。

如果在某个应用中这种失败很常见,签名者和验证者应相互比较彼此生成的签名基础,以确定消息的哪个部分被修改。如果发现是可预期的修改,签名者和验证者可以就一组可通过的替代要求达成一致。然而,当怀疑有攻击者在修改组件时,签名者和验证者不应移除对该被修改组件签名的要求。

7.4.2. 将已覆盖组件的值与目标消息中的值匹配

验证者需要确保被签名的消息组件与消息本身中的值相符。例如,派生组件 @method 要求签名基础中的值与在呈现该消息时使用的 HTTP 方法相同。本规范通过要求验证者从消息中派生签名基础来鼓励这种做法,但懒惰的缓存或将原始签名基础传递给处理子系统可能导致后续的验证者接受与所呈现签名不匹配的消息。

为对抗这一点,生成签名基础的组件需要在系统内被签名者和验证者都信任。

7.4.3. 消息组件的来源与上下文

用于派生消息组件值的签名上下文包括目标 HTTP 消息本身、任何关联消息(例如触发响应的请求),以及签名者或验证者可访问的附加信息。签名者与验证者在为签名基础创建组件值时需要仔细考虑所有信息的来源,并注意不要从不受信任的来源获取信息。否则,攻击者可能利用这种定义松散的消息上下文将其自身的值注入签名基础字符串,从而覆盖或破坏预期值。

例如,在大多数情况下,消息的目标 URI 如 [HTTP]7.1 节 所定义。然而,假设有一个应用需要对进入请求的 @authority 进行签名,但实际执行处理的应用位于一个反向代理之后。这样的应用会预期 @authority 值发生变化,并且可以配置为知道由代理另一侧的客户端看到的外部目标 URI。该应用会将此配置值作为其用于派生消息组件值(例如 @authority)的目标 URI,而不是使用进入消息的目标 URI。

这种做法并非没有问题,因为配置错误的系统可能会接受本应发送给系统中不同组件的已签名请求。对于这种情形,中间体可以改为添加自身的签名,由应用直接验证,如 第 4.3 节 所示。这种替代方法需要更主动的中间体,但较少依赖目标应用知道外部的配置值。

另一个例子,第 2.4 节 定义了一种为响应消息签名并同时包含触发该响应的请求部分的方法。在这种情况下,组件值计算的上下文是响应与请求消息的组合,而不仅仅是应用签名的单个消息。对于此特性,req 标志允许双方显式地指示哪个上下文的部分被用作组件标识符值的来源。实现需要确保每个组件仅引用预期的消息;否则,攻击者可能通过操纵任一侧来试图颠覆签名。

7.4.4. 多重消息组件上下文

在单条消息中,每个签名用于派生消息组件值的上下文可能各不相同。这在代理修改消息并对修改后的值添加签名的场景中特别常见,同时保留任何现有签名。例如,反向代理可以将请求中的公共主机名替换为其正在转发到的单个服务主机的主机名。如果客户端与反向代理都对 @authority 添加了签名,服务主机会在请求上看到两个签名,每个签名对 @authority 组件签署不同的值,以反映消息从客户端到服务主机的传递过程中该组件的变化。

在这种情况下,内部服务通常只验证其中一个签名,或如 第 7.4.3 节 所述使用外部配置的信息。然而,处理两个签名的验证者必须为每个签名使用不同的消息组件上下文,因为 @authority 组件的值对于每个签名都会不同。这类验证者需要了解反向代理对进入消息的上下文以及目标服务对来自反向代理消息的上下文。验证者必须特别小心将正确的上下文应用于正确的签名;否则,攻击者可能利用对该复杂设置的了解来混淆验证器的输入。

此类验证者还需确保签名之间消息组件上下文的任何差异是可预期且被允许的。例如,在上述场景中,反向代理可以在 Forwarded 头字段中包含原始主机名,并对 @authority、forwarded 以及客户端条目进行签名。验证者可以使用 Forwarded 头字段中的主机名来确认主机名按预期进行了转换。

7.5. HTTP 处理

7.5.1. 将无效的 HTTP 字段名作为派生组件名处理

HTTP 字段名的定义不允许在名称中使用 @ 字符。因此,既然所有派生组件名称都以 @ 开头,这些命名空间应该完全分离。然而,一些 HTTP 实现对接受的字段名字符不够严格。在这样的实现中,发送者(或攻击者)可以注入以 @ 开头的头字段并将其传递到应用代码。这些无效的头字段可用于覆盖派生消息内容的一部分并替换为任意值,为攻击者提供一个可能发起签名碰撞(参见 第 7.3.1 节)攻击或其他功能性替换攻击(例如在精心构造的 POST 请求上使用来自 GET 请求的签名)的潜在入口。

为对抗这一点,在为消息组件选择值时,如果组件名称以 @ 字符开头,则它需要作为派生组件进行处理,绝不应作为 HTTP 字段处理。只有当组件名称不以 @ 开头时,才可以从消息的字段中获取其值。第 2.5 节 中讨论的算法提供了安全的操作顺序。

7.5.2. 语义等价的字段值

签名基础生成算法(第 2.5 节)使用 HTTP 字段的值作为其组件值。在常见情况下,这相当于取字段值的实际字节作为签名者与验证者的组件值。然而,某些字段值允许以语义等价但会改变字节表示的方式对值进行转换。例如,字段定义可以声明其部分或全部值对大小写不敏感,或对内部空白字符有特殊处理。其他字段存在中间体预期会做的转换,例如在 Via 头字段中移除注释。在这些情况下,验证者可能会因为使用了经转换后的等价字段值(其字节形式与签名者使用的不同)而出现问题。验证者将难以发现此类错误,因为字段的值对于应用仍然可接受,但签名基础所需的实际字节不匹配。

在处理此类字段时,签名者与验证者必须就如何处理这些转换达成一致,或者决定不处理。一种选择是不签署有问题的字段,但必须谨慎以确保仍为应用提供足够的签名覆盖(参见 第 7.2.1 节)。另一种选择是为字段在加入 HTTP 消息之前定义应用特定的规范化值,例如总是在签名前删除内部注释或总是将值转换为小写。由于这些转换在字段被用作签名基础生成算法的输入之前应用,签名基础仍然只是包含字段在消息中出现时的字节值。如果在从消息提取值后但在加入签名基础之前应用了这些转换,则可能会引入不同的攻击面,例如针对应用的值替换攻击。所有应用特定的额外规则超出本规范范围,且这类转换本质上会损害实现于该特定应用之外的互操作性。建议应用尽可能避免使用此类额外规则。

7.5.3. 解析结构化字段值

本规范的若干部分依赖于解析结构化字段值 [STRUCTURED-FIELDS] —— 特别是对 HTTP 结构化字段值的严格序列化(第 2.1.1 节)、引用字典结构化字段的成员(第 2.1.2 节)以及在验证签名时处理 @signature-input 的值(第 3.2 节)。尽管结构化字段值被设计为相对容易解析,但天真或有缺陷的解析器实现可能导致在实现中暴露微妙的攻击面。

例如,如果对 @signature-input 值的解析器存在缺陷而未在组件标识符列表中的字符串值周围强制闭合引号,攻击者就可能利用此点,通过操纵消息的 Signature-Input 字段值将额外内容注入签名基础。

为对抗这一点,实现应在签名方与验证方都使用完全兼容且可信的解析器来处理所有结构化字段。

7.5.4. HTTP 版本与组件歧义

某些消息组件在不同的 HTTP 版本中以不同方式表示。例如,请求目标的 authority 在 HTTP/1.1 中通过 Host 头字段发送,但在 HTTP/2 中使用 :authority 伪头。如果签名者发送 HTTP/1.1 消息并对 Host 头字段进行签名,但消息在到达验证者之前被转换为 HTTP/2,则签名将无法验证,因为 Host 头字段可能被丢弃。

正因如此,HTTP 消息签名定义了一组派生组件,用于以单一方式获取所需的值,例如用以替代 Host 头字段的 @authority 派生组件(参见 第 2.2.3 节)。因此,应用应在可能的情况下优先使用派生组件来处理此类选项。

7.5.5. 规范化攻击

签名基础生成中的任何歧义都可能为攻击者提供替换或破坏消息签名的杠杆。某些消息组件值,特别是 HTTP 字段值,可能容易受到错误实现的影响,从而导致意外且不安全的行为。对此规范的天真实现可能会通过直接取字段的单一值并将其作为直接组件值来实现 HTTP 字段处理,而未对其进行适当处理。

例如,如果对 obs-fold 字段值的处理未移除内部的行折叠和空白,签名者可能在签名基础中引入额外的换行,从而为攻击者提供一个可能发起签名碰撞(参见 第 7.3.1 节)攻击的场所。或者,如果出现多次的头字段没有按本规范要求合并为单个字符串值,也可能发起类似的攻击,因为被签名的组件值会在签名基础中出现多次并可能被替换或以其它方式攻击。

为此,所有签名者与验证者的实现都需要完整实现字段值处理算法。

7.5.6. 非列表字段值

当单条消息中某个 HTTP 字段出现多次时,这些值需要按照 第 2.5 节 的描述合并为单个单行字符串值并包含在 HTTP 签名基础中。并非所有 HTTP 字段都可以以这种方式合并后仍保持为字段的有效值。为生成签名基础,消息组件值从未打算被从签名基础字符串中读取回或被应用使用。因此,最好将签名基础生成算法与应用对字段值的处理分开,尤其针对那些已知具有此特性的字段。如果被签名的字段值在验证时不合法,则该已签名消息也应被拒绝。

如果某一 HTTP 字段允许在其值中出现未加引号的逗号,则合并多个字段值可能导致两条语义不同的消息产生相同的签名基础行。例如,考虑以下示例头字段,其语法中存在内部逗号,用于定义两组独立的值列表:

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

对于该头字段,将所有这些值作为单个字段值发送会导致单个值列表:

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

这两种消息都会在签名基础中创建以下行:

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

由于两种语义不同的输入可能在签名基础中产生相同的输出,因此在处理此类值时需要格外小心。

具体来说,Set-Cookie 字段 [COOKIE] 定义的内部语法不符合 [STRUCTURED-FIELDS] 中提供的 List 语法。特别是,某些部分允许未加引号的逗号,并且在发送多个 Cookie 时通常以多条独立字段行发送。当同一消息中发送多个 Set-Cookie 字段时,通常无法将它们合并为单行并仍能解析和使用结果,如 [HTTP]5.3 节 所讨论。因此,所有 Cookie 需要从它们各自的独立字段值中处理,而签名基础需要从仅为此目的生成的特殊合并值中处理。如果 Cookie 值无效,则应拒绝该已签名消息,因为这可能是第 7.5.7 节 中描述的填充攻击的一种形式。

为应对这一点,应用可以选择限制对诸如 Set-Cookie 等有问题字段的签名,例如仅在存在单个字段值且结果不会产生歧义时将该字段包含在签名中。对于所有可能以非确定性方式映射到签名基础的字段,都需要采取类似的谨慎措施。签名者也可以使用 bs 参数对这些字段进行装甲化,如 第 2.1.3 节 所述。

7.5.7. 使用多个字段值的填充攻击

由于 HTTP 字段值需要被合并为单个字符串值以包含在 HTTP 签名基础中(见 第 2.5 节),攻击者可能向给定字段注入额外值并将其添加到验证者的签名基础中。

在大多数情况下,这会导致签名验证如预期般失败,因为新的签名基础值不会与签名者用于创建签名的值匹配。然而,理论上攻击者可以同时向某个字段注入垃圾值并向另一个字段注入期望的值,以强制产生特定输入。这是 第 7.3.1 节 中描述的碰撞攻击的一种变体,攻击者通过向现有字段值添加内容来实现其对消息的修改。

为对抗这一点,应用需要在确保签名本身验证之外,还对签名所覆盖字段的内容进行校验。具备此类保护的情况下,即使攻击者能够促成签名碰撞,其填充攻击也会被字段值处理器拒绝。

7.5.8. 对查询元素处理的歧义

5 节定义的 HTML 表单参数格式(见 [HTMLURL])被广泛部署并被许多应用框架支持。为方便起见,这些框架中的一些会将出现在 HTTP 查询中的查询参数与出现在消息内容中的参数合并,尤其是对于 Content-Type 为 "application/x-www-form-urlencoded" 的 POST 消息。@query-param 派生组件标识符(见 第 2.2.8 节)仅从请求的目标 URI 的查询部分提取其值。因此,攻击者可能通过使用未签名的表单参数覆盖已签名的查询参数(反之亦然)来隐藏或替换请求中的查询参数。

为对抗这一点,应用需要确保用于签名基础和用于应用处理的值来源于一致的上下文,本例中即目标 URI 的查询组件。此外,当 HTTP 请求具有消息内容时,应用应同时对消息内容进行签名,如 第 7.2.8 节 所讨论。


8. 隐私注意事项

8.1. 通过密钥进行识别

如果签名者对多个验证者使用相同的密钥,或在一段时间内对单个验证者重复使用相同的密钥,则该密钥的持续使用可被用于在消息发送到的验证者集合中跟踪签名者。由于加密密钥在功能上应当是唯一的,长期使用相同密钥强烈表明是同一方在对多条消息进行签名。

在许多应用中,这是一种期望的特性,使 HTTP 消息签名可用于将签名者作为身份验证的一部分向验证者证明自己。然而,这也可能导致签名者未意识到的非预期跟踪。为对抗此类跟踪,签名者可以对每个其通信的验证者使用不同的密钥。有时,签名者也可以在向给定验证者发送消息时轮换其密钥。这些方法并不否定在必要时采用其它反跟踪技术的需求。

8.2. 签名不提供机密性

HTTP 消息签名并不为签名所保护的任何信息提供机密性。HTTP 消息的内容,包括所有字段的值以及签名值本身,都以明文形式呈现给任何有权访问该消息的方。

如需在传输层提供机密性,可使用 TLS 或其等价方案,如 第 7.1.2 节 所述。

8.3. 信息泄露(Oracles)

在向开发者提供有用的错误反馈与不向攻击者提供额外信息之间需要取得平衡。例如,一个天真但“有帮助”的服务器实现可能试图指示请求某资源所需的密钥标识符。如果有人知道谁控制该密钥,则可以将资源的存在与由该密钥标识的方相关联。访问此类信息可能被攻击者用于针对资源的合法所有者发起进一步攻击。

8.4. 必需的内容

本规范的核心设计原则之一是,签名所覆盖的所有消息组件必须对验证者可用,以便重建签名基础并验证签名。因此,如果某个应用要求对特定字段进行签名,验证者将需要访问该字段的值。

例如,在具有中间处理器的某些复杂系统中,这可能导致一种令人惊讶的行为:为了避免破坏签名,中间体无法在将消息转发以供处理之前移除隐私敏感信息。缓解该特定情形的一种方法是中间体自行验证签名,然后修改消息以移除隐私敏感信息。此时中间体可以添加自己的签名以向下一个目的地表明进入的签名已被验证,如 第 4.3 节 的示例所示。

9. 参考文献

9.1. 规范性参考文献

[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/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>.
[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>.
[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-H. 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. 参考性参考文献

[AWS-SIGv4]
Amazon Simple Storage Service,“Authenticating Requests (AWS Signature Version 4)”,2006 年 3 月,<https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html>。
[BCP195]
Moriarty, K. 和 S. Farrell,“弃用 TLS 1.0 和 TLS 1.1”,BCP 195, RFC 8996,DOI 10.17487/RFC8996,2021 年 3 月。
Sheffer, Y., Saint-Andre, P., 和 T. Fossati,“传输层安全 (TLS) 与数据报传输层安全 (DTLS) 的安全使用建议”,BCP 195,RFC 9325,DOI 10.17487/RFC9325,2022 年 11 月。
<https://www.rfc-editor.org/info/bcp195>
[CLIENT-CERT]
Campbell, B. 和 M. Bishop,“Client-Cert HTTP Header Field”,RFC 9440,DOI 10.17487/RFC9440,2023 年 7 月,<https://www.rfc-editor.org/info/rfc9440>。
[COOKIE]
Barth, A.,“HTTP 状态管理机制”,RFC 6265,DOI 10.17487/RFC6265,2011 年 4 月,<https://www.rfc-editor.org/info/rfc6265>。
[DIGEST]
Polli, R. 和 L. Pardue,“Digest Fields”,RFC 9530,DOI 10.17487/RFC9530, 2024 年 2 月,<https://www.rfc-editor.org/info/rfc9530>。
[JACKSON2019]
Jackson, D., Cremers, C., Cohn-Gordon, K., 和 R. Sasse,“Seems Legit: Automated Analysis of Subtle Attacks on Protocols that Use Signatures”,DOI 10.1145/3319535.3339813,CCS '19:2019 年 ACM SIGSAC 会议论文集,页 2165-2180,2019 年 11 月,<https://dl.acm.org/doi/10.1145/3319535.3339813>。
[JWS]
Jones, M., Bradley, J., 和 N. Sakimura,“JSON Web Signature (JWS)”,RFC 7515,DOI 10.17487/RFC7515,2015 年 5 月,<https://www.rfc-editor.org/info/rfc7515>。
[RFC7239]
Petersson, A. 和 M. Nilsson,“Forwarded HTTP Extension”,RFC 7239,DOI 10.17487/RFC7239,2014 年 6 月,<https://www.rfc-editor.org/info/rfc7239>。
[RFC7468]
Josefsson, S. 和 S. Leonard,“PKIX、PKCS 和 CMS 结构的文本编码”,RFC 7468,DOI 10.17487/RFC7468,2015 年 4 月,<https://www.rfc-editor.org/info/rfc7468>。
[RFC7807]
Nottingham, M. 和 E. Wilde,“HTTP API 的问题详细信息”,RFC 7807,DOI 10.17487/RFC7807,2016 年 3 月,<https://www.rfc-editor.org/info/rfc7807>。
[RFC8126]
Cotton, M., Leiba, B., 和 T. Narten,“在 RFC 中编写 IANA 注意事项部分的指南”,BCP 26, RFC 8126,DOI 10.17487/RFC8126,2017 年 6 月,<https://www.rfc-editor.org/info/rfc8126>。
[RFC8792]
Watsen, K., Auerswald, E., Farrel, A., 和 Q. Wu,“在 Internet-Drafts 与 RFC 内容中处理长行”,RFC 8792,DOI 10.17487/RFC8792,2020 年 6 月,<https://www.rfc-editor.org/info/rfc8792>。
[RFC9457]
Nottingham, M., Wilde, E., 和 S. Dalal,“HTTP API 的问题详细信息”,RFC 9457,DOI 10.17487/RFC9457,2023 年 7 月,<https://www.rfc-editor.org/info/rfc9457>。
[SIGNING-HTTP-MESSAGES]
Cavage, M. 和 M. Sporny,“Signing HTTP Messages”,工作草案,draft-cavage-http-signatures-12,工作草案,2019 年 10 月,<https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12>。
[SIGNING-HTTP-REQS-OAUTH]
Richer, J., Ed., Bradley, J., 和 H. Tschofenig,“用于 OAuth 的 HTTP 请求签名方法”,工作草案,draft-ietf-oauth-signed-http-request-03,工作草案,2016 年 8 月,<https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03>。
[TLS]
Rescorla, E.,“传输层安全 (TLS) 协议第 1.3 版”,RFC 8446,DOI 10.17487/RFC8446,2018 年 8 月,<https://www.rfc-editor.org/info/rfc8446>。

附录 A. 检测 HTTP 消息签名

过去有许多尝试去创建已签名的 HTTP 消息,包括本规范所使用的 Signature 字段的其他未标准化定义。建议希望支持本规范、其它已发布文档或历史草案的开发者谨慎并有意识地去实现,因为本规范与其它文档或不同草案版本之间的不兼容可能导致意外问题。

建议实现者首先检测并验证本规范定义的 Signature-Input 字段,以确认正在使用本文档所述的机制而非其它替代机制。如果存在 Signature-Input 字段,则所有 Signature 字段都可以在本规范的上下文中解析和解释。


附录 B. 示例

下列非规范性示例用于测试 HTTP 消息签名的实现。所给的已签名消息可用于在指定参数下创建签名基础,并使用给定的算法与密钥创建签名。

所给的私钥可用于生成签名,但因为若干示例中所示的算法是非确定性的,签名结果预期与示例的确切字节会不同。所给的公钥可用于验证所有示例签名。

B.1. 示例密钥

本节提供在文档示例签名中引用的加密密钥。这些密钥不得用于测试以外的任何用途。

每个密钥的密钥标识符在本规范的示例中反复使用。示例假定签名者与验证者能够明确地解引用此处使用的所有密钥标识符,并且所使用的密钥与算法适合签名所呈现的上下文。

每个私钥的组件以 PEM 格式给出(参见 [RFC7468]),可通过执行以下 OpenSSL 命令显示:

openssl pkey -text

此命令在 OpenSSL 1.1.1m 上测试过所有示例密钥。注意某些系统无法直接生成或使用所有这些密钥,可能需要额外处理。所有密钥也以 JWK 格式提供。

B.1.1. 示例 RSA 密钥

下面的密钥是一对 2048 位的 RSA 公私钥对,在本文档中称为 test-key-rsa。该密钥以 PEM 格式编码,且无加密。

-----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-----

相同的公私钥对的 JWK 格式:

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. 示例 RSA-PSS 密钥

下面的密钥是一对 2048 位的 RSA 公私钥对,在本文档中称为 test-key-rsa-pss。该密钥以 PKCS #8 PEM 格式编码,且无加密。

-----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-----

相同的公私钥对的 JWK 格式:

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. 示例 ECC P-256 测试密钥

下面的密钥是一对基于 P-256 曲线的公私椭圆曲线密钥对,在本文档中称为 test-key-ecc-p256。该密钥以 PEM 格式编码,且无加密。

-----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-----

相同的公私钥对的 JWK 格式:

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

B.1.4. 示例 Ed25519 测试密钥

下面的密钥是基于 Edwards 曲线 ed25519 的椭圆曲线密钥对,在本文档中称为 test-key-ed25519。该密钥以 PKCS #8 PEM 格式编码,且无加密。

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

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

相同的公私钥对的 JWK 格式:

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

B.1.5. 示例共享密钥

下面的共享密钥为 64 个随机生成字节的 Base64 编码形式,在本文档中称为 test-shared-secret

NOTE: '\' line wrapping per RFC 8792

uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBt\
  bmHhIDi6pcl8jsasjlTMtDQ==

B.2. 测试用例

本节提供可用作测试用例以验证实现正确性的非规范性示例。这些示例基于下列 HTTP 消息:

对于请求,使用此 test-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"}

对于响应,使用此 test-response 消息:

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. 使用 rsa-pss-sha512 的最小签名

此示例展示了对 test-request 使用 rsa-pss-sha512 算法的最小签名,不覆盖 HTTP 消息的任何组件,但提供带有签名者提供的 nonce 的带时间戳的持有密钥证明签名。

对应的签名基础为:

NOTE: '\' line wrapping per RFC 8792

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

这会导致在标签为 sig-b21 的签名下,将下列 Signature-Input 与 Signature 头字段添加到消息中:

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==:

注意,由于已覆盖组件列表为空,该签名可能被攻击者应用到不相关的 HTTP 消息。在此示例中,包含 nonce 参数是为了防止同一签名被重复重放,但若攻击者截获签名并阻止其到达验证者,攻击者仍可将该签名应用到另一条消息。因此,不建议使用空的已覆盖组件集合。更多讨论见 第 7.2.1 节

注意,此处使用的 RSA-PSS 算法是非确定性的,意味着每次运行算法都会产生不同的签名值。此处提供的签名值可以使用给定的密钥进行验证,但新生成的签名值不应与示例匹配。见 第 7.3.5 节

B.2.2. 使用 rsa-pss-sha512 的选择性覆盖组件

此示例在 test-request 中覆盖了额外的组件(authority、Content-Digest 头字段以及单个命名的查询参数),使用 rsa-pss-sha512 算法。该示例还添加了一个应用特定值为 header-exampletag 参数。

对应的签名基础为:

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"

这会导致在标签为 sig-b22 的签名下,将下列 Signature-Input 与 Signature 头字段添加到消息中:

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==:

注意,此处使用的 RSA-PSS 算法是非确定性的,意味着每次运行算法都会产生不同的签名值。此处提供的签名值可以使用给定的密钥进行验证,但新生成的签名值不应与示例匹配。见 第 7.3.5 节

B.2.3. 使用 rsa-pss-sha512 的完整覆盖

此示例覆盖了 test-request 中所有适用的消息组件(包括内容类型与长度)以及许多派生组件,同样使用 rsa-pss-sha512 算法。注意未覆盖 Host 头字段,因为包含了派生组件 @authority

对应的签名基础为:

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"

这会导致在标签为 sig-b23 的签名下,将下列 Signature-Input 与 Signature 头字段添加到消息中:

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==:

注意在此示例中,Date 头字段的值与 created 签名参数的值不必相同。这是因为 Date 头字段是在创建 HTTP 消息时被添加,而 created 参数是在对该消息创建签名时填充的,这两者的时间可能不同。如果 Date 头字段被签名覆盖,则由验证者决定其值是否必须与 created 参数匹配。更多讨论见 第 7.2.4 节

注意,此处使用的 RSA-PSS 算法是非确定性的,意味着每次运行算法都会产生不同的签名值。此处提供的签名值可以使用给定的密钥进行验证,但新生成的签名值不应与示例匹配。见 第 7.3.5 节

B.2.4. 使用 ecdsa-p256-sha256 签署响应

此示例使用 ecdsa-p256-sha256 算法和密钥 test-key-ecc-p256 覆盖了 test-response 消息的部分内容。

对应的签名基础为:

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"

这会导致在标签为 sig-b24 的签名下,将下列 Signature-Input 与 Signature 头字段添加到消息中:

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==:

注意,此处使用的 ECDSA 签名算法是非确定性的,意味着每次运行算法都会产生不同的签名值。此处提供的签名值可以使用给定的密钥进行验证,但新生成的签名值不应与示例匹配。见 第 7.3.5 节

B.2.5. 使用 hmac-sha256 签署请求

此示例使用 hmac-sha256 算法和密钥 test-shared-secret 覆盖了 test-request 消息的部分内容。

对应的签名基础为:

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"

这会导致在标签为 sig-b25 的签名下,将下列 Signature-Input 与 Signature 头字段添加到消息中:

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=:

在实际使用对称签名之前,请参阅关于安全权衡的讨论,见 第 7.3.3 节

B.2.6. 使用 ed25519 签署请求

此示例使用 Ed25519 算法和密钥 test-key-ed25519 覆盖了 test-request 消息的部分内容。

对应的签名基础为:

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"

这会导致在标签为 sig-b26 的签名下,将下列 Signature-Input 与 Signature 头字段添加到消息中:

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 终止代理

在此示例中,有一个位于资源前面的 TLS 终止反向代理。客户端不对请求进行签名,而是使用双向 TLS 发起调用。终止代理验证 TLS 流并根据 [CLIENT-CERT] 将客户端证书注入到 Client-Cert 头字段,然后对该字段应用签名。通过对该头字段进行签名,反向代理不仅可以证明其对初始请求的 TLS 参数进行了验证,还可以独立于客户端的操作向后端系统对自身进行认证。

客户端对 TLS 终止代理使用双向 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"}

代理处理 TLS 连接并将客户端的 TLS 证书提取到 Client-Cert 头字段,然后将其传递到托管在 service.internal.example 的内部服务。结果如下所示的未签名请求:

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"}

如果没有签名,内部服务需要信任传入连接包含正确的信息。通过对 Client-Cert 头字段和内部请求的其它部分进行签名,内部服务可以确信可信代理已处理该请求并将其呈现给正确的服务。代理的签名基础包含以下内容:

NOTE: '\' line wrapping per RFC 8792

"@path": /foo
"@query": ?param=Value&Pet=dog
"@method": POST
"@authority": service.internal.example
"client-cert": :MIIBqDCCAU6gAwIBAgIBBzAKBggqhkjOPQQDAjA6MRswGQYDVQQ\
  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"

这会生成以下签名:

NOTE: '\' line wrapping per RFC 8792

xVMHVpawaAC/0SbHrKRs9i8I3eOs5RtTMGCWXm/9nvZzoHsIg6Mce9315T6xoklyy0y\
zhD9ah4JHRwMLOgmizw==

结果是代理向内部服务发送以下已签名请求,代理的签名使用标签 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"}

内部服务可以验证代理的签名,从而信任客户端的证书已被适当处理。

B.4. HTTP 消息转换

HTTP 允许中间体和应用在不影响消息语义的情况下转换 HTTP 消息。HTTP 消息签名旨在在不同情况下对这些转换具有鲁棒性。

例如,下面的 HTTP 请求消息使用 Ed25519 算法和密钥 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==:

该消息的签名基础字符串为:

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

下面的消息通过添加 Accept-Language 头字段以及添加查询参数进行了更改。然而,由于 Accept-Language 头字段和该查询都未被签名覆盖,相同的签名仍然有效:

NOTE: '\' line wrapping per RFC 8792

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

下面的消息通过移除 Date 头字段、添加 Referer 头字段并将 Accept 头字段合并为单行进行了更改。Date 与 Referer 头字段未被签名覆盖,且将 Accept 头字段合并为单行是签名基础的 HTTP 字段值规范化算法已考虑的允许转换。相同的签名仍然有效:

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==:

下面的消息通过重新排序原始消息的字段值但未重新排序各个 Accept 头字段来进行了更改。相同的签名仍然有效:

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==:

下面的消息通过将方法更改为 POST 并将 authority 更改为 "example.com"(在 Host 头字段内)进行了更改。由于方法和 authority 都被签名覆盖,相同的签名不再有效:

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==:

下面的消息通过更改两个 Accept 头字段实例的顺序进行了更改。由于同名字段的顺序在 HTTP 中在语义上是重要的,这会改变在签名基础中使用的值,因此相同的签名不再有效:

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==:

致谢

本规范最初基于 [SIGNING-HTTP-MESSAGES]。编辑者要感谢 [SIGNING-HTTP-MESSAGES] 的作者 —— Mark Cavage 与 Manu Sporny —— 他们对该 Internet-Draft 的工作及持续贡献。本规范还包含来自 [SIGNING-HTTP-REQS-OAUTH] 及其它类似工作的贡献。

编辑者还要感谢以下个人(按字母顺序列出)对本文件及其前身的反馈、见解与实现: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 以及 Jeffrey Yasskin。


作者联系信息

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