西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → 安卓浏览器报证书错误,curl 却是绿的,少发了一张中间证书

安卓浏览器报证书错误,curl 却是绿的,少发了一张中间证书

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 7:03:14字体大小:A-A+

作者:xiix点击:2次评论:0次标签: 证书链 中间证书 SSL证书链 openssls_client https握手失败

  • 类型:系统工具大小:12.4M语言:中文 评分:10.0
  • 标签:
立即下载

上周有个客户急吼吼地找我,说他们的官网在安卓手机上打不开,弹出来一个红色的安全警告,提示「您的连接不是私密连接」或者「证书无效」,点「继续访问」能勉强打开,但转跳到带小锁的地址就彻底卡住。更离谱的是,客户自己在电脑上用浏览器访问同一个域名,一切正常,页面渲染完整,HTTPS 小锁也是绿的。他第一反应是「手机中毒了」「是不是安卓系统版本太老」,甚至怀疑我上次改的 Nginx 配置把站点搞坏了。

但我这边用 curl 一测,输出干干净净:SSL certificate verify ok,握手过程没有任何 warning。我又让他拿一台安卓手机开抓包,结果一包就露馅了手机端在握手阶段就收到了服务器的证书,然后立刻判定为无效,直接断开,根本没走到业务请求那一步。也就是说,证书本身没过期、没被吊销、私钥也是对的,问题出在服务器发出去的那一串证书不完整。

想一边排查一边看具体的握手报文,可以用这台服务器上的 httpcanary 抓包工具(安卓抓包调试):它能在手机上把 TLS 握手过程完整抓下来,服务端返回了几张证书、少了哪一段,一眼就能看明白,比在终端里反复敲 openssl 命令直观得多。

HTTPS 证书链层级结构:叶子证书与中间证书缺失示意

一、先搞清楚:证书链到底是什么

证书链这个概念新手最容易搞混。简单说,一张被 CA 签发出来的公网证书(我们叫叶子证书或者 server cert)本身并不完整。它里面只写了「我是谁」和「谁签发了我」,但验证方要往上一级确认「签发我的这个 CA 是不是可信的」,需要顺着链条一层一层往上走:

叶子证书(你的域名) → 中间证书(Intermediate CA,通常由 CA 签发给你) → 根证书(Root CA,预装在操作系统和浏览器里)。

浏览器验证的时候,是从叶子证书开始往上顺着链子走,只要中间有一环断了,整条链就断。根证书不用你发,因为每台设备的系统里都预装了一份信任列表。但中间证书必须由服务器在握手时主动发出去,这是最容易漏的一步。

而这里有个更要命的地方:中间证书往往不由你直接签发,而是打包成一个 .bundle 或者 fullchain.pem 文件给你,名字里带不带 chain 字样全看签发机构(CA)的脾气。有些 CA 给的 bundle 是完整的链,有些只给叶子证书,让你自己去下载中间证书拼。这两种我都遇到过。

同一站点在电脑浏览器与安卓手机上访问结果不同的对比场景

二、为什么 curl 正常,手机不正常

这是排查时最容易被带偏的一点。同样是缺中间证书,为什么桌面浏览器和 curl 常常能正常访问?

原因是缓存和补链机制。主流桌面浏览器和 curl 库(比如 OpenSSL 内置的验证逻辑)都带有「AIA 补链」能力:证书里的 Authority Information Access 扩展字段里写着 CA 的下载地址,客户端发现链断了,会主动去那个地址把缺失的中间证书下载下来,临时拼进链条里,握手就成功了。

但这个能力不是所有客户端都有:

  • 部分安卓 WebView、微信内置浏览器、老版本 Android 系统的 TLS 栈不实现 AIA 补链,链一断就直接失败;
  • 证书里的 AIA 字段如果被某些工具在转换格式时弄丢了(比如从 PKCS#12 转成 PEM 时没保留扩展),补链这条路就断了;
  • 企业内网环境如果禁了外网出站,补链请求发不出去,照样失败;
  • 有些客户端虽然能补链,但缓存策略有延迟你第一次访问失败,第二次突然好了,这就是它把中间证书缓存下来了。

所以「curl 能通」根本不能证明你的证书配置没问题。它只证明你的客户端恰好会自己补。

服务器到手机电脑的 HTTPS 握手链路与中间证书缺失位置示意

三、三个命令把问题定位死

不用猜,按顺序敲这三个命令,基本两三分钟就能出结论。

第一步,看服务端到底发了什么。

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts

输出里 Certificate chain 下面会有 0、1、2 三段。正常情况(叶子 + 一张中间)应该有 2 段,如果你只看到 1 段,就是只发了叶子证书。段数少于 2 基本可以定案了。

第二步,看浏览器/客户端视角拿到的链。

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -verify_return_error

如果这台机器恰好带了 AIA 补链,你会看到 Verify return code: 0 (ok);把 -verify_return_error 去掉,再加 -verify 0 配合 ssl_chain_verify 的思路去看握手时实际用了几张证书。总之,只要第一段命令只吐一张证书,问题就已经确认了,别在客户端上继续折腾。

第三步,对比 CA 官方给的文件。

登录你的 CA 后台(Let's Encrypt、阿里云、腾讯云、DigiCert 都是),找到当初签发的那张证书,看它提供了几个下载文件:

  • 只给 .crt 一个文件的,大概率要自己拼;
  • 给 fullchain.pem / .bundle 的,直接换过去;
  • 给 chain.pem 单独一张的,用 cat 把叶子证书和它拼起来。

拼接顺序有讲究:叶子证书在最上面,中间证书在下面,顺序反了照样报错。

四、Nginx 修复:fullchain 而不是单张

确认是少发中间证书之后,修起来其实很快。Nginx 里证书路径改成拼接后的完整链文件就行。

cat leaf.crt chain.pem > fullchain.pem

然后在 server 块里:

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/leaf.key;

注意 key 必须还是叶子证书对应的私钥,中间证书没有私钥,别搞混。改完 nginx -t 检查语法,nginx -s reload 热加载,不用重启,连接不会断。

如果用的是 Let's Encrypt 的 certbot,其实默认行为就已经是对的certbot 会把 live/ 目录下的 fullchain.pem 给你,直接指向它即可。但很多人在手工续期或者从别的 CA 迁过来的时候,把 cert.pem(只有叶子)填进去了,这才是事故源头。另外 手动续期脚本也要一起改,不然三个月后自动续期又会把好的覆盖回错的。

安卓手机浏览网页时弹出安全警告提示的场景示意

五、顺手要做的几件收尾事

修完证书链本身,还有几个地方建议一起检查,不然下一个坑还在路上:

1. 检查私钥和证书是否配对。 用 openssl x509 -noout -modulus -in fullchain.pem | openssl md5 和 openssl rsa -noout -modulus -in leaf.key | openssl md5 对一下两个 md5,一致才是配对的。

2. 全站扫一遍域名。 你大概率不止一个站点用同一批证书。主站、后台、接口域名、静态资源域名,任何一个用了单张叶子证书都可能出问题。用 openssl s_client -connect ip:443 -showcerts 挨个试一遍,别只修报错的那一个。

3. 检查 OCSP stapling 有没有真的生效。 openssl s_client -status 看返回里有没有 OCSP Response Data。顺便说,很多人把 stapling 配了却因为没把中间证书传对,导致 stapling 静默失效,这两件事经常一起坏。

4. 把检查做成定时任务。 我现在的做法是每天跑一次脚本,批量对所有域名做 s_client -showcerts,只要输出的证书段数少于 2 就报警。这个检查成本极低,但能在客户投诉之前发现问题,比事后排查省事太多。

5. 顺手验证一下 TLS 版本。 老设备能连不代表配置合理,现在应该只留 TLS 1.2 和 1.3,TLS 1.0/1.1 就关掉。不过这一步和本次事故不是一回事,别混在一起改,否则出问题分不清是哪个引起的。

六、写在最后

这次事故的教训挺朴素:不要用「我这边能访问」来判断证书配置是对的。你的 curl、你的桌面浏览器、甚至你同事的 iPhone,都是带着「自动补链」能力的高级客户端,它们会替你把中间证书补上,把配置缺陷藏起来。而真正会因为这个问题挂掉的,恰恰是那些不补链的客户端安卓 App 内置浏览器、微信里的链接、老旧设备、还有各种 API 客户端。

所以正确的验收标准是:openssl s_client -showcerts 的输出里必须有 2 段以上,且是 fullchain 而不是单张叶子。做到这一点,剩下的交给浏览器和设备去谈吧。

顺带提一句,这次能这么快定位,是因为链路上有人专门看了一眼「服务器到底发了什么」这个最朴素的问题,而不是在客户端上反复重装浏览器。很多故障排查的弯路,都是因为一开始就去查最复杂的那一层了。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲