OCSP 装订让 TLS 服务端在握手过程中发送签名的证书状态响应。对于原本需要在线查询 OCSP 的客户端,它可能省去一次单独请求。收益取决于客户端的吊销策略:并非每个浏览器每次连接都查询 OCSP,也不是每个证书颁发机构仍然提供这种服务。装订只是传递状态证据的一种方式,不能代替完整证书验证,更不能把“握手收到了一段数据”当作信任已经成立。
传递证据并不转移信任责任
服务器从颁发机构或获得授权的应答方取回响应,并为适用握手缓存它。客户端仍须检查响应签名、签名者权限、证书标识、状态和可接受时间范围。服务器把响应附在握手里,不会让它自动可信。OCSP 的 good 状态也不能证明主机名匹配、证书仍在有效期内,或整条证书链已经连接到受信任根。这些验证职责必须分别保留。
线上表示还取决于 TLS 版本。TLS 1.2 使用状态请求扩展与 CertificateStatus 握手消息;TLS 1.3 则把响应放在对应 Certificate 条目的扩展中。把两者统称为 certificate_status 扩展,会混淆实际协议结构。普通网络攻击者不能随意剥离受认证握手消息,同时仍让 Finished 校验成功。讨论吊销检查被绕过时,应明确攻击能力和客户端策略,而不是虚构一种可以无痕删除握手数据的能力。
先核对支持范围,再决定启用
运维者首先需要确认颁发方提供可用响应,并检查服务端及客户端是否支持预期策略。Let’s Encrypt 已在二〇二五年八月六日结束 OCSP 服务,改用证书吊销列表。因此,对于没有 OCSP 支持的证书,缺少装订不自动意味着刷新任务损坏。应查当前颁发方文档,而不是把“所有站点都应该打开装订”当成不需要前提的清单步骤。
对于仍提供 OCSP 的颁发方,应在缓存过期之前留出刷新余量,并覆盖证书轮换。需要检查响应对应哪张证书、新鲜度及边缘实际交付内容,而不只是缓存是否存在。不同服务进程可能持有不同结果;控制面抓取成功也不证明全部已更新。缓存中的 good 响应仍可能在可接受窗口内,而较新的吊销信息已经出现,因此装订不能提供瞬时吊销知识。
坏掉的样子
缺失、过期、无效和已吊销是不同状态。客户端可能拒绝连接、查询其他机制,或应用明确的软失败策略。缺少响应不保证触发直连颁发机构,已经提供但无效的响应也不保证仅仅增加延迟。描述时应说明具体校验器的行为,不要承诺所有握手都会继续,更不能把“没有报错”直接解释为吊销状态已经得到确认。
TLS Feature 扩展通常被称为 Must-Staple;支持并执行它的客户端,可以要求服务器提供状态证据,但它不能强迫不支持的客户端执行同样检查。依赖这个要求之前,服务端需要处理冷启动、续期及应答服务中断。服务中断并不会立刻使仍在可接受窗口内的缓存失效,但剩余新鲜度窗口会限制它还能保护多久。是否失败必须结合实际缓存与客户端规则判断。
时钟异常也会改变接受结果。thisUpdate 和 nextUpdate 描述状态时间,producedAt 描述响应生成时间;真实校验器还会采用自己的最大年龄和时钟容差规则,有些协议响应也可以不带 nextUpdate。这些实现差异应明确写出。程序里一个名叫“签名正确”的布尔值,只是输入假设,不能作为真实密码学验签的证据。
快速验证
保存为 stapling_policy.py,使用 Python 3.9 或更新版本执行 python3 stapling_policy.py。独立模型使用虚构的验证后输入,明确要求两个时间边界,并对已经提供但无效的证据采取严格拒绝策略。它不访问网络,不解析证书,不实现签名算法,也不运行 TLS 服务或浏览器,因此不能用于证明任何真实证书可信。
"""Post-verification policy model. Booleans are fixtures, not cryptography."""
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
@dataclass(frozen=True)
class Response:
cert_id: str
status: str
signature_ok: bool
signer_authorized: bool
this_update: datetime
next_update: datetime
def decide(response, now, cert_id, require_staple):
if response is None:
return "reject-missing" if require_staple else "consult-other-policy"
if response.cert_id != cert_id:
return "reject-wrong-certificate"
if not (response.signature_ok and response.signer_authorized):
return "reject-untrusted"
if not response.this_update <= now < response.next_update:
return "reject-time"
if response.status == "revoked":
return "reject-revoked"
if response.status != "good":
return "reject-unknown"
return "status-good-only"
now = datetime(2026, 10, 15, 12, tzinfo=timezone.utc)
good = Response("synthetic-cert", "good", True, True,
now - timedelta(hours=1), now + timedelta(hours=1))
cases = [
(good, False, "status-good-only"),
(None, False, "consult-other-policy"),
(None, True, "reject-missing"),
(replace(good, status="revoked"), False, "reject-revoked"),
(replace(good, status="unknown"), False, "reject-unknown"),
(replace(good, signature_ok=False), False, "reject-untrusted"),
(replace(good, signer_authorized=False), False, "reject-untrusted"),
(replace(good, cert_id="another-cert"), False, "reject-wrong-certificate"),
(replace(good, next_update=now), False, "reject-time"),
(replace(good, this_update=now + timedelta(minutes=1)), False, "reject-time"),
]
for response, required, expected in cases:
actual = decide(response, now, "synthetic-cert", required)
assert actual == expected, (actual, expected)
print(actual)
assert decide(good, now + timedelta(hours=2), "synthetic-cert", False) == "reject-time"
print("PASS: 11 explicit policy cases; no certificate or TLS validation performed")
十一项检查区分可接受状态、缺失、已吊销、未知状态、错误证书、未授权签名者、错误签名、过期和未来日期。最后把观察时间向后移动,验证这个模型的严格时间区间。可选装订缺失时返回 consult-other-policy,它不是接受结论,也不表示已经向外查询过任何服务。这一分支有意保留后续策略决定。
成功结果特意命名为 status-good-only,提醒读者其余证书验证仍在函数之外。保留的 Python 3.12.14 记录报告这些精确示例通过运行;本次源码审阅没有重跑。必须启用断言,不使用 -O 或 PYTHONOPTIMIZE。真正线上验证仍须核对颁发方、签名响应、TLS 交付、客户端策略、证书轮换和刷新失败,不能从模型分支推导整个部署已经可靠。
来源
- RFC 6960,响应授权、证书绑定、状态和时间语义,已核验 2026-10-15。
- RFC 6066,TLS 1.2 状态请求与证书状态消息,已核验 2026-10-15。
- RFC 8446,TLS 1.3 证书条目状态扩展和握手认证,已核验 2026-10-15。
- RFC 7633,TLS Feature 要求与客户端支持,已核验 2026-10-15。
- Let’s Encrypt OCSP retirement,颁发方在二〇二五年八月六日结束 OCSP 服务,已核验 2026-10-15。
- Python assert 语句,优化模式会移除断言及其表达式,已核验 2026-10-18。