OpenSSL的showcerts没有根证书正常吗:服务端发链与show_chain构链排查

s_client -showcerts只看到两张证书,verify -show_chain却显示三层,是服务器漏发了根证书吗?先别往fullchain里不断追加文件。这两个选项看的不是同一件事:一个列出对端发来的材料,一个展示本地验证构建出的路径。本文用隔离三层证书链,把"收到""构链""信任"拆开。

一、showcerts不是验证通过的证书链

OpenSSL官方手册明确:-showcerts展示服务端发送的证书列表,保留发送顺序;它不是已经验证的链。看到PEM块,只能证明本次握手里收到了这些证书,不能证明名称匹配、未过期或可信。

记录版本。本文为OpenSSL 1.1.1k FIPS回环实验,不改系统信任、时间或生产配置。输出可能随版本变化。

bash 复制代码
openssl version -a

图为三层链示例,不承诺任意材料都能构链成功。配图由AI辅助生成,经技术与可读性复核。

二、根不在发送列表里,通常不是漏链

常见链路是叶证书由中间CA签发,中间CA再由根CA签发。服务端发送叶证书和所需中间证书,客户端从自己的信任库找到根,于是发送列表两张、本地路径三层,完全可以同时成立。

RFC 8446第4.4.2节要求发送者证书在列表首位,后续证书应直接签发前一张;因为信任锚独立分发,已知对端持有时可省略其证书。这里不能反过来理解成"任何最后一张都可省略"。客户端缺少构链必需的中间证书,仍可能验证失败。

信任来自预先授权,不是对端自报家门。多发根不会使其自动受信;别把陌生站点发来的根直接加入信任库。

三、保存同一次握手的完整证据

以下是Bash诊断脚本,保存后单独执行,不要直接粘贴到不想退出的交互Shell。localhost、8443及trusted-root.pem是回环示例;实际使用须替换成有权检测的终点、预期DNS名称与独立取得的可信CA文件。只写入当前专用诊断目录,不操作生产证书。

bash 复制代码
HOST=localhost
CONNECT=127.0.0.1:8443
ROOT=trusted-root.pem
openssl s_client -connect "$CONNECT" -servername "$HOST" \
  -verify_hostname "$HOST" -verify_return_error \
  -CAfile "$ROOT" -no-CApath -showcerts \
  </dev/null >handshake.txt 2>handshake.err
rc=$?
printf 's_client exit=%s\n' "$rc"
exit "$rc"

-connect选择网络终点,-servername发送SNI,-verify_hostname才提出预期名称。-verify_return_error让验证错误阻止握手继续;-no-CApath排除默认目录,显式CAfile提供本次信任材料。不要用抓到多少PEM块替代退出码与完整错误日志。

失败时可能来不及输出PEM,不能据此判断服务端未发证书;本例错误根使严格握手提前中止。拆分仅作诊断,不是成功门禁。

四、把发送列表拆开,不自动升级为信任

在同一诊断目录运行下面代码,需要Python 3。它只拆分s_client文本中的完整PEM块,拒绝覆盖已有captured目录。若前一步失败,仍可把已有证书作诊断材料,但必须保留失败结论;这个解析器不是ASN.1校验器,也不是成功门禁。

bash 复制代码
python3 - <<'PY'
from pathlib import Path
import re
text = Path("handshake.txt").read_text()
certs = re.findall(r"-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----", text, re.S)
if not certs:
    raise SystemExit("no PEM certificate captured")
out = Path("captured")
out.mkdir(exist_ok=False)
for i, pem in enumerate(certs):
    (out / f"peer-{i}.pem").write_text(pem + "\n")
print("received certificates:", len(certs))
PY

peer-0是本例叶证书,peer-1是中间CA;真实环境应逐张查看subject、issuer与签名关系,不能只凭文件序号把所有后续项都当根。多出来的交叉签名证书也可能改变候选路径,数量不是通用健康分。

五、show_chain回答本地怎么走到信任锚

下面仅适用于已经确认"叶+一张中间CA"的本例。-trusted限定信任锚来源并排除默认库,-untrusted只给构链候选,不表示把中间证书授信。-show_chain自OpenSSL 1.1.0加入;低版本不能照抄此展示选项。

bash 复制代码
openssl x509 -in captured/peer-0.pem -noout -subject -issuer
openssl verify -show_chain -purpose sslserver \
  -verify_hostname localhost -trusted trusted-root.pem \
  -untrusted captured/peer-1.pem captured/peer-0.pem
rc=$?
printf 'verify exit=%s\n' "$rc"
exit "$rc"

成功时本例显示depth 0叶、depth 1中间CA、depth 2根。中间项标记untrusted表示来自非信任材料列表,不等于它最终验证失败。这一行很容易让人紧张:标签说的是材料来源,不是最终判决。

离线通过不证明生产入口发链正确,仍须用实际客户端复验。

六、四组对照比数证书更可靠

我用同一叶证书和中间CA切换服务端发送列表,并以独立根做反例。最终HTML中的四个代码块也提取执行,避免文章看着正确、复制出来却变样。

服务端发送 客户端信任 本例结果
叶+中间 正确根 收到2张,验证成功
只有叶 正确根 缺中间,严格验证失败
叶+中间+根 无关根 发送3张仍验证失败
叶+中间+根 正确根 收到3张,验证成功

额外负例把预期名称换错,完整链仍失败;离线去掉-untrusted,中间材料不足也失败。以上是本机回环与文件实验,不冒充公网浏览器、生产代理或所有TLS实现的测试。临时私钥和监听在实验结束后清理。

七、定位问题时别做三种"修复"

第一,不为凑齐三张就追加根。单根直签、多个中间层和交叉签名都会改变数量。第二,不把-untrusted改成信任配置来消除报错;材料来源与授权信任是两回事。第三,不关闭名称或链验证,让错误消失并不等于修好。

客户端结果不一致时,对照终点、SNI、实际发链和信任库。某客户端能补齐中间证书,不代表服务器可以漏发。

八、验收清单与参考资料

先确认检测的是正确终点与名称;保存本次发送列表;核实叶与中间证书关系;从授权渠道确认信任根;执行严格链与名称验证;最后由实际应用检查HTTP和业务结果。分别保留证据,不把"看到根""数到三张"或离线OK当作完整HTTPS验收。

相关推荐
lhh_qrsly4 小时前
HTTP / HTTPS / MQTT / MQTTs(嵌入式学习版)
学习·http·https
Lsetea1 天前
curl访问HTTPS报28 Operation timed out:连接超时与总超时分层排查
运维·https·ssl证书·curl·tls
Lsetea1 天前
SSL证书指纹相同但文件哈希不同:PEM、DER与公钥摘要怎么比
运维·https·ssl证书·openssl·证书链
Lsetea2 天前
OpenSSL指定-cipher仍协商TLS1.3:证书排查中的套件与协议边界
运维·https·ssl证书·openssl·tls
程序员夏洛2 天前
TCP 是用来解决什么问题?
java·网络协议·http·https
青瓦梦滋3 天前
【在线五子棋对战】游戏匹配模块
网络·c++·网络协议·http·https
Lsetea4 天前
curl设置SSL_CERT_FILE仍报60:CURL_CA_BUNDLE与CA路径优先级排查
linux·https·ssl证书·openssl·curl
2501_915921434 天前
iOS应用从开发到上架App Store的完整步骤详解
android·ios·小程序·https·uni-app·iphone·webview
上海云盾-小余4 天前
自定义端口 HTTPS 部署:高防接入遇到的问题与调优方案
网络协议·http·https