目标
TLS 失败经常被归咎于应用代码,因为没人去读证书本身。四条 openssl 命令就能确立四件事:服务器实际下发了什么链、这条链是否可验证、主机名是否被覆盖、还剩多少天到期。移动端与桌面端表现不一致的经典悬案,最后往往落在“服务器没下发中间证书、桌面浏览器有缓存而移动端没有”这一个数字上。
步骤要点
- 用 s_client -showcerts 抓取服务器实际下发的链,先看叶子证书的 subject、issuer 与有效期。
- 统计 BEGIN CERTIFICATE 的出现次数:只有 1 说明只发了叶子证书,这正是移动端失败的常见根因,补齐后应为 2 或 3。
- 用 openssl verify 加 intermediate.pem 做离线链验证,OK 即证明叶子证书能穿过你持有的中间证书链接到信任根。
- 读 subjectAltName 列表并用不同 servername 握手,排查通配符陷阱(example.com 与 *.example.com 对 api 子域的覆盖差异)。
- 计算 enddate 与当前日期的天数差,把这个数字变成 21 天阈值的监控项,每次证书变更后再复查链计数。
排错提示
桌面正常移动端失败先查下发链数量,桌面多半缓存了中间证书,服务器补发完整链后重测;verify 报 unable to get local issuer certificate 是缺中间证书,从 CA 包或证书 AIA 扩展的 URL 取回补进链再验;连证书输出都没有说明故障在协议或 SNI 层,显式指定 -tls1_2、核对 servername 与 vhost 匹配、确认端口确实在讲 TLS。
适用边界
openssl 验证链与主机名但不含吊销检查,OCSP/CRL 需要额外工具且常被隐私代理挡掉;今天握手通过不代表明天安全,天数计算必须变成定时检查。
代码示例
# What chain does the server really present?
openssl s_client -connect example.com:443 \
-servername example.com -showcerts </dev/null 2>/dev/null \
| grep -c "BEGIN CERTIFICATE"
# 1 <-- leaf only; mobile clients fail here
# Serve the full chain, then verify offline
openssl verify -CAfile intermediate.pem \
-untrusted intermediate.pem cert.pem
# cert.pem: OK
# Hostname actually covered (the wildcard trap)
openssl x509 -in cert.pem -noout -ext subjectAltName
# DNS:example.com, DNS:*.example.com