第2章|OCSP请求与响应格式详解
说实话,很多做SSL/TLS的工程师都把OCSP当黑盒用。调个openssl命令,返回“good”就算完事。但我告诉你,一旦线上出现超时,你不懂报文结构,根本无从下手。
我刚接手PKI运维时也吃过这个亏。明明CA没问题,客户端却一直报“OCSP response verification failed”。查了一整天,最后发现是请求里少了关键扩展——Nonce。所以本章,我把OCSP请求与响应的完整结构掰开揉碎了讲给你听。
2.1 请求格式:客户端到底问了什么?
OCSP请求用ASN.1 DER编码。底层走HTTP GET或POST。结构上,核心就是“你帮我查查这张证书还活着没”。
请求由三个部分组成:
- 版本号:目前只有0(v1);
- 请求者名称:可选,一般是空的;
- 请求列表:真正干活的部分,包含待查询的证书标识;
- 扩展:可选的附加信息,但实战中几乎必带。
每个待查询的证书标识又包含:
- 哈希算法:默认SHA-1,但现在推荐用SHA-256;
- 颁发者公钥哈希:CA公钥的哈希值;
- 序列号:证书本身的序列号。
openssl ocsp -reqout导出,再配合dumpasn1解析。看一眼就知道哈希算法对不对,序列号有没有写错位。
下面是典型的OCSP请求结构,我用ASN.1的伪代码表示:
OCSPRequest ::= SEQUENCE {
tbsRequest TBSRequest,
optionalSignature [0] EXPLICIT Signature OPTIONAL
}
TBSRequest ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
requestorName [1] EXPLICIT GeneralName OPTIONAL,
requestList SEQUENCE OF Request,
requestExtensions [2] EXPLICIT Extensions OPTIONAL
}
Request ::= SEQUENCE {
reqCert CertID,
singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL
}
CertID ::= SEQUENCE {
hashAlgorithm AlgorithmIdentifier,
issuerNameHash OCTET STRING,
issuerKeyHash OCTET STRING,
serialNumber CertificateSerialNumber
}
2.2 响应格式:CA怎么回答你?
响应的结构同样遵循ASN.1。最上层是OCSPResponse,它包含一个ResponseStatus和可选的ResponseBytes。
ResponseStatus有六种取值,我直接放一张对比表,你一看就明白:
| 状态码 | 含义 | 常见原因 |
|---|---|---|
| 0 | successful | 正常,响应体里有具体状态 |
| 1 | malformedRequest | 客户端请求格式不对,DER解析失败 |
| 2 | internalError | CA那边崩了,比如数据库连不上 |
| 3 | tryLater | CA太忙,让你过会儿再问 |
| 5 | sigRequired | CA要求签名请求,但你没签名 |
| 6 | unauthorized | CA不认你,没权限查询 |
tryLater(状态码3),客户端默认会重试三次,每次间隔30秒。但我们的Nginx配置的OCSP超时只有5秒……结果就是每次刷新都报“OCSP auth error”,用户直接看到502。
如果状态码是0,则ResponseBytes里会包含真正有效的BasicOCSPResponse。它由三块组成:
- tbsResponseData:响应的主体,含版本、时间、证书状态;
- signatureAlgorithm:签名用的算法;
- signature:CA的数字签名;
- certs:帮助验证的中间证书。
tbsResponseData里最核心的是SingleResponse,它告诉你单张证书的生死:
SingleResponse ::= SEQUENCE {
certID CertID,
certStatus CertStatus,
thisUpdate GeneralizedTime,
nextUpdate GeneralizedTime OPTIONAL,
singleExtensions [0] EXPLICIT Extensions OPTIONAL
}
CertStatus ::= CHOICE {
good [0] IMPLICIT NULL,
revoked [1] IMPLICIT RevokedInfo,
unknown [2] IMPLICIT NULL
}
三种状态:
- good:证书有效,未被吊销;
- revoked:证书被吊销,附带吊销时间、原因;
- unknown:CA不认识这张证书。
这里还有一个关键时间字段——thisUpdate和nextUpdate。它们共同决定了OCSP响应在客户端能缓存多久。如果nextUpdate没填,客户端就默认不缓存,每次都得重新查。你想想看,高并发场景下,这得多消耗CA资源。
2.3 扩展:Nonce和Staple的秘密
扩展字段在请求和响应里都能出现。我最常用的扩展是两个:
- Nonce:客户端生成一个随机数,CA把它原样签回响应。避免重放攻击。我建议所有生产环境都启用。
- Staple:严格来说是服务器用OCSP响应来“钉住”证书状态。原理就是把响应提前缓存,客户端握手时服务器直接返回签过名的响应,省去外发请求。
nextUpdate。我见过有人缓存了过期的响应,结果证书吊销快一天了,浏览器还在显示绿色小锁。
Nonce的OID是1.3.6.1.5.5.7.48.1.2。如果CA支持Nonce,响应里也会原样返回。不符合时客户端应该验证不通过。但现实是,有些CA就是不支持Nonce,服务端得做降级处理。
2.4 核心流程图:请求与响应的握手
为了让你把整个过程串起来,我画了一张图。记住这张图,以后排错就有了路线图。
记住了,OCSP所有问题最后都落在这三个维度:
- 请求构造:序列号、哈希算法对不对?Nonce带没带?
- 网络传输:HTTP超时、GET/POST被代理拦截?
- 响应解析:状态码含义、签名验证、缓存策略是否合理?
2.5 实战排错:从报文看问题
我知道了,光讲理论你肯定不过瘾。讲一个真实案例。有一次客户反馈说Nginx启了OCSP Stapling,但浏览器偶尔还会弹出“证书已吊销”的告警。
我抓包一看,响应里的nextUpdate是当前时间之前的一个小时。也就是说,CA返回的响应本身就过期了。客户端拿个过期响应去钉,浏览器当然不认账。
解决方案也简单:返回报文里,CA把nextUpdate时间戳写错了。找CA供应商修了后,一切正常。
所以,读懂OCSP请求与响应的每一比特,是你排查超时问题的基本功。不做黑盒工程师,从拆开ASN.1报文开始。
openssl ocsp -no_nonce做测试。我见过有人用这个命令测了一周,没发现问题。部署上线后,客户端强制校验Nonce,结果全线超时。测试环境一定要模拟客户端的真实行为。