中间证书明明放进了CA文件,OpenSSL却报unable to get issuer certificate。补了链还不行,是证书坏了,还是信任放错了位置?本文以error 2为入口,区分"提供建链材料"和"指定信任锚",再判断是否真的应该使用-partial_chain。
一、先认完整错误,不只看issuer
error 2的名称是X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,表示查找到的证书缺少可用签发者;常见于信任材料不完整。error 20则是UNABLE_TO_GET_ISSUER_CERT_LOCALLY,涉及未受信任证书的签发者查找。两者英文很像,不能把所有issuer错误都归结为服务器漏发中间证书。
保存depth、subject、参数与退出码。depth 0是目标证书,1是直接签发者,不自动指出该装哪张根。验证错误号不等于进程退出码;以下报错限定本机实验。

二、先把三份材料分清楚
实验采用Root → Intermediate → api.example.test三级链。leaf.pem只放叶证书,intermediate.pem只放中间CA,root.pem只放经核验的根。三者均为PEM证书,不含私钥,也没有额外正负信任属性。文件名叫CA,并不会自动赋予它你期望的信任语义。
-untrusted提供辅助建链材料,不将其中的证书直接变成信任锚;-trusted提供本次验证的信任材料,并排除默认CA文件与目录。本文用它隔离系统信任库,避免"本机刚好已装根证书"掩盖问题。它不能与-CAfile、-CApath混用。OpenSSL 3.x还有CAstore选项,须按对应版本帮助核对。
bash
openssl version
openssl x509 -in intermediate.pem -noout -subject -issuer -dates
openssl x509 -in intermediate.pem -noout -fingerprint -sha256
openssl verify -show_chain -trusted intermediate.pem \
-purpose sslserver -verify_hostname api.example.test leaf.pem
上述最后一条故意只信任普通中间CA,不加-partial_chain;本机1.1.1k FIPS得到error 2 at 1 depth,签发者是未提供的Root。前面的x509仅做单张诊断,subject/issuer名称吻合不等于签名可信。指纹应通过可信渠道与预期材料比对,不能从未知站点抓一张就直接安装。
三、常规修复:回到完整根信任链
如果既定策略信任Root,修复方向就是保留该根为锚,把中间CA放进建链输入,而不是为了绿灯改变终点。下面的命令同时检查服务端用途和域名,-show_chain用于观察最终路径。
bash
openssl verify -show_chain -trusted root.pem \
-untrusted intermediate.pem -purpose sslserver \
-verify_hostname api.example.test leaf.pem
正常结果应为leaf.pem: OK,并显示叶子、中间CA、根的路径。实验中删去-untrusted后会报error 20,说明"根可信"和"中间链材料齐全"是两件事。给服务端补fullchain可以帮助客户端建链,但服务端发送根证书并不能替客户端做出信任决定。
四、partial_chain改变的是信任终点
当组织明确批准某张中间CA作为本应用信任锚,且已通过受控渠道分发材料,才考虑下面这条路径。该参数允许验证链在信任存储中的非自签证书结束,而不必继续构建到自签根;不是自动补链,也不是"缺什么就忽略什么"。
bash
# 仅用于已批准中间CA作为信任锚的独立验证
openssl verify -show_chain -partial_chain \
-trusted intermediate.pem -purpose sslserver \
-verify_hostname api.example.test leaf.pem
同一实验此时通过,路径止于Intermediate。它改变了信任边界:终点以上的Root不再是这条验证路径的成员,不能把成功结果描述为"验证到了Root"。原本依赖上级路径的策略需要重新评估,证书轮换、撤销和更新责任也必须明确。
不要直接信任网络抓取的中间证书。先核验来源、授权和指纹;CLI通过不代表应用或浏览器采用相同策略。
五、通过一个正例,还要留下负例
本次在临时目录生成测试链,执行OpenSSL 1.1.1k FIPS;不改系统信任库、时钟或线上配置。仅验证离线路径,不代表真实HTTPS握手。
| 输入与选项 | 本机观察 |
|---|---|
| 普通中间CA为trusted,不加partial_chain | error 2,depth 1 |
| 根为trusted,中间CA为untrusted | 通过,路径到根 |
| 中间CA为trusted,加partial_chain | 通过,路径止于中间CA |
| 根为trusted,未提供中间CA | error 20,depth 0 |
| partial_chain配错误验证名 | error 62,不会跳过名称检查 |
| partial_chain配过期叶证书 | error 10,不会跳过时间检查 |
另测只含clientAuth用途的叶证书,在sslserver验证下失败;换成无关信任根也失败。这些负例用来证明开关没有关闭全部验证,不是穷尽所有PKI约束。生产环境还需按自己的吊销、策略及算法要求验收。
六、别让日志把失败盖住
别只搜索OK,也别让管道吞掉失败。下面保留verify退出状态,日志写入失败也进入失败分支;只写非敏感诊断,不记录私钥或凭据。
bash
if openssl verify -show_chain -trusted root.pem \
-untrusted intermediate.pem -purpose sslserver \
-verify_hostname api.example.test leaf.pem >verify.log 2>&1; then
printf 'certificate path verified\n'
else
rc=$?
printf 'verification or log write failed: %s\n' "$rc" >&2
exit "$rc"
fi
隔离实验重放四个代码块,验证错误名称与日志路径不可写均不能输出成功。多目标应逐个记录状态,不能只看最后一个结果。
七、离线OK之后还缺什么
离线验证只证明给定文件在给定选项下成立。实际服务可能发送另一张证书、缺中间链,或应用读取了不同CA路径。上线验收要固定目标、SNI、验证名和真实客户端,重新检查所见证书与信任配置;网络、TLS与HTTP结果分开记录。
不能用关闭TLS验证作验收,也不要把中间CA盲目全局导入。报错消失不等于信任变更合理。
八、交付前检查清单
- 记录完整错误、depth、版本与真实命令。
- 区分目标证书、辅助中间链、受信任材料。
- 先按既有根信任策略补齐链;改变锚必须有依据。
- 核验show_chain终点、名称、用途、时间与负例。
- 另做真实客户端验收,保留无凭据日志。