### CRL 详解:格式、分发点与验证流程

OCSP与CRL双模式切换改造实战 - 第3节

3.1 CRL 结构长什么样?

CRL,全称 Certificate Revocation List,说白了就是一张证书黑名单。CA 把那些还没到期就被提前吊销的证书的序列号,统一列出来签个名,谁要查谁下载。

你打开一个 CRL 文件,看到的其实是 DER 编码的二进制。用 openssl 命令转成文本,会发现结构很清晰。我以前第一次看 CRL 的 ASN.1 定义时,感觉挺复杂,其实核心就三层:

CertificateList  ::=  SEQUENCE  {
    tbsCertList          TBSCertList,        -- 吊销列表主体
    signatureAlgorithm   AlgorithmIdentifier, -- 签名算法
    signatureValue       BIT STRING          -- CA 的签名
}

TBSCertList  ::=  SEQUENCE  {
    version                 Version OPTIONAL,
    signature               AlgorithmIdentifier,
    issuer                  Name,
    thisUpdate              Time,
    nextUpdate              Time,
    revokedCertificates     SEQUENCE OF SEQUENCE  {
        userCertificate         CertificateSerialNumber,
        revocationDate          Time,
        crlEntryExtensions      Extensions OPTIONAL
    } OPTIONAL,
    crlExtensions           [0]  Extensions OPTIONAL
}

嗯,要注意 revokedCertificates 这个字段是可选的。如果 CA 一张证书都没吊销过,这个字段直接为空,但 CRL 本身依然存在。

3.1.1 核心字段我挑几个重点讲

字段 含义 我在项目里踩过的坑
thisUpdate CRL 的签发时间 我见过某产品时间偏差几分钟,客户端直接拒掉整张 CRL
nextUpdate CA 承诺下次发布新 CRL 的时间 一旦错过这个时间,旧 CRL 失效,新 CRL 没到,所有证书都变不可信
userCertificate 被吊销证书的序列号 注意序列号是整数,不是字符串,比对时不要直接 strcmp
revocationDate 吊销发生的具体时间 这个时间比证书作废时间更关键,审计时就看它
crlExtensions 扩展字段,比如 CRL Number、Issuing Distribution Point 有些客户端不解析扩展,拿不到分片信息,直接报错

你想想看,如果 CA 的 nextUpdate 写的是 2025-06-01 00:00:00,但 CA 在 06-02 才发出新 CRL,中间这 48 小时就是个危险窗口。我在生产环境里配过监控,专门盯着这个时间差。

3.1.2 CRL 条目里的扩展

每个吊销条目后面还能挂扩展,最常见的是 CRLReason 和 Invalidity Date。我以前维护的一套旧 CA 系统,管理员经常忘记填 Reason,结果审计时问起来“为什么吊销”,谁也说不清。

小经验: 吊销证书时,顺手把 reason 填上。将来做合规检查、故障回溯,少很多麻烦。

Reason 取值包括:unspecified、keyCompromise、cACompromise、affiliationChanged、superseded、cessationOfOperation、certificateHold。注意 removeFromCRL 这个值在完整 CRL 里不会出现,那是 Delta CRL 才用的。

3.2 CRL 分发点(CDP)怎么配?

3.2.1 CDP 在证书里哪个位置?

每个 X.509 证书的扩展域里,有个字段叫 CRL Distribution Points。你随便抓一张 HTTPS 网站的证书,openssl 看一下就能找到。

# 查看证书中的 CDP 地址
openssl x509 -in cert.pem -text -noout | grep -A 4 "CRL Distribution"

# 输出示例
    X509v3 CRL Distribution Points:
        Full Name:
            URI:http://crl.example.com/ca.crl

这里最关键的是 URI 字段。客户端就是凭这个地址去下载 CRL 的。我曾经碰到过一个案例,管理员配 CDP 时把“http”写成了“https”,有些客户端不支持 HTTPS 下载,结果 CRL 拿不到,把所有证书都当成无效处理。

3.2.2 CDP 配置时的常见陷阱

  • CDP 地址应该用 HTTP,不用 HTTPS —— 证书验证阶段还没建立 TLS,拿 CRL 要走普通 HTTP 最保险
  • 多个 CDP 地址要按顺序配 —— 客户端会依序尝试,第一个连不上再试第二个
  • CDP URL 里不能带路径参数 —— 有些 CA 软件自动加了 ?oid=xxx,客户端不认
  • 注意 URL 编码 —— 路径里有中文或特殊符号时,必须转义
重要: 证书里的 CDP 地址一旦签发就无法修改。证书到期前发现 CDP 错了,只能重签证书。我当年手抖配错了一套线上环境的 CDP,后来花了整整一天批量重新签发。

3.2.3 CDP 的扩展字段细节

CDP 里除了 URI,还有几个子字段有讲究:

  • reasons —— 指明这个分发点只覆盖特定吊销原因
  • cRLIssuer —— 指明该 CRL 由哪个 CA 签发,一般是本 CA 自己
  • distributionPoint —— 实际的网络位置和目录

我建议在生产环境里只配一个主分发点,再加一个备用的。配太多 CDP 会让客户端反复尝试,拖慢 TLS 握手速度。记得之前有个金融客户,证书里塞了 5 个 CDP,用户体验奇差无比。

3.3 CRL 验证流程一步步拆解

这部分我画了一张流程图,帮你把整个验证过程串起来。

CRL 验证流程 CA 签发证书 & 定时发布 CRL (含 thisUpdate, nextUpdate, revokedCertificates) 客户端从 CDP 下载 CRL (HTTP GET 请求 CDP 中的 URI) CRL 有效性验证 验证失败 在 CRL 中匹配证书序列号 (userCertificate 字段比对) 未找到 → 证书可信 已找到 → 证书被吊销 缓存 CRL 并检查 nextUpdate 超时则重新下载 验证失败时的处理: 无法获取 CRL → 可配软失败或硬失败 签名无效 / 过期 → 硬失败

上面这张图差不多把整个流程说清楚了。从 CA 发布 CRL 开始,到客户端下载、检查有效性、比对序列号、最后决定是否信任证书。我挑几个验证的关键步骤再细说一下。

3.3.1 检查 CRL 颁发者

客户端拿到 CRL 后,第一件事就是看签名。CRL 必须由签发待验证证书的同一 CA 签名。怎么验证?用 CA 证书里的公钥去验 CRL 的 signatureValue。

你可能会问:万一有人伪造 CRL 怎么办?嗯,这里的关键就是数字签名。只要 CA 私钥没泄露,没人能伪造有效的 CRL。我碰到过一起事件,某内部 CA 的私钥文件权限配成了 777,结果第三方篡改了 CRL 内容,还好我们做了签名校验才及时发现。

3.3.2 检查 thisUpdate 和 nextUpdate

这就是 CRL 的有效期检查。简单说,当前时间必须在 thisUpdate 到 nextUpdate 之间。否则客户端会认为 CRL 已失效。

  • 当前时间在 thisUpdate 之前 → CRL 还没生效,不能使用
  • 当前时间在 nextUpdate 之后 → CRL 已过期,必须立即刷新

我建议在客户端里留个缓冲时间,比如 nextUpdate 前 1 小时就开始预下载新 CRL。有些保守系统会配成 10 分钟,我见过因为缓冲太短导致高并发请求压垮 CA 服务器的,教训很惨。

3.3.3 匹配被吊销序列号

最后一步就是把待验证证书的序列号和 CRL 里所有 userCertificate 比对。这里有个常见的性能陷阱:序列号只是整数,但 CRL 条目可能成千上万条。如果你用线性查找,每次握手都查一遍,CPU 很快就扛不住了。

优化建议: 在服务端把 CRL 的序列号列表加载到内存里,用哈希表或者有序数组配合二分查找。我做过的性能压测里,用索引查找比暴力遍历快了 20 倍以上。

3.3.4 处理 Delta CRL

有些大型 CA 会同时发布完整 CRL(Full CRL)和增量 CRL(Delta CRL)。Delta CRL 只包含两次发布之间新增的吊销条目。使用 Delta CRL 的流程会稍微复杂一点:客户端需要把上次缓存的基础 CRL 和最新的 Delta CRL 合并起来,得到一个完整的视图。

我接触过的一套 PKI 系统,Delta CRL 每 4 小时发一次,Full CRL 每 24 小时发一次。设计得好能大幅降低带宽消耗。但前提是客户端必须正确拼接,否则会漏掉某些吊销条目,造成安全隐患。

3.4 CRL 验证中的错误处理

实际生产环境里,CRL 验证失败的情况很常见。我个人习惯把这些情况列出来,提前做好应对:

失败场景 推荐处理策略 解释
无法下载 CRL(网络问题) 软失败(允许证书继续使用) 完全阻断会造成大面积服务不可用,折中方案是允许使用但记录告警
CRL 签名无效 硬失败(拒绝所有证书) 签名无效说明 CRL 被篡改或来自伪 CA,不能信任
CRL 已过期 硬失败 假设 CA 已无法发布新 CRL,视为 CA 失去维护能力
CDP 地址无效(404) 尝试下一个 CDP 或软失败 多配几个 CDP 地址就是为了应对这种情况
CRL 太大导致内存溢出 设置最大 CRL 大小限制 我见过恶意 CA 发布几十 MB 的 CRL 来打垮客户端
关键提示: 硬失败和软失败的选择要根据业务场景来。金融、政务类应用我强烈建议硬失败;普通互联网服务可以考虑软失败加告警。但你必须在设计阶段就明确这个决策,别上线后再改。

好了,以上就是 CRL 的完整详解。从格式、CDP 配置到验证流程,每一步都有细节要注意。我在实际项目里踩过的坑不止上面这些,但核心思路就是:CRL 是防吊销的第一道防线,做好它,你的 PKI 体系就稳了一大半。