OpenSSL报self-signed certificate in certificate chain:error 19与信任根排查

服务器明明把根证书也发出来了,OpenSSL却报self-signed certificate in certificate chain,curl仍然不信任。这里容易把"链里有根"与"客户端信任根"混成一件事。本文用临时私有CA和回环HTTPS对照,定位error 19,并解释为什么删掉服务端根证书后,错误文字变了,问题却没修好。

一、error 19不是说叶证书一定自签

OpenSSL把这个错误标为X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:利用不受信任的证书材料可以构建到根,但本地没有找到可信的根。这里的"不受信任材料"是构链输入的角色,不是已经判定每张证书伪造。

叶证书完全可以由中间CA签发。链条走到了自签根,仍缺少客户端认可它的信任依据。对端递来的"身份证明册"里有盖章的人,不代表这个盖章人自动进入你的信任名单。

同时保留完整错误和depth。本文三层链中叶子为depth 0,中间CA为1,根为2;19是X.509验证错误号,不是Shell进程退出码,也不是curl错误码。

二、先确认哪个客户端、哪套信任库

浏览器正常而脚本报错,不足以证明服务端没问题,也不足以证明证书被吊销。两者可能走不同代理、不同入口,或使用不同信任库。先记录实际客户端版本与TLS后端,再确认URL、目标节点及运行账户。

bash 复制代码
openssl version
curl --version

本文实测命令行是OpenSSL 1.1.1k FIPS、curl 7.61.1的OpenSSL后端。这是复现实验环境,不是推荐部署的版本基线。生产应使用受支持的版本,并按实际后端查信任配置;不能把Linux文件路径原样套到所有系统。

若只有某个容器、服务账户或定时任务失败,应在它实际运行的环境读取CA配置,而不是以管理员终端成功作结论。诊断日志可能含请求头或内部域名,对外分享前脱敏。

三、分清发来的链与受信任的根

s_client的-showcerts展示的是服务器发来的证书列表,不是一条已经验证通过的链。不要把抓到的全部PEM直接放进受信任CA文件;对端不能通过多发一张证书,就替客户端决定该信谁。

私有PKI场景应从获授权的管理渠道取得根证书,独立核对指纹、用途与适用范围,再向需要它的客户端分发。下面只读取本地文件,不修改系统信任库;subject与issuer相同也不能单独证明其可信。

bash 复制代码
openssl x509 -in root.pem -noout \
  -subject -issuer -dates -fingerprint -sha256

指纹应与独立可信渠道提供的值比较,不是把同一份网络下载文件算两遍。文件名root.pem不带任何信用,下载成功更不是授权决定。公开网站则应核查是否部署了适合目标客户端的证书链,避免让普通访客安装来路不明的根。

四、把两种角色写进验证命令

以下假定已准备好leaf.pem、intermediate.pem和经授权确认的root.pem,叶证书SAN包含localhost。它们分别是待验证叶子、辅助构链的中间CA和信任锚;示例不是从未知服务抓证书后自动授信的脚本。

bash 复制代码
openssl verify -trusted root.pem \
  -untrusted intermediate.pem -purpose sslserver \
  -verify_hostname localhost -show_chain leaf.pem

在本文OpenSSL版本中,-trusted只使用指定信任锚,不再读取默认CA列表,且不能与-CAfile或-CApath混用。-untrusted补充构链材料,并不会因为放了根就让它受信任。命令还显式检查服务端用途与主机名,-show_chain用于观察实际构建路径。

需要的是"来源可信、角色正确、验证通过"三件事。离线OK不代表线上发链已经更新,下一步还必须连真实TLS终止点验收。

五、隔离实验:为什么删根只会换报错

本轮生成Root→Intermediate→localhost三层临时链,另建不相关的Other根作信任负例。所有离线验证使用明确的-trusted文件;另在127.0.0.1随机端口运行HTTPS服务,分别发送带根和不带根的链。未更改系统时间、系统信任或生产服务,结束销毁临时证书与私钥。

信任锚与构链材料 verify实测结果 含义
信Other;中间+根放untrusted 19,depth 2 根出现但未获信任
信Other;仅提供中间 20,depth 1 找不到可用的上级
信Root;提供中间 退出0,OK 路径到可信根
信Root;缺少中间 20,depth 0 构链材料仍不足
信Root;提供中间但验证错名 62,depth 0 信任不替代名称匹配

表中的失败案例进程均退出2,而不是退出19、20或62。HTTPS对照中,curl信Other时,带根返回60并报告self signed certificate in certificate chain;去掉根仍是60,只是变为unable to get local issuer certificate。两次均未进入HTTP处理。

改用正确Root后,两种发链方式都得到HTTP 200。这个结果不是鼓励生产发送根,而是证明删除根本身不能修复缺失的信任。实验只代表本机工具与夹具,不推断所有浏览器的构链策略。

六、修复后用真实请求验收

下面在Bash中执行,针对已准备的回环服务;8443替换为实际测试端口,root.pem为已确认的测试根。-q须放在首个选项以忽略默认curlrc,--noproxy仅用于本地实验,不是绕过生产网络策略的建议。

bash 复制代码
umask 077
rc=0
curl -q --noproxy '*' --cacert root.pem \
  --connect-timeout 3 --max-time 8 \
  --fail --silent --show-error --output body.txt \
  --write-out '%{http_code}' https://localhost:8443/ \
  > probe.status 2> probe.err || rc=$?
[ "$rc" -eq 0 ] || exit "$rc"
[ "$(<probe.status)" = 200 ] || exit 1

保留证书验证,不使用-k。包装器同时检查curl退出码和测试接口约定的200;HTTP错误、输出文件写失败、错误日志路径不可写都不能当成功。000表示未取得HTTP状态,不是服务端返回"000"。真实业务还需按接口契约验响应内容。

七、交付前最后一遍清单

  • 明确失败账户、运行环境、TLS后端与实际终止点。
  • 保留原始错误、depth、退出码和当时的证书指纹。
  • 信任根来自独立授权渠道,辅助链不自动变为信任锚。
  • 离线用途与名称检查通过,再做HTTPS和业务验证。
  • 覆盖受影响客户端及节点,不拿一次成功代表全网。

遇到19,优先问"这个根为什么应该被我信任",而不是"怎样把错误提示去掉"。证书链材料、信任策略和业务响应分开交付,修复才不会只停在一行OK。

八、参考资料

相关推荐
2501_915106329 小时前
抓包抓到乱码或压缩字节怎么解析,TraceEagle五种查看模式和一键解码
网络协议·计算机网络·网络安全·ios·adb·https·udp
zxanz12 天前
证书运维自动化,守住业务 HTTPS 底线
运维·https·自动化
Lsetea2 天前
Git访问HTTPS证书不受信任:用URL级sslCAInfo限定私有CA范围
git·https·开发工具·ssl证书·ca证书
2501_916007473 天前
抓包工具功能对比,TraceEagle、Charles、Fiddler、Wireshark、mitmproxy 差别
前端·网络协议·ios·adb·https·fiddler·wireshark
闲云自留地3 天前
K8s Ingress 全解:ingress-nginx 部署、域名路径路由、HTTPS 限流灰度一篇通
nginx·https·kubernetes
zxanz13 天前
国密 SM2 SSL 证书和国际证书怎么选,一文理清差异
https·ssl
张赐荣4 天前
sslx: 一个检测、查看 HTTPS 证书信息的跨平台单文件命令行工具
网络协议·http·https
RacentCLM4 天前
HTTPS只在登录时有用?关于TLS的误解讲清
数据库·网络协议·https
伤心的梧桐叶5 天前
商品大图断点续传后打不开,先看是不是把完整响应又接了一遍
http·https
我命由我123455 天前
6-2.Python 异常处理 - 异常捕获(捕获指定异常、常见异常类型、异常信息)
java·运维·开发语言·python·http·https·java-ee