简短回答:自2024年3月起,CA/Browser Forum 要求 CA 发布 CRL,并把 OCSP 改为可选。Let's Encrypt 于2025年5月从其证书中移除了 OCSP URL,并于2025年8月关闭了 OCSP 响应器。浏览器不再就每张证书向 CA 查询。Firefox 检查本地的 CRLite 过滤器,Chrome 检查自己推送的 CRLSet,Apple 则使用自己汇总的列表。大多数库和命令行工具(Java、.NET、Go、Python、Node.js、curl)默认根本不检查吊销状态。在我们的测试中,它们全都接受了一张已吊销的 Let's Encrypt 证书。如果你的客户端必须拒绝已吊销的证书,就要显式启用 CRL 检查,并为较短的证书有效期做好规划。后者本来就是整个行业的发展方向。
为什么吊销机制需要重新设计
证书可以在过期之前被吊销,通常是因为密钥泄露或证书被误签发。通知客户端的经典方式有两种:
- CRL(证书吊销列表,RFC 5280):CA 发布一份经过签名的列表,列出已吊销证书的序列号。证书在其 CRL 分发点(CRL Distribution Points)扩展中指向这份列表。
- OCSP(RFC 6960):客户端就某一张证书向 CA 的响应器查询,得到经过签名的状态:“good”(正常)、“revoked”(已吊销)或“unknown”(未知)。响应器的 URL 位于颁发机构信息访问(Authority Information Access)扩展中。
这两种方式在 Web 上都有问题。CRL 可能很大。OCSP 会把用户访问的每个网站都泄露给 CA,还会增加延迟;而且响应器无法访问时,浏览器只能“软失败”(soft-fail)。能够拦截 OCSP 请求的攻击者可以让这项检查形同虚设。OCSP Stapling 由服务器获取响应并附在握手中发送,解决了隐私问题,但始终没有普及。Must-Staple 是要求必须提供 OCSP Stapling 响应的证书标志,浏览器对它的支持很少。
现行规则的规定
SC-063 提案(“OCSP 改为可选、强制要求 CRL、鼓励自动化”,2024年3月15日生效)修改了《基线要求》:
- CRL 成为强制要求。CA 必须在签发第一张证书后的 24 小时内发布完整的或分片的 CRL。如果其所有证书都带有 OCSP URL,CA 必须至少每 7 天更新一次 CRL,否则至少每 4 天更新一次;吊销证书后,还必须在 24 小时内更新(§4.9.7)。对于订户证书,
nextUpdate最多只能比thisUpdate晚 10 天(§7.2)。 - OCSP 改为可选。如果证书带有 OCSP URL,响应器就必须作答,且响应的有效期必须在 8 小时到 10 天之间(§4.9.9)。没有 OCSP URL 的证书必须包含 CRL 分发点(§7.1.2.11.2)。
- 短期证书不需要吊销信息。2026年3月15日起签发的“短期订户证书”(Short-lived Subscriber Certificate)有效期最多 7 天(此前为 10 天)。CA 可以支持吊销这类证书,CRL 分发点也是可选的。
- 吊销时限。密钥泄露及类似情况为 24 小时,其他情况为 5 天(§4.9.1.1)。
随后,Let's Encrypt 完全放弃了 OCSP,“主要是因为它对互联网上的隐私构成了相当大的风险”:
- 2025年1月30日:新的 Must-Staple 证书申请会失败。
- 2025年5月7日:新证书不再包含 OCSP URL,改为包含 CRL URL。
- 2025年8月6日:OCSP 响应器关闭。
如今证书中带有什么
2026年10月20日检查了几个网站是否有 OCSP URL、CRL URL 和 OCSP Stapling 响应,得到的结果参差不齐:
letsencrypt.org Let's Encrypt OCSP=- CRL=http://ye2.c.lencr.org/96.crl
staple: OCSP response: no response sent
www.google.com Google Trust Services OCSP=- CRL=http://c.pki.goog/we2/xuzt3PU9F_w.crl
staple: OCSP response: no response sent
github.com Sectigo Limited OCSP=http://ocsp.sectigo.com CRL=-
staple: OCSP response: no response sent
www.digicert.com DigiCert Inc OCSP=http://ocsp.digicert.com CRL=http://crl3.digicert.com/DigiCertEVRSACAG2.crl
staple: OCSP Response Status: successful (0x0)
www.microsoft.com Microsoft Corporation OCSP=http://oneocsp.microsoft.com/ocsp CRL=http://www.microsoft.com/pkiops/crl/partition/Microsoft%20TLS%20G2%20RSA%20CA%20OCSP%2004_Partition00093.crl
staple: OCSP Response Status: successful (0x0)
www.amazon.com Amazon OCSP=http://ocsp.r2m04.amazontrust.com CRL=http://crl.r2m04.amazontrust.com/r2m04.crl
staple: OCSP Response Status: successful (0x0)
Let's Encrypt 和 Google Trust Services 签发的证书只带 CRL。GitHub 那张来自 Sectigo 的证书情况正好相反:只有 OCSP,这也是《基线要求》允许的。只支持其中一种方式的客户端,无法检查上面所有证书。
CRL 并不小,但还在可控范围内:
http://yr1.c.lencr.org/46.crl 192226 bytes 5094 entries Oct 20 12:26:45 2026 GMT -> Oct 29 12:26:44 2026 GMT
http://c.pki.goog/we2/xuzt3PU9F_w.crl 87562 bytes 2456 entries Oct 20 11:12:29 2026 GMT -> Oct 30 10:12:28 2026 GMT
http://crl3.digicert.com/DigiCertEVRSACAG2.crl 635286 bytes 17091 entries Oct 20 06:24:08 2026 GMT -> Oct 27 06:24:08 2026 GMT
Let's Encrypt 对 CRL 做了分片:每张证书指向众多编号文件中的一个,CRL 的颁发分发点(Issuing Distribution Point)扩展则注明这是哪个分片。CRL 解码器可以读取其中任何一个文件,用颁发者证书验证它的签名,并告诉你某张证书是否在列表中。
浏览器如何检查吊销状态
主流浏览器都已不再为普通证书发送 OCSP 请求:
- Firefox:CRLite。Firefox 会下载一份压缩编码的数据,涵盖出现在 CT 日志中的所有已吊销证书,每 12 小时更新一次,并在本地检查。据 Mozilla 介绍,平均每天的数据量约为 300 kB。自 Firefox 137 起,CRLite 已对所有桌面版用户启用。Firefox 142 不再对域名验证证书使用 OCSP,Android 版 Firefox 则从 145 版开始采用 CRLite。
- Chrome:CRLSets。Chrome 推送一份根据 CA 的 CRL 构建的列表,但其中只包含“被认定为已吊销的证书中的一部分”,而且“Chrome 一般不执行在线检查(即 OCSP 和 CRL)”(Chromium)。在我们的测试中,Chromium 156 正常加载了
https://revoked-isrgrootx1.letsencrypt.org/,状态码为 HTTP 200。 - Apple:汇总列表。Apple 从各家 CA 收集吊销数据,再分发到设备上。2025年12月,Apple 开发者技术支持解释说,只有因密钥泄露等原因被吊销的证书,才“会进入 Apple 的‘黑名单’”;对于 Let's Encrypt 证书,也不会再做 OCSP 二次检查。因此,因普通原因被吊销的证书在 Safari 中可能仍能正常加载。
这在实践中意味着:对浏览器来说,吊销证书并不是能立即生效的紧急开关。Firefox 通过每 12 小时一次的 CRLite 更新获知吊销。对于因普通原因被吊销的证书,Chrome 和 Safari 可能永远不会拦截。密钥泄露后,真正起限制作用的是证书剩余的有效期,这也是转向 47 天证书的原因之一。
各种库和工具默认检查什么
2026年10月20日,下面的每个客户端都分别连接了 https://revoked-isrgrootx1.letsencrypt.org/(2026年10月7日被吊销)和 https://valid-isrgrootx1.letsencrypt.org/。被吊销的证书带有 CRL URL,没有 OCSP URL。
| 客户端 | 访问已吊销站点的默认结果 | 实测有效的吊销检查方式 |
|---|---|---|
| Java 25 (JSSE) | HTTP 200 | -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true |
.NET 10 HttpClient | HTTP 200 | SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online |
Go 1.27 crypto/tls | 200 OK | 在 tls.Config.VerifyConnection 中自行检查 |
Python 3.13 ssl | 握手成功 | VERIFY_CRL_CHECK_LEAF,并把 CRL 加载到上下文中 |
Node.js 24 tls | 握手成功 | crl 选项,数组中每个元素放一个 CRL,覆盖证书链的每一级 |
| curl 8.14 (OpenSSL) | HTTP 200 | --crlfile,包含证书链每一级的 CRL |
| Chromium 156 | HTTP 200 | 无(仅 CRLSet) |
下面逐一介绍各客户端的细节。
Java
除非把 com.sun.net.ssl.checkRevocation 设为 true,否则 JSSE 不检查吊销状态。即使设置了,只要没有同时启用 CRL 分发点,它就只使用 OCSP,所以 Let's Encrypt 证书的验证失败原因是状态未知,而不是已吊销:
--- 1. default
HTTP 200
--- 2. -Dcom.sun.net.ssl.checkRevocation=true
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path validation failed: java.security.cert.CertPathValidatorException: Could not determine revocation status
--- 3. checkRevocation + -Dcom.sun.security.enableCRLDP=true (revoked site)
javax.net.ssl.SSLHandshakeException: (certificate_revoked) PKIX path validation failed: java.security.cert.CertPathValidatorException: Certificate has been revoked, reason: UNSPECIFIED, revocation date: Wed Oct 07 18:37:19 UTC 2026, authority: CN=YR1, O=Let's Encrypt, C=US, extension OIDs: []
--- 4. same, valid site
HTTP 200
启用检查后,PKIX 验证器在无法确定状态时会直接判定失败(硬失败)。这样做是安全的,但 CRL 服务器一旦故障,你的服务也会随之中断。使用 java.security.cert.PKIXRevocationChecker 可以按连接选择 OCSP 与 CRL 的先后顺序以及软失败选项。
.NET
使用 SocketsHttpHandler 的 HttpClient 默认不检查吊销状态。把模式设为 Online 后,.NET 会自行获取 CRL 或 OCSP 响应。在 Linux 上,它通过自己基于 OpenSSL 的实现完成这一步:
var handler = new SocketsHttpHandler();
handler.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online;
using var client = new HttpClient(handler);
https://revoked-isrgrootx1.letsencrypt.org/ revocation=default -> 200
https://revoked-isrgrootx1.letsencrypt.org/ revocation=Online -> The remote certificate is invalid because of errors in the certificate chain: Revoked
https://valid-isrgrootx1.letsencrypt.org/ revocation=default -> 200
https://valid-isrgrootx1.letsencrypt.org/ revocation=Online -> 200
Go
crypto/tls 会验证证书链和名称,但从不检查吊销状态。如果需要,就在验证完成后自行检查 CRL。最难的部分可以交给 x509.ParseRevocationList 和 CheckSignatureFrom:
func checkCRL(cs tls.ConnectionState) error {
chain := cs.VerifiedChains[0]
leaf, issuer := chain[0], chain[1]
if len(leaf.CRLDistributionPoints) == 0 {
return errors.New("certificate has no CRL distribution point")
}
resp, err := http.Get(leaf.CRLDistributionPoints[0])
if err != nil {
return err
}
defer resp.Body.Close()
der, err := io.ReadAll(io.LimitReader(resp.Body, 64<<20))
if err != nil {
return err
}
crl, err := x509.ParseRevocationList(der)
if err != nil {
return err
}
if err := crl.CheckSignatureFrom(issuer); err != nil {
return fmt.Errorf("CRL signature: %w", err)
}
if time.Now().After(crl.NextUpdate) {
return errors.New("CRL is past its nextUpdate")
}
for _, entry := range crl.RevokedCertificateEntries {
if entry.SerialNumber.Cmp(leaf.SerialNumber) == 0 {
return fmt.Errorf("certificate %X revoked at %s", leaf.SerialNumber, entry.RevocationTime.UTC())
}
}
return nil
}
// client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{VerifyConnection: checkCRL}}}
https://revoked-isrgrootx1.letsencrypt.org/ -> 200 OK
https://revoked-isrgrootx1.letsencrypt.org/ -> Get "https://revoked-isrgrootx1.letsencrypt.org/": certificate 67E51010C975E496C8EB800F3EF1BAB3F2B revoked at 2026-10-07 18:37:19 +0000 UTC
https://valid-isrgrootx1.letsencrypt.org/ -> 200 OK
https://valid-isrgrootx1.letsencrypt.org/ -> 200 OK
这个示例只检查叶子证书,而且每次握手都会下载 CRL。在生产环境中,应把每个 CRL 缓存到它的 NextUpdate 时间,并决定 CRL 服务器宕机时如何处理。
Python、Node.js 和 curl
三者都使用 OpenSSL 的 CRL 检查,并要求你自己提供 CRL。它们从不自行下载 CRL:
python 3.13.5 default: handshake ok, TLSv1.3
python 3.13.5 VERIFY_CRL_CHECK_LEAF: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate revoked (_ssl.c:1029)
node v24.22.0 default: handshake ok
node v24.22.0 crl: UNABLE_TO_GET_CRL unable to get certificate CRL
Python 的 ssl.VERIFY_CRL_CHECK_LEAF 只检查叶子证书,因此用 ctx.load_verify_locations(cafile=...) 加载叶子证书的 CRL 就够了。Node.js 和 curl 会检查证书链中的每一张证书,所以只提供叶子证书的 CRL 时,即使访问有效站点也会报 UNABLE_TO_GET_CRL(Node)或 unable to get certificate CRL(curl)。要为证书链中的每张证书都提供对应的 CRL。对于 Node.js 24,应以数组形式传入,每个 CRL 一个 PEM 字符串。在我们的测试中,把三个 CRL 拼接成一个 buffer 传入时,仍然报 UNABLE_TO_GET_CRL:
revoked-isrgrootx1.letsencrypt.org (one buffer): UNABLE_TO_GET_CRL unable to get certificate CRL
revoked-isrgrootx1.letsencrypt.org (array): CERT_REVOKED certificate revoked
valid-isrgrootx1.letsencrypt.org (one buffer): UNABLE_TO_GET_CRL unable to get certificate CRL
valid-isrgrootx1.letsencrypt.org (array): handshake ok
curl 的两个开关表现不同。--cert-status 要求服务器提供 OCSP Stapling 响应,因此在所有不提供 OCSP Stapling 的服务器上都会报错,包括所有 Let's Encrypt 站点。--crlfile 配合包含整条证书链 CRL 的文件即可正常工作:
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" https://revoked-isrgrootx1.letsencrypt.org/
HTTP 200
$ curl -sS -o /dev/null --cert-status https://revoked-isrgrootx1.letsencrypt.org/
curl: (91) No OCSP response received
$ curl -sS -o /dev/null --crlfile revoked-chain-crls.pem https://revoked-isrgrootx1.letsencrypt.org/
curl: (60) SSL certificate problem: certificate revoked
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" --crlfile valid-chain-crls.pem https://valid-isrgrootx1.letsencrypt.org/
HTTP 200
$ curl -sS -o /dev/null --crlfile leaf-crl-only.pem https://valid-isrgrootx1.letsencrypt.org/
curl: (60) SSL certificate problem: unable to get certificate CRL
下面的脚本会收集服务器发送的每张证书对应的 CRL:
#!/bin/bash
host="$1"; out="$2"
dir=$(mktemp -d)
openssl s_client -connect "$host:443" -servername "$host" -showcerts </dev/null 2>/dev/null \
| awk -v d="$dir" '/BEGIN CERTIFICATE/{n++} n>0{print > (d "/cert" n ".pem")}'
: > "$out"
for cert in "$dir"/cert*.pem; do
url=$(openssl x509 -in "$cert" -noout -ext crlDistributionPoints 2>/dev/null | grep -o 'http[^ ]*' | head -1)
[ -n "$url" ] && curl -s "$url" | openssl crl -inform DER >> "$out"
done
rm -r "$dir"
echo "$host: $(grep -c 'BEGIN X509 CRL' "$out") CRLs"
手动检查证书
用 OpenSSL 可以查看证书的状态信息存放在哪里,并直接检查。先查看扩展:
$ openssl x509 -in leaf.pem -noout -ext crlDistributionPoints,authorityInfoAccess
Authority Information Access:
CA Issuers - URI:http://yr1.i.lencr.org/
X509v3 CRL Distribution Points:
Full Name:
URI:http://yr1.c.lencr.org/44.crl
然后下载 CRL,并用它验证证书。-untrusted 用于提供服务器发送的中间证书:
$ curl -s -o crl.der http://yr1.c.lencr.org/44.crl
$ openssl crl -inform DER -in crl.der -out crl.pem
$ openssl verify -crl_check -CRLfile crl.pem -untrusted chain.pem leaf.pem
CN=revoked-isrgrootx1.letsencrypt.org
error 23 at 0 depth lookup: certificate revoked
error leaf.pem: verification failed
$ openssl crl -in crl.pem -noout -text | grep -A1 "Serial Number: 067E51010C975E496C8EB800F3EF1BAB3F2B"
Serial Number: 067E51010C975E496C8EB800F3EF1BAB3F2B
Revocation Date: Oct 7 18:37:19 2026 GMT
对于仍在运行 OCSP 的 CA,可以直接查询响应器,或者查看 OCSP Stapling 响应:
$ openssl ocsp -issuer issuer.pem -cert leaf.pem -url http://ocsp.digicert.com -resp_text -noverify
OCSP Response Status: successful (0x0)
Cert Status: good
This Update: Oct 19 22:09:00 2026 GMT
Next Update: Oct 26 21:09:00 2026 GMT
leaf.pem: good
This Update: Oct 19 22:09:00 2026 GMT
Next Update: Oct 26 21:09:00 2026 GMT
$ openssl s_client -connect www.digicert.com:443 -servername www.digicert.com -status </dev/null
OCSP Response Status: successful (0x0)
Cert Status: good
-noverify 会跳过对响应签名的检查,快速查看时没问题,但不能据此做决定。OCSP 解码器可以解析请求和响应,验证响应器的签名及其授权,还可以替你向响应器发起查询。
你的服务该怎么做
- 先判断是否真的需要吊销检查。对于调用公共 API 的出站请求,浏览器的模式(短有效期、CT、CA 事件响应)通常就够了。对于高价值的连接,例如支付回调、组织之间的 mTLS,或者代码签名和文档签名,应显式检查吊销状态。
- 优先支持 CRL。公共 CA 必须发布 CRL,而且有些已不再运行 OCSP。只支持 OCSP 的客户端无法判断 Let's Encrypt 和 Google Trust Services 证书的吊销状态。
- 按 CRL 自身的周期缓存和刷新。每个 CRL 只下载一次,保留到
nextUpdate,并在后台刷新,这样 TLS 握手就永远不用等待下载。 - 有意识地选择 fail-open 或 fail-closed。采用 fail-closed(无法完成检查时拒绝连接)时,CA 的 CDN 故障会变成你的服务中断。采用 fail-open(无法完成检查时照常放行)时,对任何能拦截 CRL 下载的人来说,这项检查都变得可有可无。两种结果都要记录到日志中。
- 去掉对 Must-Staple 和 OCSP Stapling 的依赖。Let's Encrypt 会拒绝 Must-Staple 申请,而 OCSP Stapling 只对仍在运行 OCSP 的 CA 有用。
- 私有 PKI 由你自己设计。可以发布
nextUpdate较短的 CRL,也可以签发短期证书并省去吊销,就像《基线要求》现在对有效期不超过 7 天的证书所允许的那样。
参考资料
- CA/Browser Forum,《基线要求》 v2.3.1:§4.9.1.1(吊销时限)、§4.9.7(CRL 更新频率)、§4.9.9(OCSP)、§7.1.2.11.2(CRL 分发点)、§7.2(CRL 格式规范)、短期订户证书的定义。SC-063 提案。
- RFC 5280(CRL)和 RFC 6960(OCSP)。
- Let's Encrypt,2025年终止 OCSP 支持。
- Mozilla,CRLite:Firefox 中快速、私密、全面的证书吊销检查(2025年8月),以及 Android 版 Firefox 145 版本说明。
- Chromium,CRLSets。
- Apple 开发者论坛,第 808875 号帖子(2025年12月)。
测试于2026年10月20日运行,环境为 node:24.22.0-trixie(OpenSSL 3.5.7、curl 8.14.1、Node.js 24.22.0、Python 3.13.5)、eclipse-temurin:25.0.4.1_1-jdk-noble、golang:1.27.3-trixie、mcr.microsoft.com/dotnet/sdk:10.0.402-noble 以及 mcr.microsoft.com/playwright:v1.64.1-noble(Chromium 156)。