xacme:证书自动化签发
HTTPS 的续命术:ACME(RFC 8555)客户端把账户、订单、dns-01 挑战与证书获取装进一个函数调用——配合磁盘存储实现到期自动续。
本章导读
build.bat xacme 变体。生产联测需真实域名与 DNS 控制权。38.1 ACME 流程
RFC 8555 的核心循环:向 CA 的 directory 注册账户(ES256 密钥 + JOSE 签名)→ 下单域名(可含通配符)→ 完成 dns-01 挑战(在 DNS 上发布 CA 给的 TXT 记录)→ finalize 用 CSR 生成证书 → 下载链。xacmeClientInit 建客户端(解析 directory、注册/复用账户),xacmeClientIssue 跑完整个循环返回 PEM 链。CA 中立:LE / ZeroSSL / Google / Buypass 预设常量,换 CA 只是换 URL。
38.2 DNS provider
dns-01 需要 DNS 写权限:provider 接口是纯结构(id / Add / Remove / 可选 Propagate 回调),内建阿里云(dns_ali,V3 签名);其它厂商自填结构即可接入,零注册零全局状态。库自带 TXT 传播探测——向权威 NS 发 UDP 查询确认记录可見后再通知 CA 验证。
38.3 签发与续期
xacmeclient* pClient;
xstrview tDomain = xrtStrView("example.com");
bool bRenewed;
pClient = ...; /* xacmeClientInit(引擎, CA pem, 目录URL, 账户密钥) */
char* sChain = xacmeClientIssueStored(pClient, &tDomain, 1,
&tDnsProvider, sStoreRoot, 30, &bRenewed);
if ( sChain != NULL && bRenewed ) {
install_cert(sChain); /* 热替换(ch47 演练过) */
}
xrtFree(sChain);
IssueStored 的语义:本地证书剩余寿命 ≥ 阈值天数就直接返回现有链(pbRenewed=false);不足则走完整签发并落盘。剩余天数判断基于 x509 解析(xrt),不依赖外部进程。
38.4 存储与恢复
store 是目录约定:accounts/ 存账户密钥(PKCS#8 PEM,Init 可复用免重复注册)、certs/<域名>/ 存全链与 CA 溯源,原子写入(xrtFileWriteAtomic)。续期判断、证书回读、CA 链回读各有独立函数——不用 IssueStored 也能拼自己的续期策略。
38.5 契约
| 关注 | 要点 |
|---|---|
| 所有权 | 客户端/Issue 返回的链均库分配;Destroy/xrtFree 释放 |
| 网络 | 自带同步 HTTPS 传输(可借引擎或自建);调用量集中在签发窗口 |
| 凭据 | 账户密钥与 DNS 密钥走 store/环境(ch44 分层),绝不入源码 |
| 失败 | 线程错误带域 "xrt.acme.flow" 的稳定错误码,可分类重试 |
常见错误
- 症状
- 频繁重签触发 Let's Encrypt 速率限制,域名被锁数小时。
- 原因
- 调试期应使用 LE staging 预设(不占正式配额);IssueStored 的续期阈值别设成"每次都续"。
- 症状
- 重启后账户对不上,CA 视为新注册,订单历史丢失。
- 原因
- 账户密钥要经 store 持久化并在 Init 传入复用——IssueStored 已代劳,绕开它就要自己管。
编程练习
staging 目录 + 账户配置 + provider 注册表(Count/Id)跑通;NeedRenew 对一份近到期证书返回真。
真实域名 + LE staging 完成一次 dns-01 签发并落盘。
ch41 定时器每日检查,不足 30 天自动 IssueStored + 热替换(ch47 的零断连演练)。
小结
| 要点 | 结论 |
|---|---|
| 流程 | directory→账户→订单→dns-01→finalize→下载,一函数封装 |
| DNS | provider 纯结构接口,内建阿里云,自带 TXT 探测 |
| 续期 | IssueStored 按剩余天数决定签/不签,原子落盘 |
| 存储 | 账户密钥复用 + 全链溯源,目录约定清晰 |
| 纪律 | staging 调试;密钥持久化;凭据不入源码 |
- xrt 参考 · x509 — 到期解析与信任链