3. OCSP Stapling 工作原理
说实话,OCSP Stapling 这个名字刚接触时容易觉得玄乎。它到底怎么塞进 TLS 握手的?响应又是怎么来的?我们得一层层拆开看。
我个人习惯把 OCSP Stapling 理解为“服务器主动替客户端跑腿”。本来客户端需要自己去问 CA 这张证书有没有被吊销,现在服务器提前把 CA 的答复装进握手消息里。客户端省了一次网络请求,隐私也更好——CA 不知道你访问了哪个站点。
3.1 TLS 握手扩展:status_request
一切要从 TLS 扩展说起。RFC 6066 定义了 status_request 扩展,它允许客户端在 ClientHello 里告诉服务器:“嘿,如果你有证书状态信息,顺手带给我,别让我再跑一趟。”
扩展结构很简单。客户端发个 certificate_status_type(目前只有 ocsp 一种),再带上 OCSP 响应的期望长度(optional)。服务器如果支持,就在 ServerHello 里回复同样的扩展 ID。注意,这里只是握手阶段的一个开关——真正的 OCSP 响应是在服务端发送 Certificate 消息之后,紧跟着一个 CertificateStatus 消息送出去的。
我在项目中遇到过一次奇怪的问题:客户端发送了 status_request,但服务端没响应。抓包一看,Nginx 配置里忘了开 ssl_stapling。你以为配置了证书就自动生效?嗯,不跟服务器说清楚,它不会自动去拿 OCSP 响应的。
| TLS 消息 | 扩展字段 | 作用 |
|---|---|---|
| ClientHello | status_request (5) | 声明客户端想接收 stapled OCSP 响应 |
| ServerHello | status_request (5) | 服务器确认支持 stapling |
| CertificateStatus | — | 实际携带 DER 编码的 OCSP Response |
-status 参数就能看到服务端是否返回了 stapled 响应。我习惯用 openssl s_client -connect example.com:443 -status -tlsextdebug 快速检查。
3.2 Stapled Response 的获取
服务器从哪里拿到这个 OCSP 响应?当然不是凭空生成的。它需要定期去 CA 的 OCSP 响应器(Responder)查询自己证书的状态。
获取方式大体分两种:
- 在线获取(Live Fetching):每次 TLS 握手前,服务器向 OCSP 响应器发起查询。这种方案实时性强,但延迟高、对 OCSP 响应器压力大,实际很少用。
- 缓存获取(Cached Stapling):服务器启动时或定时任务去拉一次 OCSP 响应,然后缓存起来,后续握手直接用缓存。这是主流做法。
你可以自己试一下手动获取 OCSP 响应:
# 假设已有 CA 证书、服务器证书
openssl ocsp -issuer ca.crt -cert server.crt \
-url http://ocsp.example.com \
-respout stapled.der
拿到 stapled.der 后,Nginx 或 Apache 就可以喂给客户端。我记得帮一个客户排查性能问题,发现他每 30 秒就去 OCSP 响应器拉一次,把人家 CA 的服务器都拉挂了。后来我建议他把缓存时间调到 4 小时,配合 nextUpdate 字段判断是否过期,问题就解决了。
ssl_stapling_responder 地址(Nginx)或 SSLStaplingResponder(Apache)。
3.3 缓存策略与有效性检查
缓存是 OCSP Stapling 的命根子。你想想看,服务器每来一个客户端就去做一次 OCSP 查询,那还不如不做。但缓存太久又可能客户端拿到已过期的吊销信息。
OCSP 响应自身带有时间约束:
- thisUpdate:响应生成时间
- nextUpdate:建议缓存截止时间
- producedAt:签名时间
服务器通常以 nextUpdate 为准,缓存到那个时间点前几分钟。我见过一个线上事故:某 CA 的 OCSP 响应器突然挂了,但服务器缓存里还保留着 7 天前的有效响应,客户端拿到的还是“未吊销”状态。实际上证书在 3 天前就被吊销了。这就是缓存太长的风险。
所以成熟的方案会做双重检查:
- 用
nextUpdate判断缓存是否仍在有效期内。 - 如果缓存过期,异步发起新的 OCSP 请求。此时旧的缓存可以继续服务一段时间(比如 10 分钟),避免握手失败。
我曾经在配置 OpenResty 时踩过坑:用了默认的 ssl_stapling_cache,但没注意到 Nginx 的共享内存大小只有 1MB,导致高并发时缓存被频繁淘汰,服务器几乎每次握手都回去拉 OCSP。后来我把 ssl_stapling_cache 加到 10MB,并启用 ssl_stapling_responder_timeout 设置 5 秒,世界就清净了。
nextUpdate 的 80% 时间作为缓存自动刷新点,剩下 20% 作为容错窗。
你看,这张图把三个实体和两条线串起来:服务器先主动找 CA 拿 OCSP 响应,缓存起来;客户端到来时,服务器在握手的过程中把缓存内容塞给客户端。整个流程客户端不需要额外联网,隐私也保住了。
我个人始终认为 OCSP Stapling 是成本最低、收益最高的 TLS 安全增强手段之一。不开启它,相当于把证书吊销验证的负担完全抛给了客户端——很多客户端甚至默认不验证。而开启后,也只需要维护好缓存刷新策略和内存大小。
最后提醒一句:千万别忘了在 Nginx 里配置 ssl_trusted_certificate,否则服务器无法验证 OCSP 响应的签名,会直接拒绝缓存。我曾经在这上面折腾了一下午,结果发现只是少了根链。