OCSP基本安全机制:签名、Nonce、时间戳

说实话,OCSP协议本身设计得很简洁。但简洁带来的问题就是——容易被人钻空子。尤其是重放攻击,你明明已经验证通过了一次,攻击者把旧的响应截下来,过段时间再扔给你,你如果没做防护,那就有可能再次放行。

怎么防?靠的就是三个核心机制:签名、Nonce 和 时间戳。这三者可以独立使用,也可以组合使用。我个人习惯是三者全上,因为每个机制防的攻击面不一样。

签名机制

签名是OCSP响应的第一层保护。它保证了两件事:响应确实来自授权的OCSP响应器,并且内容没有被篡改过。

你想想看,客户端收到一个响应,首先得确认这是不是真的来自那个响应器,对吧?签名就是干这个的。响应器用它的私钥对整个响应内容签名,客户端再用响应器证书里的公钥去验签。一旦验签失败,直接丢弃。

实际经验: 我在项目中遇到过一种情况——攻击者把证书链替换了,然后用自己的私钥重新签名。所以不光要验签,还得验证签名证书是不是来自可信的CA链。这一点经常被忽略。

签名流程简述

  1. OCSP响应器构造响应内容。
  2. 使用私钥对响应内容的摘要进行签名。
  3. 将签名算法标识、签名值、签名证书链附加到响应中。
  4. 客户端收到后,提取签名证书,验证其可信性。
  5. 使用证书公钥验签,确认响应完整性。
// 伪代码示例:OCSP签名验证
function verifyOCSPSignature(response, trustedRoots):
    // 1. 提取签名证书链
    certChain = response.certificates
    // 2. 验证证书链合法性
    if !verifyCertChain(certChain, trustedRoots):
        return false
    // 3. 验签
    signerCert = certChain[0]
    expectedHash = hash(response.tbsData)
    return verifySignature(signerCert.publicKey, response.signature, expectedHash)
重点: 如果响应没有签名,那就跟裸奔一样。RFC 6960要求所有非错误响应都必须签名,我建议你在客户端把未签名的响应直接视为非法。

Nonce机制

签名防住了篡改和伪造,但防不住重放——攻击者拿一个旧的合法响应来骗你,签名是有效的,怎么办?
这就轮到Nonce出场了。

Nonce说白了就是一个一次性随机数。客户端在请求里带上一个随机值,响应器把这个值原样塞回响应里。客户端收到响应后,检查Nonce是否跟自己发的那个一致。不一致?说明这个响应不是你刚才问的那个,有可能是重放的。

我曾经接手过一个旧系统,记得当时没有做Nonce检查,线上出了一个问题:某个证书的状态从“撤销”变成了“正常”,但客户端一直不更新。查了半天才发现,是代理层缓存了一个旧响应,客户端每次都拿那个旧响应做验证,结果永远认为证书是正常的。

注意: Nonce是防重放最直接的手段,但不是所有OCSP响应器都支持。如果不支持,你就得靠时间戳来兜底。

Nonce的典型交互流程

步骤 客户端 响应器
1 生成随机Nonce —
2 将Nonce放入请求扩展 —
3 发送请求 接收请求,提取Nonce
4 — 将Nonce原样放入响应扩展
5 验证响应中的Nonce是否匹配 —
// Nonce验证代码示例
byte[] sentNonce = generateRandomNonce();
request.setNonce(sentNonce);
// ... 发送请求,获取响应 ...
byte[] receivedNonce = response.getNonce();
if (!Arrays.equals(sentNonce, receivedNonce)) {
    throw new SecurityException("Nonce不匹配,疑似重放攻击");
}

时间戳机制

如果响应器不支持Nonce怎么办?那就只能用时间戳来做粗略防护了。

OCSP响应里有两个时间字段:thisUpdate 和 nextUpdate。thisUpdate 表示响应的生成时间,nextUpdate 表示预期的下一个更新时间。客户端收到响应后,可以检查当前时间是否在 thisUpdate 和 nextUpdate 之间。如果当前时间比 nextUpdate 还晚,说明这个响应过期了,不能再用。

这个机制的问题在于:时间窗口可能很长。比如响应器设置 nextUpdate 为4小时后,那攻击者在这4小时内都能重放这个响应。

关键差异对比:
机制 防护类型 依赖条件 弱点
签名 数据完整性、源认证 可信的CA链、公钥基础设施 不防重放
Nonce 重放攻击 客户端生成随机数、响应器支持 部分响应器不支持
时间戳 过时响应重放 客户端时间同步 时间窗口内仍可重放

嗯,这里要注意——时间戳只能做辅助。我建议客户端同时获取NTP时间,不要完全依赖系统时间,否则你可能会被攻击者通过篡改系统时间来绕过检查。

三者结合使用效果最好。签名保真,Nonce防重,时间戳兜底。你想想看,即便Nonce匹配、签名有效,但如果响应已经过期了,那也不能用。

三种机制的关系总结

OCSP 响应安全机制关系图 签名机制 Nonce 机制 时间戳机制 防篡改 & 防伪 防重放(强) 防过度延迟 验证 CA 链 + 签名 请求/响应 随机数比对 检查 thisUpdate / nextUpdate 安全响应判定入口(三者缺一不可) 丢弃响应 / 告警 / 降级处理 推荐三机制全开

看到这张图了吗?签名、Nonce、时间戳是三道防线。签名最先做,因为它保的是基础和信任链;然后是Nonce检查,确认响应是新鲜的;最后用时间戳兜底,防止漏网之鱼。任何一个环节出问题,我都建议直接丢弃响应,别给攻击者留机会。

避坑指南: 我曾经在测试环境发现一个奇怪的现象——部分老旧OCSP响应器返回的响应里没有Nonce扩展,但签名和时间戳都在。这种情况下,你不能直接丢弃,因为有些CA还在用老设备。我的做法是:优先用Nonce,如果不支持就降级为严格的时间戳检查,加大小于1小时的过期容忍度。

好了,这一章的核心就是这三把锁。下一轮我们聊聊怎么在客户端把这些检查串起来,形成一个完整的验证流程。