第3章:OCSP协议基础(请求 / 响应格式)

我们得先把协议本身搞清楚。
OCSP 说白了就是个在线状态查询。
客户端发个请求:“这个证书现在有效没?”
服务器回个响应:“有效”、“已吊销”,或者“我不知道”。

嗯,这就是 RFC 6960 定义的核心行为。
我刚开始读 RFC 时,老实说有点懵,因为它是用 ASN.1 写的,一堆抽象语法。
但是别怕,我们拆开来看,其实很简单。

核心事实:OCSP 请求和响应都使用 DER 编码的 ASN.1 结构,通过 HTTP 传输。请求体是 OCSPRequest,响应体是 OCSPResponse。

3.1 OCSP 请求结构

一个 OCSP 请求长什么样?
它由两部分组成:tbsRequest 和 optionalSignature。

我个人习惯把 tbsRequest 理解成“请求真正要问的内容”。
它里面最关键的是 requestList,一个列表,每个条目都对应一张要查询的证书。

项目经验:我遇到过有人把几十张证书塞进一个请求里。理论上可以,但很多 OCSP 响应器会限制列表长度,一般建议不超过 10 张。我们后面讲到性能调优时会再聊。

每个 Request 里包含一个 CertID,用来唯一标识一张证书。CertID 的 ASN.1 定义是这样的:

CertID ::= SEQUENCE {
    hashAlgorithm   AlgorithmIdentifier,
    issuerNameHash  OCTET STRING,  -- 颁发者名称的哈希
    issuerKeyHash   OCTET STRING,  -- 颁发者公钥的哈希
    serialNumber    CertificateSerialNumber
}

说白了,OCSP 响应器拿到这四个字段后,就能在本地找到对应的证书状态记录。

请求里还有个 singleRequestExtensions,可以用来传一些额外信息。比如你希望响应器包含特定扩展字段。我一般建议生产环境只传必要的扩展,能省则省。

避坑指南:我曾经遇到一个场景,请求里用了非标准扩展,结果响应器直接返回了 malformedRequest。扩展可以加,但千万别破坏合规性。

3.2 OCSP 响应结构

响应结构稍微复杂一点,但核心依然清晰。

一个 OCSPResponse 首先有个 responseStatus,它告诉我们请求是否被正确处理。只有状态是 successful 时,responseBytes 才有效。

状态码 含义 说明
0 successful 响应正常,可解析 responseBytes
1 malformedRequest 请求格式错误或无法解析
2 internalError 服务器内部异常
3 tryLater 服务器暂时无法处理
5 sigRequired 请求必须签名但未签名
6 unauthorized 请求未被授权

你想想看,状态码这么少意味着什么?
意味着如果响应失败,我们能迅速判断原因。我在项目里监控 OCSP 响应时,一直盯着 tryLater 和 internalError 这两个码,它们往往是性能问题的前兆。

当 responseStatus 为 0 时,我们进入 responseBytes。它包含 responseType 和 response。常见类型是 id-pkix-ocsp-basic,对应的结构是 BasicOCSPResponse。

BasicOCSPResponse ::= SEQUENCE {
    tbsResponseData      ResponseData,
    signatureAlgorithm   AlgorithmIdentifier,
    signature            BIT STRING,
    certs            [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL
}

嗯,这里要注意,ResponseData 里包含了核心信息:版本、响应器 ID、生成时间、以及一个 responses 列表。每个 SingleResponse 对应一张证书的状态。

实战经验:响应器 ID 可以是 byName(颁发者名称)或 byKey(颁发者公钥哈希)。我个人习惯用 byKey,因为它更省空间,响应体小,网络开销就低。

SingleResponse 结构:

SingleResponse ::= SEQUENCE {
    certID                       CertID,
    certStatus                   CertStatus,
    thisUpdate                   GeneralizedTime,
    nextUpdate           [0]     GeneralizedTime OPTIONAL,
    singleExtensions     [1]     EXPLICIT SEQUENCE OF Extension OPTIONAL
}

这里 certStatus 有三种可能:good、revoked、unknown。
thisUpdate 是本次更新的时间,nextUpdate 是下一次更新的建议时间。我建议客户端一定用好 nextUpdate,合理缓存能减少一大半查询量。

避坑指南:我曾经在项目中忽略 nextUpdate,每三秒发一次请求,结果被 CA 的 OCSP 响应器限流了。嗯,从那以后我老实了,尊重 nextUpdate 就是尊重自己。

3.3 响应签核与信任链

BasicOCSPResponse 还自带签名机制。
signature 字段是对 tbsResponseData 的签名,certs 字段可选,携带签名证书链。

为什么要签?
因为 OCSP 响应可能被篡改。签名保证了你拿到的状态是可信的。我见过一些测试环境直接关闭签名校验,但生产环境千万别这么做。

验证流程一般是:

  1. 从 certs 里提取签名证书。
  2. 验证证书链,确保它受信任。
  3. 用签名证书的公钥验证 signature。
  4. 检查 thisUpdate 是否在合理时间范围内。

这四步缺一不可。少做一步就有安全隐患。

OCSP 请求与响应核心结构 OCSP 协议请求 / 响应核心结构 OCSPRequest tbsRequest optionalSignature CertID (hash + serial) singleRequestExtensions OCSPResponse responseStatus responseBytes (BasicOCSPResponse) SingleResponses signature + certs 请求签名可选 生产环境常用 响应必须签名 证书链可选

上面这张图把请求和响应的核心层级串起来了。
你可以看到请求里 tbsRequest 指向具体的 CertID 和扩展,而响应里的 responseBytes 对应着 SingleResponses 和签名数据。
两边是对称的:一个问状态,一个给状态。

我个人觉得,理解这个结构后,OCSP 的调优就有方向了。比如你想减小响应体,就可以在 SingleResponses 数据量上下功夫,或精简签名证书链的长度。

小技巧:RFC 6960 也支持 ocsp-nonce 扩展,在请求里加一个随机数,响应里原样返回,防止重放攻击。但是会增加响应体大小。要不要用?看你的安全要求。

好了,OCSP 的请求和响应格式,就这样。
我经常把这一节画成思维导图贴在工位旁边。你记住一件事就够了:请求查状态,响应给状态,中间靠签名称保安全。