证书没过期,根也配对了,OpenSSL却报certificate chain too long。别急着从fullchain里删一张证书:可能不是文件太大,而是验证端允许的中间CA层数不够。本文用隔离证书链复现error 22,说明verify_depth怎样计数,以及它和CA证书里的pathlen为什么不能互相代替。
一、先看报错来自哪一个验证端
这里讨论openssl verify离线验证。若错误来自网关或双向TLS,先确认是谁验证谁:服务端验证客户端,与客户端验证服务端,是独立路径。调错一侧,日志当然不会变。
本轮环境为OpenSSL 1.1.1k FIPS、RSA 2048位和SHA-256,临时生成Root→CA-A→CA-B→leaf;CA主体各异,无交叉签名或自颁发中间证书。未改生产服务、系统信任库。其他版本需重新验证。

二、verify_depth数的是中间CA,不是PEM块
OpenSSL官方文档将-verify_depth定义为允许的中间CA数量上限;叶证书和信任锚不计入。上面的路径有四张证书,但中间CA只有两张,所以深度预算需要覆盖两层,而不是把参数写成4才安心。
错误信息里的depth又是另一回事:它描述出错证书在检查链中的位置,通常叶子为0、直接签发者为1。不要把error 22中的22、日志depth和命令退出码混成一个数字,它们分别是错误编号、位置和进程结果。
BEGIN CERTIFICATE数量不等于验证路径层数。重复材料、备用链或额外根会改变文件数量,却不一定增加中间层;成功时结合-show_chain看实际路径。
三、准备可信根和中间材料,再复现限制
在独立工作目录保留原件。leaf.pem是目标叶证书;root.pem只放经过授权确认的信任锚;intermediates.pem放CA-B与CA-A的PEM证书。ca-a.pem、ca-b.pem为分别导出的单张副本。不要把私钥放进这些包,更不要直接把对端送来的根当成可信根。
bash
openssl version -a
openssl x509 -in leaf.pem -noout -subject -issuer
openssl x509 -in ca-a.pem -noout -text
openssl x509 -in ca-b.pem -noout -text
本例用-trusted显式提供可信材料,不额外加载默认CAfile、CApath;3.0文档还包括CAstore。这些来源选项不能混用。-untrusted只提供构链材料,不会自动获得信任。核对subject、issuer、CA用途及扩展后,运行预算为1的负例:
bash
openssl verify -trusted root.pem -untrusted intermediates.pem \
-purpose sslserver -verify_hostname app.example.test \
-verify_depth 1 -show_chain leaf.pem
本轮得到error 22 at 2 depth lookup: certificate chain too long,退出2,位置对应CA-A;其他路径不一定是depth 2。保留完整输出,不只截最后一行。
四、经策略确认后调整预算,再看完整路径
如果组织允许两层中间CA,且这条链的来源、用途和约束都正确,可以在隔离验证里将预算调整为2。数字来自实际PKI设计,不是遇错就一路加到99。
bash
openssl verify -trusted root.pem -untrusted intermediates.pem \
-purpose sslserver -verify_hostname app.example.test \
-verify_depth 2 -show_chain leaf.pem
本轮返回leaf.pem: OK,退出0;-show_chain列出leaf、CA-B、CA-A、Root。对照中,Root直接签发叶证书时,-verify_depth 0也可通过;只有一层中间CA的链,0失败、1通过。这说明叶子和根确实不消耗这里的中间层预算。
通过只说明本次输入、时间和选项下的离线验证成功。服务使用的库、信任配置、部署节点和握手材料可能不同;不能拿本机这一句OK替代真实客户端请求,更不能保证浏览器行为一致。
五、提高深度仍失败,区分error 22与error 25
verify_depth属于验证端策略;pathLenConstraint是CA证书basicConstraints中的签名内容。RFC 5280对后者限制的是该证书之后的非自颁发中间证书数量,最终目标不计入。不要把这条定义简单扩大到所有特殊路径。
本轮另一条链把CA-A签为pathlen:0,却仍让它经CA-B签发叶子。即使将verify_depth设为10,仍得到error 25 at 2 depth lookup: path length constraint exceeded,退出2。验证端给了更多预算,也不能取消CA证书已经签名的约束。
| 隔离条件 | 本轮结果 | 处理方向 |
|---|---|---|
| 两层中间CA,depth预算1 | error 22,退出2 | 核对验证端预算 |
| 同一合法链,预算2 | OK,退出0 | 继续真实服务验收 |
| 根直签叶,预算0 | OK,退出0 | 根与叶不计入预算 |
| CA-A违反pathlen,预算10 | error 25,退出2 | 修正签发层级与证书 |
| 缺中间CA或验证名错误 | 其他验证错误 | 不能只调深度 |
改配置不会改变已签发证书。层级违反约束时应授权重设计或签发,不能手改PEM,也不能临时信任CA-A绕过原路径。
六、自动化保留退出码,别让日志盖住失败
下面的Bash片段仅用于已批准两层中间CA的场景。目录由任务身份独占写入,避免并发覆盖和不可信软链接;日志也应按内部信息处理。先保存verify结果,失败立即返回,不把printf或日志工具的成功当验证成功。
bash
umask 077
rc=0
openssl verify -trusted root.pem -untrusted intermediates.pem \
-purpose sslserver -verify_hostname app.example.test \
-verify_depth 2 -show_chain leaf.pem >verify.log 2>&1 || rc=$?
[ "$rc" -eq 0 ] || exit "$rc"
printf '%s\n' '离线链检查通过,仍需实际服务验收'
本轮重放了正文四段命令:负例按预期失败,其余通过;包装器另外覆盖错误域名、缺中间CA、pathlen违规和日志路径不可写,均阻断成功消息。这里没有关闭签名、主机名或用途验证,也没有启用部分链来缩短路径。
七、修复后的验收清单
- 记录实际执行文件、库版本、验证方和目标证书,不照搬另一进程的设置。
- 可信根来自授权渠道,中间材料与目标叶分开,不能以文件数量代替路径。
- 成功路径的中间层预算符合政策,CA的pathlen、用途、名称、时间仍分别有效。
- 保留一个超深负例,确认自动化会失败关闭;输出与日志失败也要测试。
- 按真实应用配置重新握手并验证业务响应;本文离线实验不冒充上线验收。