摘要

  • v3 .onion 名称来自 Ed25519 服务身份公钥,而不是 DNS 委派;“可达”和“账户已认证”都不是完整的身份控制证明。
  • RFC 9799 新增 onion-csr-01:由洋葱身份密钥签名、同时绑定 CA 与申请人随机数的验证 CSR,并强制它与最终证书 CSR 使用不同公钥。
  • CAA 可以置于加密描述符中,也可以作为带到期时间的身份密钥签名记录集随最终请求提交;Tor 路径、重定向和 CT 公开性则是另一组隐私决定。

“看得见服务”为什么不能升级成“控制着名称”

证书自动化最容易制造的一种错觉,是把流程中的最后一个绿色状态当成此前所有判断的总证明。CA 访问到了服务,挑战返回正确内容,于是控制似乎已经成立;订单进入可终结状态,最终 CSR 又携带了一把可用公钥,于是发行似乎也已获得全部授权。

对 .onion 而言,这条推理缺了一层根。RFC 7686 明确把它置于普通 DNS 之外。Tor 的 v3 地址把服务的 Ed25519 身份公钥、校验值和版本编码进名称本身。它长得像域名,RFC 9799 也为了兼容继续使用 ACME 的 dns 标识符类型,但其权威不来自注册商或 DNS 委派。

因此,能在某条 Tor 路径上应答的应用,不一定交出了从名称导出的私钥证明;能签署 ACME 请求的账户密钥,也不是洋葱身份密钥;最终 CSR 中准备放进证书的公钥,更不能自动替代身份控制证明。RFC 9799 没有用一个万能字段掩盖差别,而是为每一层安排不同的职责。

dns-01 被明确禁止,因为不存在可供查询的 .onion DNS TXT 权威。http-01 与 tls-alpn-01 可以使用,但必须采用 RFC 9799 规定的 Tor 连接方式;CA/B Forum 基线要求还要求 CA 自己建立 Tor 连接,不能把验证外包给 Tor2Web。这两种既有方法仍不能签发通配符证书。

需要直接回答身份密钥时,协议提供 onion-csr-01。CA 给出至少含 64 位熵的随机数;若响应使用的随机数是 CA 在 30 天以前生成的,服务器必须拒绝。申请人生成一份专用 PKCS#10 请求,把 CA 随机数的原始字节放入 caSigningNonce,再生成至少 64 位熵的申请人随机数放入 applicantSigningNonce,最后用洋葱服务私钥签名。

CA 对这份请求执行五项检查:结构是否是有效 PKCS#10;公钥是否对应所验证的 .onion 名称;签名能否由该洋葱公钥验证;CA 随机数是否精确匹配;申请人随机数是否存在并满足熵要求。CSR 的 subject 没有证明意义,CA 不得验证它。更关键的是,这份验证 CSR 的公钥不得与最终订单 CSR 的公钥相同。

这不是形式上的繁琐,而是密钥生命周期的隔离。洋葱身份密钥只回答“谁控制由此导出的名称”;最终证书密钥可以独立轮换;ACME 账户密钥认证承载证明的协议通道。onion-csr-01 也不再使用常规 ACME 的 key authorization 字符串,因为验证 CSR 已经通过由账户密钥签名的 ACME 请求传输。三把密钥各证明一件事,任何一把都不能替另外两把作证。

描述符访问是一次授权,不是“服务可达”的副产品

启用受限发现的洋葱服务会为授权客户端加密描述符。CA 即使可以观察到某个应用响应,也可能仍然无法解开包含引介点与 CAA 的内层描述符。RFC 9799 允许挑战对象携带 authKey,即 CA 准备用于描述符访问的 Ed25519 公钥。

CA 不得把同一把公钥用于多个洋葱服务,但可以在同一服务重新验证时复用。这个约束直接切断了一条默认的关联路径:若一把访问钥匙横跨多个服务,CA 仅凭凭据就能把本来希望分离的运营面串在一起。

CA 根据 Tor 规则计算自己的 CLIENT-ID,再查找匹配的 auth-client 行。协议没有另设一个“本服务要求客户端认证”的声明位;没有匹配项时,CA 必须假定服务不要求认证并继续。若 authKey 对该服务是新的,运营者要加入它、重新签名并发布描述符,然后等待目录传播。

RFC 没把传播写成固定时延。它判断通常应在几分钟内完成,却承认网络运行变化会影响结果;因此要求挑战不能在不合理的短时间内过期,并建议至少留出 30 分钟。这两个数字不能混成 SLA:前者是预期,后者是 CA 挑战寿命的建议下限。

客户端对授权挑战作出响应,是给 CA 的同步信号:现在应当已有机会读到新增访问钥匙。启用认证时,CA 要等到这个信号之后再查 CAA。审计证据于是不能只记“客户端已确认”,还要把 CA 公钥、描述符修订、发布时间、传播观察、计算出的客户端标识和首次成功解密串起来。

CAA 离开 DNS,但“谁有权发行”的问题没有消失

身份控制回答的是申请人是否掌握名称之根,CAA 回答的则是哪个发行者、用什么方法可以发行。RFC 9799 把 RFC 8659 的 flags tag value 形式放进洋葱服务描述符的第二加密层。记录的约束语义保持不变,查找算法却必须改变。

这里没有 DNS 树可向上攀爬,也不存在可查询的 .onion 顶级域 CAA。一个基础洋葱地址下的所有子域共享同一组 CAA。把普通 DNS 的父级查找直觉带进来,不仅会失败,还会伪造一个并不存在的权威层级。

受限描述符带来一个困难:未知 CA 可能看不见第二层政策。为此,第一层可以出现 caa-critical。它不公开 CAA 内容,只公开“这里存在一项必须读取的政策”;CA 在成功解密并解析第二层前不得继续发行。这个标记是一道停机闸门,不是“CAA 已经核验”的完成凭证。

另一条路径是带内 Onion CAA。客户端可以在最终请求里为每个洋葱名称附上 onionCAA:CAA 字符串或 null、Unix 到期时间,以及洋葱身份私钥生成的 Ed25519 签名。到期时间不应超过未来八小时,服务器必须用至少 64 位整数处理。签名对象的字节序列也被严格固定:onion-caa|、无前导零的十进制到期时间、分隔符和 CAA 文本;null 按空字符串计算。

签名有效只说明这组字节可归于洋葱身份密钥,并且还在可接受时间窗内。它并不替 CA 决定数据源。CA 可以仅依据带内记录发行,也可以忽略它而读取描述符,或无论如何都去读描述符。若服务器没有读取描述符 CAA 的能力、客户端又没提供 onionCAA,服务器返回 onionCAARequired;目录里的 inBandOnionCAARequired 可以提前声明这一要求。

所以,合规记录必须保存 CA 最终选择了哪条路径、验证了哪些字节和到期时间、应用了哪组 CAA,而不能从证书结果倒推。Tor 目录本来就是不受信任的分发者;描述符路径与带内路径的权威最终都落在同一把洋葱身份密钥上。移动的是证据载体,不是授权根。

一张正确证书也可能对应一次错误的隐私决定

RFC 9799 要求 CA 自己通过 Tor 连接洋葱服务,不能委托第三方。它也建议 ACME 客户端通过 Tor 访问 CA,并在 CA 提供洋葱端点时优先采用。若客户端直接连接一个地址稳定、用途明显的 CA 端点,请求主机上存在洋葱服务的事实就可能被旁观者推断出来。

http-01 重定向又会打开一条泄露支线。若重定向到公共 DNS 主机,CA 会看到其 IP 或与洋葱服务之间的基础设施关系。与此同时,这条公共域验证又不得经 Tor 出口完成,因为出口劫持会破坏另一类验证安全。

最不可逆的一步发生在公开信任证书进入 Certificate Transparency 时:洋葱名称将公开可见。对某些服务,浏览器认可的公共信任值得这一代价;对内部或非公开服务,私有信任或自签名可能更合适。标准没有替运营者决定,只要求在申请前正视结果,并建议尽量少交订户信息。若不希望 CA 关联多个洋葱服务,还必须分别使用 ACME 账户密钥。

因此,完整证据链不应缩写成“证书已签发”。它应当依次保存:名称背后的身份公钥、账户认证的订单、新鲜且绑定双随机数的身份签名、针对该服务的 CA 描述符访问授权与传播、CA 实际采用的 CAA 来源、独立的最终证书公钥,以及沿途接受的连接与公开性代价。RFC 9799 提供的是每个交接点的最小共同规则,而不是让最后一张证书覆盖此前所有本地决定。

来源