摘要

  • RFC 9773 允许认证机构建议客户端何时尝试续期,也允许新订单指明它要替换的旧证书;客户端仍须自行选取随机时点、保存退避与兜底状态、完成 ACME 流程,并在本地控制部署。
  • suggestedWindow、被接受的 replaces、有效订单、下载成功、CT 记录、磁盘文件和真实 TLS 握手回答的是不同问题。只有下一责任方给出自己的证据,状态才应向前推进。

续期曲线变成一堵墙

设想一组证书收到一个长达二十四小时的建议续期窗口。认证机构已经完成它最擅长的工作:根据全局签发负载,把未来的尖峰拆开,并为可能的紧急替换留出空间。

下一次调度器唤醒时,续期曲线却像一堵墙一样竖了起来。

有的客户端把随机时点取整到同一个定时任务边界;有的无法睡到精确时刻,于是看到“所选时点早于下次唤醒”便立即行动;有的在重新部署后丢失失败次数;还有的系统时钟偏了几分钟。认证机构给的是一段区间,本地系统却又把区间还原成了截止点。

这不是对某家认证机构或某次真实事故的描述,而是一项分析场景。它揭示了自动续期中最容易被一个绿色状态遮住的问题:每一步的时钟究竟由谁掌握?

答案会随状态迁移。认证机构知道整个签发生态的负载,也可能最早知道某批证书需要提前替换;客户端知道自己的唤醒周期、失败历史和维护约束;ACME 服务端决定订单能否签发;订阅方决定证书放到哪里、何时重载进程、失败时如何回退;真正提供服务的端点通过当前配置决定握手时出示哪张证书;外部观察者只能证明自己抽样到的路径,不能用一次成功连接代表所有边缘节点。

RFC 9773 的价值,恰恰在于它没有把这些权力揉成一个动作。它提供一条窄而明确的协调信号,把执行留给承担结果的运行系统。

认证机构发布的是建议来源

支持 ACME Renewal Information(ARI)的服务端,会在目录对象中发布 renewalInfo 地址。客户端针对一张证书发送无需认证的 GET 请求。请求路径不是内部数据库编号,也不是运营方临时取的名字,而是把证书 Authority Key Identifier 中的 keyIdentifier 与 DER 编码的序列号分别做 base64url 编码、去掉尾部填充,再以句点连接。

这个构造让请求对象可复现、可核对,但不会把响应变成命令。

响应中必须有 suggestedWindow,其中 start 和 end 给出认证机构建议尝试续期的时间范围;可选的 explanationURL 可以向运营人员解释窗口为何变化,例如分散签发压力,或为一次大规模撤销事件提前更换证书。

“建议”二字就是架构边界。该窗口不是 X.509 的有效期,不会改写 notBefore 或 notAfter;它不是 CRL,也不是 OCSP 状态;它本身不能证明旧证书已被撤销,更不能证明客户端已经读取响应、选定时刻或创建订单。

紧急场景更需要保留这些区别。认证机构可以把整个窗口放到过去,用来敦促立即续期;时钟明显偏慢的客户端却可能仍把它看成未来;没有实现 ARI 的客户端则完全不会看到它。中心端掌握的信息可以提高信号质量,却不能凭空制造本地采用。

随机时点属于客户端

RFC 9773 建议客户端在窗口内均匀随机选择一个时点。如果该时点已过去,就立即尝试续期;如果能精确调度,就等到那个时点;如果所选时点早于下一次正常唤醒,则现在行动;否则等待下一次续期信息检查,再重新评估。

这里没有由认证机构下发唯一预约时间。若认证机构为每张证书指定一个精确时刻,它还必须知道客户端能否准时醒来、本地维护规则是否允许、失败后是否仍应按该时刻重试。窗口让认证机构参与全局协调,同时把执行权留在本地。

均匀随机也不保证曲线天然平滑。实现必须持久保存已经作出的选择,避免每次醒来都抽取一个更方便的新时点;随机源和调度粒度不能让大量克隆实例落到相同位置;时钟偏差需要被监测;舰队级编排器不能把每张证书的独立选择重新合并成一个全局任务边界。

规范特别提醒依赖定时任务的客户端:检查频率提高后,必须保存失败信息,否则更频繁的唤醒会破坏退避。至少应知道同一组订单标识出现过多少次失败、最近一次失败在何时。无状态调度器会忘记自己为什么应该等待,把韧性设计变成对认证机构的额外压力。

如果窗口的 end 等于或早于 start,该对象无效。客户端应把它视为未得到可用响应,转入合适的重试或兜底计划。来自正确地址的错误建议,也不会因此取得执行权。

Retry-After 管的是再次询问

在 ARI 中,Retry-After 的含义比常见 HTTP 用法更具体。它表达下一次获取 RenewalInfo 的期望间隔,既是希望的最短间隔,也是希望的最长间隔;它并不等于“证书订单应在这么久之后启动”。

例如,客户端遵守 Retry-After: 21600,只能说明它接受六小时后刷新续期信息。窗口内选中的实际续期时点仍是另一条本地状态。

错误退避优先。连接超时、请求超时或 5xx 可进入有上限的指数退避;临时重试耗尽,或者遇到缺失与非法的 Retry-After、无效对象、DNS 失败、拒绝连接及非 5xx 错误时,客户端应在六小时后或本地设定的默认间隔后再次询问。

因此,运营系统至少要保存三只钟:何时再次问认证机构、何时尝试续期、ACME 订单失败后何时重试。若都塞进一个名为 next_run 的字段,事后就无法判断延迟来自认证机构的刷新建议、本地随机选择、信息获取故障,还是签发失败。

Let’s Encrypt 的集成指南建议每张证书每天至少检查两次 ARI,同时保留基于剩余有效期的兜底。这是特定运营方的第一手实践建议,不是 RFC 9773 规定的全球常数。成熟客户端必须分清哪些限制来自协议、哪些来自认证机构、哪些来自本地负责人。

replaces 建立血缘,不签发部署回执

ARI 为 ACME 订单增加可选字段 replaces。它使用与 RenewalInfo 请求相同的证书标识,说明新订单意在替换哪一张明确的旧证书。

这条血缘关系很有价值。认证机构可以识别符合条件的续期,在建议窗口内按自身政策给予优先级或速率待遇,也可以跟踪受合规事件影响的证书是否已有后继者。服务端依据政策核对账户和标识关系;如果同一旧证书已被另一张非无效订单标记为替换,则返回 HTTP 409 与 alreadyReplaced。

但这仍是签发层关系。replaces 被接受,不代表客户端已下载新证书,不代表负载均衡器已收到文件,不代表 Web 服务完成重载,也不代表旧私钥已离开内存。即使认证机构记录“已替换”,也必须按服务端协议语义来理解,不能扩张成订阅方基础设施的证明。

这种克制不是信息不足,而是安全设计。认证机构可以协调续期血缘,却不应被要求为它无法观察的本地部署背书。

已签发不等于已安装

证书签发仍由 RFC 8555 的 ACME 流程完成。客户端提交新订单,在需要时完成标识授权,把 CSR 发送到 finalize 地址,观察订单状态变化,再从订单中的证书地址下载结果。

每个状态只能证明有限事实。

ready 表示服务端认为签发要求已满足,等待最终提交;processing 表示正在签发;valid 表示认证机构已签发证书并填入下载地址;成功下载则证明客户端取得了相应字节。

以上任何一项都不会自动把证书装进真实服务。

下载与启用之间还隔着文件权限、密钥分发、证书链拼接、私钥匹配、配置校验、进程重载、连接排空、边缘复制和回退。一套集中证书系统可能显示完成,而旧工作进程仍在提供服务;新文件可以躺在磁盘上,却没有任何进程重新打开;一个区域完成收敛,另一个区域仍出示旧证书。

可解释的状态机至少应包含:

  1. RenewalInfo 已获取并验证;
  2. 随机续期时点已选定并持久保存;
  3. 带有旧证书血缘的新订单已建立;
  4. 授权与最终提交完成;
  5. 新证书下载并计算指纹;
  6. 文件送达明确的部署目标;
  7. 配置通过检查,进程完成重载;
  8. 本地服务出示新指纹;
  9. 外部观察覆盖预定的边缘路径;
  10. 在有界恢复期结束后退役旧材料。

一个“已续期”布尔值无法容纳这些状态,只能把故障藏起来。

CT 看见签发,不看见服务路径

证书透明度(CT)是另一种容易被误读的绿色信号。RFC 9162 描述的是公开记录已签发或已被观察到的 TLS 服务端证书,使监测者可以审计认证机构活动、发现异常签发并验证日志的追加属性。

这是强证据,却不是部署证据。

证书可以在订阅方下载之前进入日志;第三方可以提交证书链;预证书也会在签发过程中留下记录。CT 日志不会逐个访问生产端点,也不知道哪台负载均衡器当前持有对应私钥。

正确结论是:该证书或预证书按照日志规则进入了透明度系统。错误结论是:线上服务已经采用它。

CT 仍应进入证据账本,因为它可以暴露未经预期的签发,也能帮助核对认证机构输出;但它不能独自推动部署状态向前。

TLS 握手更接近现实,但仍只是抽样

TLS 1.3 提供更直接的观察。在使用证书认证的握手中,服务端通过 Certificate 消息发送证书链,以 CertificateVerify 证明掌握对应私钥,并用 Finished 完成对握手记录的认证。

因此,对明确端点发起一次绕过缓存的新连接,可以证明该端点在那个时刻出示了哪张证书。应把叶证书指纹、签发者、标识和有效期与 ACME 订单的预期产物核对,并分别覆盖相关网络路径、地址族、区域、终止层及 SNI 变化。

证据仍有范围。一次成功连接不能证明所有 Anycast 站点、CDN 边缘或负载均衡成员都已收敛;只测一个 IPv4 地址可能漏掉仍在使用旧证书的 IPv6 路径;会话恢复可能不重新交换完整证书;内网探针可能终止在与公网流量不同的一层。

运行证据只有在观察边界被写清时才最有力量。“时间 T 从路径 P 看到端点 E 在全新握手中出示指纹 F”是可审计事实;“全球已部署”是更大的结论,需要相称的抽样设计。

建立因果账本

每张证书都应保留相互关联、但不可相互冒充的事实:

  • 证书标识、认证机构目录和最近一次成功获取 RenewalInfo 的时间;
  • 原始窗口起止、说明地址、响应指纹与 Retry-After;
  • 选定时点、选择方法、时钟偏差、调度粒度与持久化记录;
  • 兜底阈值、失败次数、最近失败与下次允许重试时间;
  • 旧证书标识、ACME 账户、订单地址与 replaces 结果;
  • 授权、最终提交和有效订单的时间;
  • 下载证书的指纹、证书链、密钥匹配与保存位置;
  • 分发目标、配置检查、重载回执与回退状态;
  • 按边缘、地址族和终止层记录的新鲜握手观察;
  • 旧证书退役时间,以及“不再需要恢复”的证明。

私钥、完整账户凭据和无限制拓扑不应进入普通分析面。最小化证据并不妨碍建立因果链。指纹、受控目标标识和带责任人的操作记录,通常已经足以回答关键问题。

认证机构报告建议与签发,客户端报告选择与订单进度,部署方报告文件保管与重载,端点在握手中出示证书,观察者记录特定路径。没有哪一层需要借用下一层的权力。

运行代码补完最后一句

更广泛的互联网工程纪律在这里同样适用:建议不是运行现实,发布不是采用,协调性文档只有被参与者实现、在本地验证并投入运行后,才产生相应事实。

ARI 的共享层很薄:发布信息资源、确定证书标识、表达建议窗口、给出刷新节奏、传递替换血缘。它无须接管订阅方部署日程,也无权宣布某项服务已经改变。

本地未来决策体现在客户端选择时点、退避和兜底;自愿采用体现在客户端是否实现 ARI,以及如何把信号接入自己的续期机制;运行代码优先体现在最后一步:新证书只有被运行系统实际出示和使用,才成为服务现实。

认证机构可以掌握窗口,而不掌握每只本地时钟;客户端可以掌握尝试,而不决定签发;部署系统可以完成安装,而不能凭自身回执代表所有路径;观察者可以证明一条路径,而不能把它扩张成整个舰队。

这不是责任碎片化,而是责任精确化。自动化只有在这种边界上,才能同时变快和变安全。

来源