Skip to main content

8. Identifier Validation Challenges (标识符验证挑战)

世界上很少有标识符类型具有标准化机制来证明对给定标识符的拥有。在所有实际情况下, CA 依赖各种手段来测试申请具有给定标识符的证书的实体是否实际控制该标识符。

挑战为服务器提供保证, 账户持有者也是控制标识符的实体。对于每种类型的挑战, 必须满足以下条件: 为了实体成功完成挑战, 实体必须同时:

  • 持有用于响应挑战的账户密钥对的私钥, 以及
  • 控制所讨论的标识符。

第 10 节记录了本文档中定义的挑战如何满足这些要求。新挑战需要记录它们如何满足。

ACME 使用可扩展的挑战/响应框架进行标识符验证。服务器在发送给客户端的授权对象中呈现一组挑战 (作为 "challenges" 数组中的对象), 客户端通过向挑战 URL 发送 POST 请求中的响应对象进行响应。

本节描述了一组初始挑战类型。挑战类型的定义包括:

  1. 挑战对象的内容
  2. 响应对象的内容
  3. 服务器如何使用挑战和响应来验证对标识符的控制

挑战对象都包含以下基本字段:

type (必需, 字符串): 对象中编码的挑战类型。

url (必需, 字符串): 可以将响应发布到的 URL。

status (必需, 字符串): 此挑战的状态。可能的值为 "pending", "processing", "valid" 和 "invalid" (参见第 7.1.6 节)。

validated (可选, 字符串): 服务器验证此挑战的时间, 以 [RFC3339] 中指定的格式编码。如果 "status" 字段为 "valid", 则此字段是必需的 (REQUIRED)。

error (可选, 对象): 服务器验证挑战时发生的错误 (如果有), 被构建为问题文档 [RFC7807]。可以使用子问题 (第 6.7.1 节) 指示多个错误。具有错误的挑战对象的状态必须 (MUST) 等于 "invalid"。

所有其他字段由挑战类型指定。如果服务器将挑战的 "status" 设置为 "invalid", 它应该 (SHOULD) 还包括 "error" 字段以帮助客户端诊断挑战失败的原因。

不同的挑战允许服务器获得对标识符控制的不同方面的证明。在某些挑战中, 如 HTTP 和 DNS, 客户端直接证明其执行与标识符相关的某些操作的能力。在何种情况下向客户端提供哪些挑战的选择是服务器策略的问题。

本节中描述的标识符验证挑战都与域名验证有关。如果将来扩展 ACME 以支持其他类型的标识符, 则需要有新的挑战类型, 并且它们需要指定它们适用于哪些类型的标识符。

8.1. Key Authorizations (密钥授权)

本文档中定义的所有挑战都使用密钥授权字符串。密钥授权是一个字符串, 它将挑战的令牌与密钥指纹连接起来, 由 "." 字符分隔:

keyAuthorization = token || '.' || base64url(Thumbprint(accountKey))

"Thumbprint" 步骤表示 [RFC7638] 中指定的计算, 使用 SHA-256 摘要 [FIPS180-4]。如 [RFC7518] 中所述, 在进行计算之前, 必须 (MUST) 去除 JWK 对象字段中的任何前置零八位字节。

如下面各个挑战中所指定的, 挑战的令牌是完全由 URL 安全 base64 字母表中的字符组成的字符串。"||" 运算符表示字符串的连接。

8.2. Retrying Challenges (重试挑战)

ACME 挑战通常要求客户端设置一些网络可访问的资源, 服务器可以查询该资源以验证客户端控制标识符。在实践中, 在设置资源时服务器的查询失败并不罕见, 例如, 由于信息在集群中传播或防火墙规则尚未就位。

客户端不应该 (SHOULD NOT) 响应挑战, 直到它们相信服务器的查询将成功。如果服务器的初始验证查询失败, 服务器应该 (SHOULD) 在一段时间后重试查询, 以考虑设置响应 (如 DNS 记录或 HTTP 资源) 的延迟。精确的重试计划由服务器决定, 但服务器操作员应牢记计划试图适应的操作场景。鉴于重试旨在解决 HTTP 或 DNS 配置中的传播延迟等问题, 通常不应该有任何理由每 5 或 10 秒重试一次以上。当服务器仍在尝试时, 挑战的状态保持 "processing"; 只有在服务器放弃后才会标记为 "invalid"。

服务器必须 (MUST) 通过挑战中的 "error" 字段和响应挑战资源请求的 Retry-After HTTP 头字段向客户端提供有关其重试状态的信息。服务器必须 (MUST) 在每次失败的验证查询后向挑战中的 "error" 字段添加一个条目。服务器应该 (SHOULD) 将 Retry-After 头字段设置为服务器下一次验证查询之后的时间, 因为挑战的状态在该时间之前不会改变。

客户端可以通过在新的 POST 请求中重新发送对挑战的响应 (使用新的 nonce 等) 来显式请求重试。这允许客户端在状态发生更改时 (例如, 在更新防火墙规则后) 请求重试。服务器应该 (SHOULD) 在收到此类 POST 请求时立即重试请求。为了避免通过客户端发起的重试进行拒绝服务攻击, 服务器应该 (SHOULD) 对此类请求进行速率限制。

8.3. HTTP Challenge (HTTP 挑战)

通过 HTTP 验证, ACME 事务中的客户端通过证明它可以在该域名下可访问的服务器上配置 HTTP 资源来证明其对域名的控制。ACME 服务器挑战客户端在特定路径上配置文件, 并以特定字符串作为其内容。

由于域名可能解析为多个 IPv4 和 IPv6 地址, 服务器将自行决定连接到 DNS A 和 AAAA 记录中找到的至少一个主机。由于许多 Web 服务器以微妙且不直观的方式将默认 HTTPS 虚拟主机分配给特定的低特权租户用户, 因此挑战必须通过 HTTP 而不是 HTTPS 完成。

type (必需, 字符串): 字符串 "http-01"。

token (必需, 字符串): 唯一标识挑战的随机值。此值必须 (MUST) 至少具有 128 位熵。它禁止 (MUST NOT) 包含 base64url 字母表之外的任何字符, 并且禁止 (MUST NOT) 包括 base64 填充字符 ("=")。有关随机性要求的其他信息, 请参见 [RFC4086]。

{
"type": "http-01",
"url": "https://example.com/acme/chall/prV_B7yEyA4",
"status": "pending",
"token": "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
}

客户端通过从挑战中提供的 "token" 值和客户端的账户密钥构造密钥授权来完成此挑战。然后, 客户端将密钥授权作为资源配置在所讨论域名的 HTTP 服务器上。

配置资源的路径由固定前缀 "/.well-known/acme-challenge/" 组成, 后跟挑战中的 "token" 值。资源的值必须 (MUST) 是密钥授权的 ASCII 表示。

GET /.well-known/acme-challenge/LoqXcYV8...jxAjEuX0
Host: example.org

HTTP/1.1 200 OK
Content-Type: application/octet-stream

LoqXcYV8...jxAjEuX0.9jg46WB3...fm21mqTI

(在上面的示例中, "..." 表示令牌和密钥授权中的 JWK 指纹已被截断以适应页面。)

客户端使用空对象 () 进行响应以确认服务器可以验证挑战。

POST /acme/chall/prV_B7yEyA4
Host: example.com
Content-Type: application/jose+json

{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "UQI1PoRi5OuXzxuX7V7wL0",
"url": "https://example.com/acme/chall/prV_B7yEyA4"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}

在接收到响应时, 服务器从挑战 "token" 值和当前客户端账户密钥构造并存储密钥授权。

给定挑战/响应对, 服务器通过验证资源是否按预期配置来验证客户端对域名的控制。

  1. 通过填充 URL 模板 [RFC6570] "http://{domain}/.well-known/acme-challenge/{token}" 构造 URL, 其中:

    • domain 字段设置为正在验证的域名; 以及
    • token 字段设置为挑战中的令牌。
  2. 验证生成的 URL 格式良好。

  3. 使用 HTTP GET 请求取消引用 URL。此请求必须 (MUST) 发送到 HTTP 服务器上的 TCP 端口 80。

  4. 验证响应的主体是格式良好的密钥授权。服务器应该 (SHOULD) 忽略主体末尾的空白字符。

  5. 验证 HTTP 服务器提供的密钥授权与服务器存储的密钥授权匹配。

服务器在取消引用 URL 时应该 (SHOULD) 遵循重定向。例如, 客户端可能使用重定向, 以便响应可以由集中式证书管理服务器提供。有关与重定向相关的安全考虑, 请参见第 10.2 节。

如果上述所有验证都成功, 则验证成功。如果请求失败, 或主体未通过这些检查, 则验证失败。

客户端应该 (SHOULD) 在挑战完成后取消为此挑战配置的资源, 即, 一旦挑战的 "status" 字段的值为 "valid" 或 "invalid"。

请注意, 由于令牌同时出现在 ACME 服务器发送的请求和响应中的密钥授权中, 因此可以构建从请求到响应复制令牌的客户端。客户端应避免这种行为, 因为它可能导致跨站点脚本漏洞; 相反, 客户端应该在每个挑战的基础上进行显式配置。确实从请求到响应复制令牌的客户端必须 (MUST) 验证请求中的令牌与上面的令牌语法匹配 (例如, 它仅包含来自 base64url 字母表的字符)。

8.4. DNS Challenge (DNS 挑战)

当正在验证的标识符是域名时, 客户端可以通过为特定验证域名配置包含指定值的 TXT 资源记录来证明对该域名的控制。

type (必需, 字符串): 字符串 "dns-01"。

token (必需, 字符串): 唯一标识挑战的随机值。此值必须 (MUST) 至少具有 128 位熵。它禁止 (MUST NOT) 包含 base64url 字母表之外的任何字符, 包括填充字符 ("=")。有关随机性要求的其他信息, 请参见 [RFC4086]。

{
"type": "dns-01",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q",
"status": "pending",
"token": "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
}

客户端通过从挑战中提供的 "token" 值和客户端的账户密钥构造密钥授权来完成此挑战。然后, 客户端计算密钥授权的 SHA-256 摘要 [FIPS180-4]。

配置到 DNS 的记录包含此摘要的 base64url 编码。客户端通过在正在验证的域名前添加标签 "_acme-challenge" 来构造验证域名, 然后在该名称下配置带有摘要值的 TXT 记录。例如, 如果正在验证的域名是 "www.example.org", 则客户端将配置以下 DNS 记录:

_acme-challenge.www.example.org. 300 IN TXT "gfj9Xq...Rg85nM"

客户端使用空对象 () 进行响应以确认服务器可以验证挑战。

POST /acme/chall/Rg5dV14Gh1Q
Host: example.com
Content-Type: application/jose+json

{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "SS2sSl1PtspvFZ08kNtzKd",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}

在接收到响应时, 服务器从挑战 "token" 值和当前客户端账户密钥构造并存储密钥授权。

要验证 DNS 挑战, 服务器执行以下步骤:

  1. 计算存储的密钥授权的 SHA-256 摘要 [FIPS180-4]

  2. 查询验证域名的 TXT 记录

  3. 验证其中一条 TXT 记录的内容与摘要值匹配

如果上述所有验证都成功, 则验证成功。如果未找到 DNS 记录, 或 DNS 记录和响应有效载荷未通过这些检查, 则验证失败。

客户端应该 (SHOULD) 在挑战完成后取消为此挑战配置的资源记录, 即, 一旦挑战的 "status" 字段的值为 "valid" 或 "invalid"。