CRL 基础:结构、字段解析与实操要点
CRL,全称证书撤销列表。说白了,它就是一张黑名单。
CA 签发证书,但如果私钥泄露、域名变更或员工离职,证书就需要提前作废。CA 没法把证书收回来,只能定期发个公告:“这些证书现在无效了。” 这个公告就是 CRL。
我刚开始接触 PKI 时,总觉得 CRL 就是个静态文件,后来被生产环境狠狠教育了一次——一次 CRL 过期导致 HTTPS 握手失败,把我忙了一整晚。嗯,从那以后,我对 CRL 里的每个字段再也不敢轻视了。
CRL 的整体结构
CRL 的语法由 X.509 v2 标准定义,使用 ASN.1 描述,编码通常是 DER 或 PEM。你可以把它想象成一个带签名的数据包,里面包含三大部分:
- tbsCertList(待签名数据):真正的内容区,包含版本、发行者、时间、撤销列表等。
- signatureAlgorithm:签名算法标识,和 tbsCertList 末尾的签名算法一致,用于校验。
- signatureValue:CA 对 tbsCertList 的数字签名,确保 CRL 不可篡改。
我个人习惯把 tbsCertList 比喻成“信封里的信纸”,签名就是封口的火漆。信纸内容错了,或者火漆损坏了,这封信就不可信了。
关键字段逐项解析
我把 tbsCertList 里最重要的几个字段拆开讲。每一个字段都在生产环境里给我留下过深刻印象。
| 字段名 | ASN.1 类型 | 说明 |
|---|---|---|
| version | INTEGER (0/1) | CRL 版本。0 表示 v1,1 表示 v2 (带扩展)。现在基本都用 v2。 |
| signature | AlgorithmIdentifier | CA 签名的算法 OID,比如 sha256WithRSAEncryption。 |
| issuer | Name | 签发这份 CRL 的 CA 的 DN(Distinguished Name)。 |
| thisUpdate | Time | CRL 的发布时间(UTC 或 GeneralizedTime)。 |
| nextUpdate | Time | 下一份 CRL 发布的时间。客户端必须在这个时间之前拿到新 CRL。 |
| revokedCertificates | SEQUENCE of SEQUENCE | 撤销证书的列表,每一项包含序列号、撤销时间、可选的扩展。 |
| crlExtensions | SEQUENCE | v2 特有的扩展区域,如 CRL 编号、Delta CRL 指示器等。 |
这里我重点说两个坑,你们注意一下。
- nextUpdate 过期:我曾经遇到过第三方 CA 的 CRL 下载服务器挂了,客户端读到 nextUpdate 之后没有新 CRL,直接拒绝所有证书——因为 RFC 5280 要求“如果无法获取到有效的下一份 CRL,应视为关键性错误”。这个行为让整个系统僵死了。后来我们在中间件层加了一个“宽限期”逻辑,才解决了问题。
- revokedCertificates 里的序列号:序列号是证书的唯一标识,但 CA 生成时可能用随机数,也可能用顺序号。解析时记得用大数比较,不要转 INT 后溢出。我见过一个系统用 int32 存序列号,结果 32 位溢出了,永远匹配不上那条撤销记录。
openssl crl -inform DER -in my.crl -text -noout你会看到所有字段的详细内容。建议拿一个真实的 CA CRL 跑一下,直观感受。
CRL 的扩展区域
v2 的 CRL 有扩展字段,最常见的是:
- Authority Key Identifier:明确标识签发 CRL 的 CA 公钥,防止混淆。
- CRL Number:单调递增的序列号,用于判断 CRL 的新旧。客户端可以用这个检测是否漏了某一份。
- Delta CRL Indicator:如果存在,说明这是一份“增量 CRL”,只包含上次全量发布后新增的撤销证书。
- Issuing Distribution Point:用来指定 CRL 分发的范围(如按地区、按算法),有助于缩小客户端每次查找的列表大小。
我建议:如果生产环境证书量超过 10 万,必须引入 Delta CRL 或分段 CRL。不然全量 CRL 文件可能大到十几 MB,每次下载都影响握手延迟。
CRL 的类型与部署策略
实际运营中,CRL 不是只有一种发布方式。你想想看,一个 CA 可能签发了几十万张证书,如果每天只出一份巨庞大的 CRL,客户端下载会很慢。所以业界有几种变体:
- 全量 CRL(Full CRL):包含当前所有被撤销的证书。文件最大,但客户端一次就能获取完整信息。
- 分段 CRL(Partitioned CRL):按条件把全量 CRL 切开。每个分段只包含某一部分证书的撤销记录。客户端根据自己持有的证书特征,只下载对应的分段。
- 增量 CRL(Delta CRL):只包含自上一个全量 CRL 发布以来新增的撤销记录。客户端先拿到全量,然后定期下载增量更新。文件很小。
- 间接 CRL(Indirect CRL):由第三方(非原 CA)签发,用于跨 CA 的撤销信息汇聚。场景比较特殊,通常用在桥接 PKI 里。
我记得有一个项目,刚开始用全量 CRL,每 24 小时发布一次,文件 8MB。客户端每次 TLS 握手都要检查 CRL,导致连接建立时间从 200ms 飙升到 2s 以上。后来切换到 Delta CRL,全量 8MB 每周发一次,增量每天才几十 KB,延迟降到了正常水平。
SVG:CRL 结构解剖图
下面这张图帮你把 CRL 的骨架理清楚。从顶层到底层,箭头指向表示包含关系。
openssl crl -inform PEM -in crl.pem -text -noout | less你会看到图中的字段一一对应显示。
解析 CRL 要注意的编码细节
CRL 在生产环境中通常以 DER 二进制或 PEM BASE64 形式传递。我个人推荐统一用 DER 存储,因为 PEM 的头部信息有时被错误截断。解析步骤可以概括为:
- 判断编码格式——如果以 "-----BEGIN X509 CRL-----" 开头,就是 PEM;否则是 DER。
- 解码——PEM 需要去掉头和尾,把中间 BASE64 解码成 DER。
- 验签——根据 signatureAlgorithm 找到对应的 CA 公钥,验证 signatureValue。
- 读 revokedCertificates 列表——注意有些 CA 可能把空列表用 SEQUENCE {} 表示,而不是省略字段。
- 检查 nextUpdate——如果当前时间超过 nextUpdate,视为 CRL 失效,必须立即重新下载。
我曾经遇到过一个“幽灵 CRL”——文件本身是有效的,但 nextUpdate 写成了 Unix 时间戳而非 UTC 时间格式,导致解析库抛异常。后来查了 RFC 5280,发现 Time 类型可以是 UTCTime 或 GeneralizedTime,但绝不能用数字时间戳。CA 那边改了一次配置才恢复正常。
好了,CRL 的结构和字段就讲到这。你想象它就是一个带签名的表格,表格里有“谁被撤销了、什么时候撤销的、下次什么时候更新”。理解了这些,后面讲 CRL 分发点和集成改造时,你就能快速理解为什么某些方案会出现性能瓶颈。