返回博客

浏览器说证书没问题,微信小程序却一直报「证书无效」

SSL 证书到期,顺手用 acme.sh 签了一张 Let's Encrypt 的免费证书,部署到 CDN,curl -I 返回 200,Chrome 绿锁,openssl s_client 显示 Verify return code: 0 (ok)。一切看起来万无一失。

打开微信开发者工具跑一下 wx.request——直接报错:

{errMsg: "request:fail 对应的服务器证书无效.", errno: undefined}

然后就陷入了迷宫:换 RSA 密钥、重新部署全链、检查 CDN 配置……都没用。

现象

  • 浏览器(Chrome / Safari):正常,绿色小锁
  • curl / openssl s_clientVerify return code: 0 (ok)
  • 阿里云 CDN 控制台:证书状态 DomainCnameStatus: ok
  • 微信开发者工具模拟器 + 真机:request:fail 对应的服务器证书无效errno: undefined
  • 换成 Let's Encrypt RSA(R13 中间证书)再试:同样失败

根因

微信小程序内置了一套独立的 CA 信任库,与操作系统和浏览器完全隔离。这个信任库由腾讯自行维护,更新节奏远慢于系统根证书库。

Let's Encrypt 近年新启用的中间 CA(ECDSA 系列 E5–E8、RSA 系列 R10–R14)均挂在 ISRG Root X1 / X2 根下。问题在于:

  • ISRG Root X1 被纳入微信信任库的时间极晚,Android 微信 ≤ 8.0.40 部分版本至今仍不信任
  • ISRG Root X2(ECDSA 根)支持更慢,E5+ 中间证书在大量客户端版本下直接失败
  • DigiCert Global Root G2 自微信小程序上线起便在信任库中——兼容率接近 100%

这意味着:curl 用的是系统 openssl,浏览器用的是系统/自己的根证书库,而小程序走的是完全不同的通道。Verify return code: 0 只证明证书在 openssl 视角下合法,与小程序的判断无关

坑:换 RSA 密钥不是出路

Let's Encrypt ECDSA(E5–E8)和 RSA(R10–R14)两条链都共享 ISRG Root X1 这个根,切换密钥类型治标无效。唯一的变量是根 CA,不是密钥算法。

正解

放弃让 Let's Encrypt 兼容微信小程序,直接换成 DigiCert / TrustAsia 体系的证书。

阿里云每个实名账号每年有 20 张免费的 DigiCert DV 3 个月证书(digicert-free-1-free 套餐),可以通过命令行直接申请并部署到 CDN:

# 申请证书(DNS 验证,域名在同账号则 AliDNS 自动添加 TXT 记录)
aliyun cas CreateCertificateForPackageRequest \
  --ProductCode digicert-free-1-free \
  --Domain example.com \
  --ValidateType DNS \
  --Username "Admin" \
  --Email admin@example.com \
  --Phone "13800000000"
# -> { "OrderId": 15713024 }
 
# 等签发完成(通常 1-3 分钟)
aliyun cas DescribeCertificateState --OrderId 15713024
 
# 提取证书并部署到 CDN
CERT=$(aliyun cas DescribeCertificateState --OrderId 15713024 | jq -r .Certificate)
KEY=$(aliyun cas DescribeCertificateState --OrderId 15713024 | jq -r .PrivateKey)
 
aliyun cdn SetCdnDomainSSLCertificate \
  --DomainName example.com \
  --SSLProtocol on \
  --CertName "example-digicert-$(date +%Y%m%d)" \
  --CertType upload \
  --SSLPub "$CERT" \
  --SSLPri "$KEY"

不在阿里云上的可选替代:腾讯云免费 TrustAsia DV(每账号 50 张/年)、付费 DigiCert / GlobalSign DV。

正解:换 DigiCert / TrustAsia,而不是调整 Let's Encrypt 参数

以下 CA 体系在微信小程序中兼容率 100%:

  • DigiCert Encryption Everywhere DV TLS CA - G2(DigiCert Global Root G2)
  • TrustAsia TLS RSA CA
  • GlobalSign GCC R6 AlphaSSL CA

以下会在部分微信版本失败:

  • Let's Encrypt E5–E8(ECDSA / ISRG Root X2)—— 大量版本失败
  • Let's Encrypt R10–R14(RSA / ISRG Root X1)—— 旧版客户端失败

排查

还有两个验证时的坑容易把人带偏:

  1. CDN 终止点:如果域名走了 CDN(dig CNAME 能看出来),证书必须部署到 CDN 层,而不是源站 Nginx。openssl s_client 连的是 CDN 边缘,跟源站证书无关。

  2. 开发者工具缓存:微信开发者工具会按域名缓存证书验证结果。换完证书后 必须工具 → 清缓存 → 清除全部缓存,再完全退出重启(Cmd+Q)。否则你看到的还是旧的失败结果,以为没生效。

等 CDN 传播完毕(约 1–5 分钟),用 openssl s_client 确认 issuer 变成 DigiCert Inc 后,清缓存重开开发者工具,wx.request 就会恢复正常。

一句话外卖

curl 和浏览器说证书合法,只能证明证书在系统信任库下合法——微信小程序用的是独立的 CA 信任库,Let's Encrypt 的 ISRG 根系列在其中长期垫底,别用 Let's Encrypt 给小程序用的域名签证书