3. TLS/SSL 协议原理:握手过程、证书链、常见漏洞

讲 API 网关加密,绕不开 TLS/SSL。很多同学觉得它就是“那串小锁图标”,但实际工作中,你遇到的绝大多数连接安全问题,根子都在握手和证书链上。我记得刚做网关那会,线上突然报大量握手超时,查了一整天——最后发现是中间人设备把证书链裁剪掉了。嗯,从那以后我再看 TLS,就不敢只看概念了。

3.1 握手过程:一笔一画都是协议

TLS 握手,说白了就是客户端和服务端在正式传数据之前,先互相打个照面、亮明身份、商量好加密方式。我用一个最简单的场景给你拆开看:

  1. ClientHello — 客户端发个招呼,告诉服务端它支持哪些 TLS 版本、哪些加密套件、还带一个随机数。
  2. ServerHello — 服务端从候选里挑一个组合,也回一个随机数。
  3. ServerCertificate — 服务端把自己的证书发过去(可能还带着中间 CA 证书链)。
  4. ServerKeyExchange — 如果需要,服务端发密钥交换参数,比如 DH 的公钥。
  5. ClientKeyExchange — 客户端生成预主密钥,用服务端公钥加密发过去。
  6. ChangeCipherSpec + Finished — 双方各自算出会话密钥,然后互相发一条“我准备好了”的消息。
个人习惯: 调试握手问题时,我直接用 openssl s_client -connect host:port -debug 看每一帧,比看抓包快多了。

这里面有一个关键点:证书链验证发生在第 3 步之后。客户端收到服务端证书,必须一直追溯到信任的根证书,中间不能断。

3.2 证书链:信任靠的是层层担保

为什么要有证书链?想象一下:如果每个网站都直接找根 CA 签发证书,根 CA 得累死,而且一旦根私钥泄露,整个互联网都完蛋。所以实际结构是:

  • 根证书(Root CA) — 自签名,预埋在操作系统或浏览器里。
  • 中间证书(Intermediate CA) — 由根证书签发,可以有多级。
  • 服务器证书(Leaf) — 由中间 CA 签发,绑在具体的域名或 IP 上。

客户端验证时,从叶子证书开始,逐级向上找签发者,直到碰到一个信任的根证书。这个过程叫 路径构建。

我曾经踩过的坑: 有一次部署 API 网关,客户端一直报 “certificate verify failed”。排查发现服务端只发了叶子证书,没发中间 CA。客户端信任列表里没有这个中间 CA,自然就断了。解决方案:在网关的 TLS 配置里加上 fullchain.pem,把中间证书和叶子证书一起发过去。

证书链长度也要注意。有些旧的客户端(比如 Android 4.x)不支持超过 3 级的链。我在金融项目里就因为这个被迫把中间 CA 合并成一级。

角色 签发者 位置
根 CA 自己签自己 客户端信任存储区
中间 CA 根 CA 服务端证书链 + 客户端信任
服务器证书 中间 CA 服务端

3.3 常见漏洞:你不得不防的几个老坑

TLS/SSL 协议活了几十年,漏洞史能写一本厚书。这里只挑对 API 网关影响最大的几个,我每个都遇到过。

3.3.1 POODLE(CVE-2014-3566)

攻击者利用 SSL 3.0 的 CBC 模式填充字节可预测性,逐字节猜出明文。当年这个消息一出,所有支持 SSL 3.0 的服务都得紧急下线。

修复方案: 服务端禁止 SSL 3.0,强制 TLS 1.2 以上。我通常会在 Nginx 的 ssl_protocols 里只写 TLSv1.2 TLSv1.3。

3.3.2 Heartbleed(CVE-2014-0160)

OpenSSL 1.0.1~1.0.1f 版本里 Heartbeat 扩展的重叠读取漏洞,攻击者可以在一次心跳请求中读出 64KB 服务端内存,里面可能就有私钥。那阵子我通宵给所有网关节点打补丁升级 OpenSSL。

我建议: 把 OpenSSL 版本管理加入 CI/CD 流水线,新 CVE 一出就自动扫描并告警。

3.3.3 BEAST(CVE-2011-3389)

针对 TLS 1.0 CBC 模式的攻击,利用 IV 可预测性破解加密流。现在 TLS 1.0 已经被大多数标准禁止了。

3.3.4 CRIME / BREACH

压缩 + 加密的组合拳:攻击者通过注入可控内容并观察压缩后长度变化,可以猜出 cookie 或 CSRF 。API 网关如果开启 HTTP 压缩,必须做好 随机化或者禁用压缩。

  • CRIME(CVE-2012-4929) — 针对 TLS 压缩。
  • BREACH(CVE-2013-3587) — 针对 HTTP 压缩。

修复很简单:关闭 TLS 压缩,并确认你的 API 响应里不包含敏感信息在压缩流中。

3.3.5 中间人攻击(MITM)

严格来说这不是协议漏洞,而是证书验证疏忽。最常见的是客户端没有校验证书链,或者信任了自签名证书。我在早期一个内部工具里犯过这个错——为了测试方便设了 SSL_CERT_FILE=/dev/null,结果上线忘了改回来…

你想想看: 如果攻击者在你的客户端和服务端之间放一个代理,偷偷换掉证书,而你客户端没校验——那加密就跟纸糊的一样。

3.4 一张图看透 TLS 握手

下面这张图把刚才讲的握手过程、证书链、以及常用加密套件串联起来。你可以把它当作排错时的思维原型。

TLS 1.2 完整握手流程(含证书链) 客户端 服务端 ① ClientHello ② ServerHello ③ 服务端证书 + 中间 CA 链 证书链验证 叶子证书 → 中间 CA → 根 CA 逐级向上查找签发者 直到匹配信任存储区 ④ ClientKeyExchange (加密预主密钥) ⑤ ChangeCipherSpec + Finished ✅ 加密通道建立

3.5 网关场景下的最佳实践

最后,给平台工程同学提几个我在 API 网关 TLS 配置里反复验证过的动作:

  • 最小化协议版本 — 只开 TLS 1.2 和 TLS 1.3,关掉所有低版本。
  • 选择合适的加密套件 — 优先使用 AEAD 套件(如 ECDHE-RSA-AES128-GCM-SHA256),避免 CBC 模式。
  • 证书链完整性 — 每次部署前用 openssl verify -CAfile trust.pem chain.pem 检查一遍。
  • OCSP Stapling — 服务端主动提供证书状态,减少客户端额外查询。
  • HSTS 头部 — 网关响应里加 Strict-Transport-Security,强制客户端走 HTTPS。
一句话总结: 网关是 TLS 终止点,你的握手策略直接决定了后端所有 API 的安全性。不要图省事用默认配置——我见过太多因为证书链不全或者协议版本太老导致的线上事故了。