第3章 OCSP请求格式:理解OCSP请求的结构、扩展字段

排查OCSP异常,光看响应码是不够的。很多时候问题出在请求本身——客户端发出去的请求就不对,服务器自然没法正确回应。我翻过不少抓包文件,发现80%的异常都跟请求格式有关。说白了,你得先知道正常请求长什么样,才能看出哪里不对劲。

3.1 OCSP请求的ASN.1结构概览

OCSP请求用ASN.1编码,传输层一般是DER或BER。整个结构像俄罗斯套娃:最外层是OCSPRequest,里面装TBSRequest,再里面是RequestList。我习惯把TBS理解为“待签名部分”,请求的核心信息都在这里。

字段类型说明
tbsRequestTBSRequest请求体,包含版本、请求列表、扩展
optionalSignatureSignature可选,签名后的鉴别信息

TBSRequest内部又有这些家伙——

  • version:默认0(v1),大部分时候用不到。
  • requestList:核心数组,每个元素是一个SingleRequest。
  • requestExtensions:扩展字段,非常重要,后面单独说。
小提示:SDK升级后如果出现“Request malformed”这类错误,优先检查requestList是否为空。我遇到过某版本SDK在批量请求时漏填了序列号,结果服务器直接回了400。

3.2 SingleRequest与CertID

每个SingleRequest对应一张要查询状态的证书。它包含一个CertID,以及可选的扩展。CertID的生成逻辑是OCSP排查的重灾区。

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

嗯,这里要注意:issuerNameHash和issuerKeyHash的哈希算法必须与hashAlgorithm一致。我排查过一个线上事故,客户端SDK用了SHA-256计算哈希,但hashAlgorithm字段填的是SHA-1 OID,服务器解析出来匹配不到证书,全部返回unknown。

为什么会这样?因为OCSP响应者会拿着你提供的issuerNameHash和issuerKeyHash去自己的缓存里找颁发者信息。哈希不一致,等于给了个错误的身份证。

避坑指南:我曾经在升级某安全SDK时发现旧版本用的是SHA-1,新版本偷偷改成了SHA-256,但OID没跟着变。排查了两天,最后抓包对比才发现问题。如果你切换了加密库,一定要确认哈希算法和OID是成对更新的。

3.3 扩展字段(Extensions)

OCSP请求的扩展字段藏在requestExtensions里。常用的有这几个——

扩展OID名称用途
1.3.6.1.5.5.7.48.1.2nonce防重放攻击,请求带一个随机数,响应必须原样返回
1.3.6.1.5.5.7.48.1.3acceptableResponseTypes告诉服务器我期望哪种响应格式
1.3.6.1.5.5.7.48.1.6preferredSignatureAlgorithm指定响应签名的首选算法

Nonce扩展是最容易出问题的。如果客户端加了nonce,但服务器响应里没有原样返回,或者nonce值不匹配,客户端就会丢弃响应,表现成“连接中断”或“超时”。我记得有次SDK升级后,所有iOS设备都报OCSP失败,最后发现是客户端生成的nonce太长(超过255字节),服务器截断了,两边对不上。

acceptableResponseTypes这个扩展,说白了就是告诉服务器:“我只认basicOCSPResponse,别给我别的格式。”如果你的SDK只支持basic响应,但服务器默认返回了另一种,就会解析失败。我建议在扩展里显式指定,避免歧义。

排查要点:抓包后先看扩展字段是否存在,再看nonce的长度和值。如果nonce字段有但响应里没有,八成是服务端不支持或者配置遗漏了。SDK升级后这类兼容性问题特别常见。

3.4 实际请求抓包分析

下面是一个简化后的OCSP请求DER数据(十六进制),我标注了关键位置——

30 4B            -- OCSPRequest (SEQUENCE, 75 bytes)
  30 49          -- tbsRequest (SEQUENCE, 73 bytes)
    A0 03        -- [0] (version, 3 bytes)
      02 01 00   -- INTEGER 0 (v1)
    31 24        -- requestList (SET, 36 bytes)
      30 22      -- SingleRequest (SEQUENCE, 34 bytes)
        30 20    -- CertID (SEQUENCE, 32 bytes)
          30 0B  -- hashAlgorithm (SEQUENCE, 11 bytes)
            06 09 60 86 48 01 65 03 04 02 01 -- SHA-256 OID
          04 14  -- issuerNameHash (OCTET STRING, 20 bytes)
            AA BB CC DD ...                   -- 实际哈希值
          04 14  -- issuerKeyHash (OCTET STRING, 20 bytes)
            EE FF 00 11 ...                   -- 实际哈希值
          02 08  -- serialNumber (INTEGER, 8 bytes)
            12 34 56 78 9A BC DE F0
    A2 1C        -- [2] (requestExtensions, 28 bytes)
      30 1A      -- Extensions (SEQUENCE, 26 bytes)
        30 18    -- Extension (SEQUENCE, 24 bytes)
          06 0A  -- extnID (OID, 10 bytes)
            2B 06 01 05 05 07 30 01 02 -- nonce OID
          04 0A  -- extnValue (OCTET STRING, 10 bytes)
            01 02 03 04 05 06 07 08 09 0A

你看,从30 4B开始逐层解析。只要有一层长度字段算错,整包就废了。SDK升级后如果发现请求长度超出预期(比如之前是70字节,现在变成了200字节),多半是加上了非必要的扩展,或者哈希长度变了。

调试技巧:我习惯用openssl ocsp -reqout命令导出一份正常的请求文件,然后与新SDK发出的请求做二进制对比。差异一目了然。另外,在代码里加日志输出DER十六进制,比靠抓包省事得多。
OCSP 请求结构框架图 OCSPRequest TBSRequest optionalSignature version (0) requestList requestExtensions SingleRequest[] CertID (hash+issuer+serial) Extension (OID + value) Nonce (防重放) 排查重点:CertID 哈希算法一致性、Nonce 长度和服务端回显、扩展字段兼容性

这张图把整个请求结构串起来了。你排查的时候,就从最外层开始逐层验证:版本号对不对?requestList有没有漏掉证书?CertID里的哈希和序列号是否匹配?扩展字段有没有多出奇怪的OID?每一步都做二进制比对,基本就能定位问题了。

一句话总结:OCSP请求格式的坑,十有八九出在CertID的哈希计算和Nonce扩展的来回校验上。SDK升级后如果改动了加密库或OCSP构造逻辑,这两块必须重点回归测试。