3. OCSP 响应结构:响应格式、状态码与签名验证

拿到一条 OCSP 响应,你怎么判断它是真是假?我最早做溯源分析时,经常盯着十六进制报文发愣——后来才明白,只要拆开三层就够了:响应外壳、状态码、签名链。这章我就带你把这三层逐个扒开。

3.1 响应格式:ASN.1 包了啥

OCSP 响应用 DER 编码,打包在 Content-Type: application/ocsp-response 里。最外层是一个 OCSPResponse 结构:

OCSPResponse ::= SEQUENCE {
    responseStatus      OCSPResponseStatus,
    responseBytes       [0] EXPLICIT ResponseBytes OPTIONAL
}

OCSPResponseStatus ::= ENUMERATED {
    successful          (0),  -- 正常
    malformedRequest    (1),  -- 请求畸形
    internalError       (2),  -- 内部错误
    tryLater            (3),  -- 稍后再试
    -- (4) 未使用
    sigRequired         (5),  -- 必须签名
    unauthorized        (6)   -- 未授权
}

ResponseBytes ::= SEQUENCE {
    responseType   OBJECT IDENTIFIER,
    response       OCTET STRING  -- 实际是 BasicOCSPResponse 的 DER
}

responseStatus 不是 successful(0),后面就不用看了。状态不对,直接跳过。

我的习惯:先看 responseStatus,如果是 1、2、3、5、6,这张响应大概率是错误应答,不用费劲解析后面。

当 responseStatus 是 0,就要钻进 responseBytes。里面含一个 OID 和 OCTET STRING,OID 通常是 1.3.6.1.5.5.7.48.1.1(id-pkix-ocsp-basic),OCTET STRING 解开就是一个 BasicOCSPResponse:

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

ResponseData ::= SEQUENCE {
    version              [0] EXPLICIT Version DEFAULT v1,
    responderID          ResponderID,
    producedAt           GeneralizedTime,
    responses            SEQUENCE OF SingleResponse,
    responseExtensions   [1] EXPLICIT Extensions OPTIONAL
}

解析时,我最关注 tbsResponseData 里的 responses 序列——每条 SingleResponse 对应一个证书的状态查询结果。你想想看,高频请求溯源时,这里每条记录都可能是攻击者的目标。

3.2 状态码详解:good、revoked、unknown

状态码写在 SingleResponse 的 certStatus 字段里。三种取值:

枚举值含义我在溯源场景中的处理
good(0)证书有效,未吊销可能是正常业务,也可能是攻击者用有效期内的证书做掩护。需要结合请求频率、来源 IP 等特征判断。
revoked(1)证书被吊销高优先级告警。我曾经见过一个案例,攻击者用已吊销证书反复查询 OCSP 服务器,试图探测吊销时间点。
unknown(2)响应者不知道这个证书有两种可能:要么请求的证书序列号不在响应者管辖范围,要么是恶意构造的随机序列号——这个在探测行为中非常常见。
注意:有些 OCSP 响应对 unknown 不签名,甚至直接返回 unauthorized(6)。如果你在溯源日志里看到大量 unknown 状态,多半是攻击者在扫你的证书库范围。

3.3 签名验证:信任的最后一道关

OCSP 响应本身可以签名,也可以不签名。但 RFC 6960 强烈建议签名。对于溯源来说,不签名的响应几乎不可信。

验证过程分三步:

  1. 验签算法:从 signatureAlgorithm 拿到算法 OID(通常是 sha256WithRSAEncryption 或 ecdsa-with-SHA256),然后用 signature BIT STRING 对 tbsResponseData 的 DER 编码做验签。
  2. 验证签名者证书:从 certs 字段取签名证书,检查它是否受信任(通常链到已知的 OCSP 响应者根 CA)。我习惯顺带检查证书有效期和吊销状态——避免循环依赖。
  3. 验证 Nonce(可选但重要):如果请求里带了 nonce 扩展,响应里也必须携带相同 Nonce,否则是重放攻击。我在溯源高频请求时,经常通过 Nonce 缺失或不对应来识别伪造响应。

核心逻辑图:下面这张图展示了一条 OCSP 响应从拿到手到判定可信的完整拆解流程。

OCSP 响应结构拆解与验证流程 原始 DER 数据 OCSPResponse responseStatus / responseBytes responseStatus ≠ 0 直接丢弃/记录错误 BasicOCSPResponse tbsResponseData + signature + certs tbsResponseData responderID / producedAt SingleResponse + Extensions 签名验证 验签名 / 验证书链 核对 Nonce 判定状态码 & 响应可信度 good / revoked / unknown status=0 时进入 BasicOCSPResponse 解析
避坑指南:我曾经在做一个批量溯源脚本时,忘了验证 producedAt 时间戳是否在合理范围内。结果拿到的都是一周前的重放响应,排查了半天才发现。你一定要检查 producedAt 和当前时间的差值,我通常设 5 分钟容忍窗口。

3.4 实战:从一个高频请求日志里提取响应状态

举个例子,你从抓包或 OCSP 服务器日志里看到这样的响应字节。用 OpenSSL 命令就能快速拆解:

# 从 hex 转 DER 文件
echo "3082..." | xxd -r -p > ocsp_resp.der

# 用 openssl 解析
openssl ocsp -respin ocsp_resp.der -text -noverify 2>/dev/null

输出里你会看到:

OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    ...
    Cert Status: good
    This Update: Apr 10 14:32:00 2025 GMT
    Next Update: Apr 10 14:37:00 2025 GMT

嗯,这里要注意:Cert Status: good 并不等于安全——如果请求来自同一个 IP 每秒发出 100 条这种 good 响应请求,那就是典型的扫描行为。我个人习惯把状态码和请求频率关联起来做基线,超过阈值再告警,否则会被正常业务淹没。

高能提醒:有些畸形 OCSP 响应会篡改 signatureAlgorithm 为 NULL 或弱签名算法(如 MD5)。如果你不做算法强度校验,攻击者可以直接伪造 revoked 响应让客户端拒绝你的合法证书。我在护网行动中遇到过这种攻击手法,还好被签名验证卡住了。

总结一下:一张 OCSP 响应的质量,90% 体现在 responseStatus 是否正确、签名是否可信、Nonce 是否匹配。你做高频请求溯源时,先把这三关过了,再去看状态码。