6. Message Transport (消息传输)
ACME 客户端和 ACME 服务器之间的通信通过 HTTPS 进行, 使用 JSON Web 签名 (JSON Web Signature, JWS) [RFC7515] 为从客户端发送到服务器的消息提供一些额外的安全属性。HTTPS 提供服务器身份验证和机密性。通过一些 ACME 特定的扩展, JWS 提供客户端请求有效载荷的身份验证、防重放保护以及 HTTPS 请求 URL 的完整性。
6.1. HTTPS Requests (HTTPS 请求)
每个 ACME 功能都是通过客户端向服务器 [RFC2818] 发送一系列 HTTPS 请求来完成的, 这些请求携带 JSON 消息 [RFC8259]。必须 (REQUIRED) 使用 HTTPS。下面第 7 节的每个小节描述了该功能使用的消息格式以及消息发送的顺序。
在 ACME 使用的大多数 HTTPS 事务中, ACME 客户端是 HTTPS 客户端, ACME 服务器是 HTTPS 服务器。ACME 服务器在验证挑战时充当客户端: 验证 'http-01' 挑战时是 HTTP 客户端, 验证 'dns-01' 时是 DNS 客户端, 等等。
ACME 服务器在配置其 TLS 实现时应该 (SHOULD) 遵循 [RFC7525] 的建议。支持 TLS 1.3 的 ACME 服务器可以 (MAY) 允许客户端发送早期数据 (0-RTT)。这是安全的, 因为 ACME 协议本身在所有需要的情况下都包含防重放保护 (参见第 6.5 节)。因此, 对于可以在 0-RTT 中携带哪些 ACME 数据没有限制。
ACME 客户端必须 (MUST) 根据 [RFC7231] 发送 User-Agent 头字段。除了底层 HTTP 客户端软件的名称和版本外, 此头字段应该 (SHOULD) 包括 ACME 软件的名称和版本。
ACME 客户端应该 (SHOULD) 根据 [RFC7231] 发送 Accept-Language 头字段, 以启用错误消息的本地化。
旨在普遍可访问的 ACME 服务器需要使用跨源资源共享 (Cross-Origin Resource Sharing, CORS), 以便可以从基于浏览器的客户端访问 [W3C.REC-cors-20140116]。此类服务器应该 (SHOULD) 将 Access-Control-Allow-Origin 头字段设置为值 "*"。
ACME 使用的 JSON 对象中的二进制字段使用 [RFC4648] 第 5 节中描述的 base64url 编码进行编码, 根据 [RFC7515] 第 2 节中 JSON Web 签名中指定的配置文件。此编码使用 URL 安全字符集。必须 (MUST) 去除尾随的 '=' 字符。包含尾随 '=' 字符的编码值必须 (MUST) 被拒绝为编码不当。
6.2. Request Authentication (请求认证)
所有具有非空主体的 ACME 请求必须 (MUST) 将其有效载荷封装在 JSON Web 签名 (JSON Web Signature, JWS) [RFC7515] 对象中, 使用账户的私钥签名, 除非另有说明。服务器必须 (MUST) 在处理请求之前验证 JWS。将请求主体封装在 JWS 中提供了请求的身份验证。
作为 ACME 请求主体发送的 JWS 对象必须 (MUST) 满足以下附加标准:
-
JWS 必须 (MUST) 采用扁平化 JSON 序列化 (Flattened JSON Serialization) [RFC7515]
-
JWS 禁止 (MUST NOT) 具有多个签名
-
禁止 (MUST NOT) 使用 JWS 未编码有效载荷选项 (JWS Unencoded Payload Option) [RFC7797]
-
禁止 (MUST NOT) 使用 JWS 未保护头 (JWS Unprotected Header) [RFC7515]
-
JWS 有效载荷禁止 (MUST NOT) 分离
-
JWS 保护头必须 (MUST) 包括以下字段:
-
"alg" (算法, Algorithm)
- 此字段禁止 (MUST NOT) 包含 "none" 或消息认证码 (Message Authentication Code, MAC) 算法 (例如, 算法注册表描述中提到 MAC/HMAC 的算法)。
-
"nonce" (在第 6.5 节中定义)
-
"url" (在第 6.4 节中定义)
-
"jwk" (JSON Web 密钥, JSON Web Key) 或 "kid" (密钥 ID, Key ID), 如下所述
-
ACME 服务器必须 (MUST) 实现 "ES256" 签名算法 [RFC7518], 并应该 (SHOULD) 实现使用 "Ed25519" 变体 (由 "crv" 指示) 的 "EdDSA" 签名算法 [RFC8037]。
"jwk" 和 "kid" 字段是互斥的。服务器必须 (MUST) 拒绝同时包含两者的请求。
对于 newAccount 请求以及由证书密钥认证的 revokeCert 请求, 必须 (MUST) 有一个 "jwk" 字段。此字段必须 (MUST) 包含与用于签名 JWS 的私钥对应的公钥。
对于所有其他请求, 请求使用现有账户签名, 并且必须 (MUST) 有一个 "kid" 字段。此字段必须 (MUST) 包含通过 POST 到 newAccount 资源接收的账户 URL。
如果客户端发送使用服务器不支持的算法签名的 JWS, 则服务器必须 (MUST) 返回状态码 400 (Bad Request) 和类型 "urn:ietf:params:acme:error:badSignatureAlgorithm" 的错误。随错误返回的问题文档必须 (MUST) 包括一个 "algorithms" 字段, 其中包含支持的 "alg" 值数组。有关错误响应结构的更多详细信息, 请参见第 6.7 节。
如果服务器支持签名算法 "alg" 但不支持或选择拒绝公钥 "jwk", 则服务器必须 (MUST) 返回状态码 400 (Bad Request) 和类型 "urn:ietf:params:acme:error:badPublicKey" 的错误。问题文档详细信息应该 (SHOULD) 描述拒绝公钥的原因; 一些示例原因是:
-
"alg" 是 "RS256" 但模数 "n" 太小 (例如, 512 位)
-
"alg" 是 "ES256" 但 "jwk" 不包含有效的 P-256 公钥
-
"alg" 是 "EdDSA" 且 "crv" 是 "Ed448", 但服务器仅支持带有 "Ed25519" 的 "EdDSA"
-
相应的私钥已知已被泄露
由于 ACME 中的客户端请求在扁平化 JSON 序列化中携带 JWS 对象, 因此它们必须将 Content-Type 头字段设置为 "application/jose+json"。如果请求不满足此要求, 则服务器必须 (MUST) 返回状态码 415 (Unsupported Media Type) 的响应。
6.3. GET and POST-as-GET Requests (GET 和 POST-as-GET 请求)
请注意, 通过签名 JWS 请求主体进行身份验证意味着没有实体主体的请求未经身份验证, 特别是 GET 请求。除了本节中描述的情况外, 如果服务器收到 GET 请求, 它必须 (MUST) 返回状态码 405 (Method Not Allowed) 和类型 "malformed" 的错误。
如果客户端希望从服务器获取资源 (否则将使用 GET 完成), 则它必须 (MUST) 发送带有如上所述的 JWS 主体的 POST 请求, 其中 JWS 的有效载荷是零长度八位字节串。换句话说, JWS 对象的 "payload" 字段必须 (MUST) 存在并设置为空字符串 ("")。
我们将这些称为 "POST-as-GET" 请求。在接收到具有零长度 (因此非 JSON) 有效载荷的请求时, 服务器必须 (MUST) 对发送者进行身份验证并验证任何访问控制规则。否则, 服务器必须 (MUST) 将此请求视为与对同一资源的 GET 请求具有相同的语义。
服务器必须 (MUST) 允许对目录和 newNonce 资源的 GET 请求 (参见第 7.1 节), 以及对这些资源的 POST-as-GET 请求。这使客户端能够引导到 ACME 身份验证系统中。
6.4. Request URL Integrity (请求 URL 完整性)
在部署中, 终止 HTTPS 的 TLS 的实体与操作逻辑 HTTPS 服务器的实体不同是很常见的, 中间有一个 "请求路由" 层。例如, ACME CA 可能有一个内容分发网络终止来自客户端的 TLS 连接, 以便它可以检查客户端请求以进行拒绝服务 (Denial-of-Service, DoS) 保护。
这些中介也可以更改 HTTPS 请求中未签名的请求值, 例如请求 URL 和头字段。ACME 使用 JWS 提供完整性机制, 该机制可防止中介将请求 URL 更改为另一个 ACME URL。
如第 6.2 节所述, 所有 ACME 请求对象在其保护头中携带 "url" 头参数。此头参数编码客户端将请求定向到的 URL。在 HTTP 请求中接收到此类对象时, 服务器必须 (MUST) 将 "url" 头参数与请求 URL 进行比较。如果两者不匹配, 则服务器必须 (MUST) 将请求拒绝为未授权。
除目录资源外, 所有 ACME 资源都使用服务器提供给客户端的 URL 进行寻址。在发送到这些资源的 POST 请求中, 客户端必须 (MUST) 将 "url" 头参数设置为服务器提供的确切字符串 (而不是对 URL 执行任何重新编码)。服务器应该 (SHOULD) 执行相应的字符串相等性检查, 为每个资源配置提供给客户端的 URL 字符串, 并让资源检查请求在其 "url" 头参数中是否具有相同的字符串。如果字符串相等性检查失败, 服务器必须 (MUST) 将请求拒绝为未授权。
6.4.1. "url" (URL) JWS Header Parameter ("url" (URL) JWS 头参数)
"url" 头参数指定此 JWS 对象所针对的 URL [RFC3986]。"url" 头参数必须 (MUST) 在 JWS 的保护头中携带。"url" 头参数的值必须 (MUST) 是表示目标 URL 的字符串。
6.5. Replay Protection (重放保护)
为了保护 ACME 资源免受任何可能的重放攻击, ACME POST 请求具有强制性的防重放机制。此机制基于服务器维护其已颁发的 nonce 列表, 并要求客户端的任何签名请求携带此类 nonce。
ACME 服务器使用 HTTP Replay-Nonce 头字段向客户端提供 nonce, 如第 6.5.1 节所述。服务器必须 (MUST) 在对 POST 请求的每个成功响应中包含 Replay-Nonce 头字段, 并应该 (SHOULD) 在错误响应中也提供它。
ACME 客户端发送的每个 JWS 必须 (MUST) 在其保护头中包含 "nonce" 头参数, 其内容如第 6.5.2 节中定义。作为 JWS 验证的一部分, ACME 服务器必须 (MUST) 验证 "nonce" 头的值是服务器先前在 Replay-Nonce 头字段中提供的值。一旦 nonce 值出现在 ACME 请求中, 服务器必须 (MUST) 将其视为无效, 就像它从未颁发过的值一样。
当服务器因其 nonce 值不可接受 (或不存在) 而拒绝请求时, 它必须 (MUST) 提供 HTTP 状态码 400 (Bad Request), 并指示 ACME 错误类型 "urn:ietf:params:acme:error:badNonce"。带有 "badNonce" 错误类型的错误响应必须 (MUST) 包含一个 Replay-Nonce 头字段, 其中包含服务器将在原始查询的重试中接受的新鲜 nonce (并且可能在其他请求中, 根据服务器的 nonce 范围策略)。在收到此类响应时, 客户端应该 (SHOULD) 使用新 nonce 重试请求。
用于生成和跟踪 nonce 的精确方法由服务器决定。例如, 服务器可以为每个响应生成一个随机的 128 位值, 保留已颁发 nonce 的列表, 并在使用时从此列表中删除 nonce。
除了上述关于在 "badNonce" 响应中颁发的 nonce 的约束外, ACME 不限制服务器如何限定 nonce 的范围。客户端可以 (MAY) 假设 nonce 具有广泛的范围, 例如, 通过为所有请求使用单个 nonce 池。但是, 在响应 "badNonce" 错误时重试时, 客户端必须 (MUST) 使用错误响应中提供的 nonce。服务器应该将 nonce 的范围设置得足够广, 以便不经常需要重试。
6.5.1. Replay-Nonce (Replay-Nonce 头字段)
Replay-Nonce HTTP 头字段包含服务器生成的值, 服务器可以使用该值来检测未来客户端请求中的未授权重放。服务器必须 (MUST) 以这样的方式生成 Replay-Nonce 头字段中提供的值, 即它们对每条消息都是唯一的, 具有高概率, 并且对除服务器之外的任何人都是不可预测的。例如, 随机生成 Replay-Nonces 是可以接受的。
Replay-Nonce 头字段的值必须 (MUST) 是根据 [RFC7515] 第 2 节中描述的 base64url 编码进行编码的八位字节串。客户端必须 (MUST) 忽略无效的 Replay-Nonce 值。Replay-Nonce 头字段的 ABNF [RFC5234] 如下:
base64url = ALPHA / DIGIT / "-" / "_"
Replay-Nonce = 1*base64url
Replay-Nonce 头字段不应该 (SHOULD NOT) 包含在 HTTP 请求消息中。
6.5.2. "nonce" (Nonce) JWS Header Parameter ("nonce" (Nonce) JWS 头参数)
"nonce" 头参数提供一个唯一值, 使 JWS 的验证者能够识别何时发生重放。"nonce" 头参数必须 (MUST) 在 JWS 的保护头中携带。
"nonce" 头参数的值必须 (MUST) 是八位字节串, 根据 [RFC7515] 第 2 节中描述的 base64url 编码进行编码。如果 "nonce" 头参数的值根据此编码无效, 则验证者必须 (MUST) 将 JWS 拒绝为格式错误。
6.6. Rate Limits (速率限制)
ACME 服务器可以对资源创建进行速率限制, 以确保公平使用并防止滥用。一旦超过速率限制, 服务器必须 (MUST) 响应类型为 "urn:ietf:params:acme:error:rateLimited" 的错误。此外, 服务器应该 (SHOULD) 发送 Retry-After 头字段 [RFC7231], 指示当前请求何时可能再次成功。如果有多个速率限制, 那就是所有速率限制都允许使用完全相同参数的当前请求再次访问的时间。
除了错误响应的人类可读 "detail" 字段外, 服务器可以 (MAY) 在 Link 头字段 [RFC8288] 中发送一个或多个链接关系, 使用 "help" 链接关系类型指向有关被触发的特定速率限制的文档。
6.7. Errors (错误)
错误可以在 HTTP 层和挑战对象中报告, 如第 8 节所定义。ACME 服务器可以返回带有 HTTP 错误响应代码 (4XX 或 5XX) 的响应。例如, 如果客户端使用本文档中不允许的方法提交请求, 则服务器可以 (MAY) 返回状态码 405 (Method Not Allowed)。
当服务器以错误状态响应时, 它应该 (SHOULD) 使用问题文档 [RFC7807] 提供附加信息。为了便于自动响应错误, 本文档定义了以下标准令牌, 用于 "type" 字段 (在 ACME URN 命名空间 "urn:ietf:params:acme:error:" 内):
| 类型 | 描述 |
|---|---|
| accountDoesNotExist | 请求指定的账户不存在 |
| alreadyRevoked | 请求指定要吊销的证书已被吊销 |
| badCSR | CSR 不可接受 (例如, 由于密钥太短) |
| badNonce | 客户端发送了不可接受的防重放 nonce |
| badPublicKey | JWS 由服务器不支持的公钥签名 |
| badRevocationReason | 提供的吊销原因不被服务器允许 |
| badSignatureAlgorithm | JWS 使用服务器不支持的算法签名 |
| caa | 证书颁发机构授权 (Certification Authority Authorization, CAA) 记录禁止 CA 颁发证书 |
| compound | 特定错误条件在 "subproblems" 数组中指示 |
| connection | 服务器无法连接到验证目标 |
| dns | 标识符验证期间 DNS 查询出现问题 |
| externalAccountRequired | 请求必须包含 "externalAccountBinding" 字段的值 |
| incorrectResponse | 收到的响应与挑战的要求不匹配 |
| invalidContact | 账户的联系 URL 无效 |
| malformed | 请求消息格式错误 |
| orderNotReady | 请求尝试完成尚未准备好完成的订单 |
| rateLimited | 请求超过速率限制 |
| rejectedIdentifier | 服务器不会为该标识符颁发证书 |
| serverInternal | 服务器遇到内部错误 |
| tls | 服务器在验证期间收到 TLS 错误 |
| unauthorized | 客户端缺乏足够的授权 |
| unsupportedContact | 账户的联系 URL 使用了不支持的协议方案 |
| unsupportedIdentifier | 标识符是不支持的类型 |
| userActionRequired | 访问 "instance" URL 并在那里采取指定的操作 |
此列表并非详尽无遗。服务器可以 (MAY) 返回其 "type" 字段设置为上面定义的 URI 以外的 URI 的错误。服务器禁止 (MUST NOT) 对未在相应 IANA 注册表中列出的错误使用 ACME URN 命名空间 (参见第 9.6 节)。客户端应该 (SHOULD) 显示所有错误的 "detail" 字段。
在本文档的其余部分, 我们使用上表中的令牌来引用错误类型, 而不是完整的 URN。例如, "类型为 'badCSR' 的错误" 是指 "type" 值为 "urn:ietf:params:acme:error:badCSR" 的错误文档。
6.7.1. Subproblems (子问题)
有时 CA 可能需要响应一个请求返回多个错误。此外, CA 可能需要将错误归因于特定标识符。例如, newOrder 请求可能包含 CA 无法为其颁发证书的多个标识符。在这种情况下, ACME 问题文档可以 (MAY) 包含 "subproblems" 字段, 包含问题文档的 JSON 数组, 每个文档可以 (MAY) 包含 "identifier" 字段。如果存在, "identifier" 字段必须 (MUST) 包含 ACME 标识符 (第 9.7.7 节)。
"identifier" 字段禁止 (MUST NOT) 出现在 ACME 问题文档的顶层。它只能出现在子问题中。子问题不需要都具有相同的类型, 并且它们不需要与顶层类型匹配。
ACME 客户端可以选择使用子问题的 "identifier" 字段作为提示, 如果省略该标识符, 操作将成功。例如, 如果订单包含十个 DNS 标识符, 并且 newOrder 请求返回带有两个子问题 (引用其中两个标识符) 的问题文档, 则 ACME 客户端可以选择提交另一个仅包含问题文档中未列出的八个标识符的订单。
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Link: `https://example.com/acme/directory`;rel="index"
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Some of the identifiers requested were rejected",
"subproblems": [
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Invalid underscore in DNS name \"_example.org\"",
"identifier": {
"type": "dns",
"value": "_example.org"
}
},
{
"type": "urn:ietf:params:acme:error:rejectedIdentifier",
"detail": "This CA will not issue for \"example.net\"",
"identifier": {
"type": "dns",
"value": "example.net"
}
}
]
}