第3章 数字证书与CA:你真的信任那把“锁”吗?
上一章我们把TLS握手流程跑了一遍,你可能会问:客户端怎么判断拿到的公钥是真的?总不能是服务器随便丢一张吧?
嗯,这里就要引入数字证书和CA(证书颁发机构)了。说白了,证书就是“公钥的身份证”。
我刚开始接触HTTPS改造时,总觉得证书就是个文件,配上去能通就行。直到有一次生产环境出现证书链不完整,部分老旧手机直接报错……从那以后我才认真啃了证书的结构和信任链。
3.1 数字证书长什么样?
一个X.509 v3证书,核心字段其实就这几块:
| 字段 | 内容举例 | 我的备注 |
|---|---|---|
| 版本号 | v3 | 现在基本都是v3,支持扩展 |
| 序列号 | CA给每个证书的唯一编号 | 吊销时查这个号 |
| 签名算法 | SHA256-RSA | 别再用SHA1了 |
| 颁发者 | C=CN, O=TrustCor, CN=TrustCor Root CA | 谁签发的 |
| 有效期 | 2024-01-01 到 2025-01-01 | 注意时区,我踩过坑 |
| 主体 | CN=www.example.com | 证书要保护哪个域名 |
| 公钥 | RSA 2048位的公钥 | 这就是对方要给你的公钥 |
| 签名 | 颁发者用私钥签的指纹 | 验真伪的核心 |
你拿到一个证书文件,用 openssl x509 -in cert.pem -text -noout 就能看到全部内容。我建议生产环境至少导出一次明文看看,很多配置错误都能提前发现。
关键点:证书保护的并不是“谁是谁”,而是“某个公钥确实属于某个主体”。验证签名就是确认这个绑定关系没有被篡改。
3.2 证书链:为什么浏览器信任我的证书?
你不可能直接把自签名证书发给用户,浏览器会报警。真正线上用的证书,是一个链条:
- 根证书(Root CA):CA自己给自己签的自签名证书。操作系统或浏览器内置了一批信任的根证书。
- 中间证书(Intermediate CA):根证书签发的子CA,因为根证书太金贵,不能直接签终端证书。
- 终端证书(Leaf / Server Certificate):你的服务器证书,由中间证书签发。
浏览器验证过程:从你的服务器证书往上找,依次验证签名,直到碰到操作系统里内置的根证书。这就是证书链的验证逻辑。
我曾经遇到过一个大坑:配置的证书只发了终端证书,没发中间证书。Android和iOS没问题,但某些低版本Windows和Java环境直接报错。后来我强制在Nginx配置里补上中间证书链才解决。
我的习惯:用 openssl verify -CAfile root.crt -untrusted intermediate.crt server.crt 提前验证链是否完整。去线上踩坑不如本地先跑一遍。
3.3 根证书与中间证书:为什么要有层次?
你想想看,要是所有网站直接用的都是根证书签发的,那根证书私钥一旦泄露,整个互联网都得瘫痪。所以CA搞了分层:
- 根证书私钥:物理隔离,离线保存,几年才用一次。
- 中间证书私钥:在线使用,但权限受限,只允许签终端证书。
- 即使中间证书被攻破,CA可以吊销它,用另一个中间证书继续,根证书不受影响。
我见过一些内部PKI系统,图省事直接用根证书签发所有服务证书,结果某次根证书过期,所有服务全部中断……教训就是一定要用中间证书做隔离层。
| 位置 | 私钥保护要求 | 有效期 | 典型签发对象 |
|---|---|---|---|
| 根证书 | 离线、HSM、多人控制 | 10-20年 | 中间证书 |
| 中间证书 | 在线但密钥保护 | 5-10年 | 终端证书 |
| 终端证书 | 服务器上,需定期轮换 | 1年或90天 | 域名/IP/用户 |
注意:你的HTTPS服务器配置里要确保发送完整的证书链(终端+中间),不要发送根证书。根证书是浏览器本地有的,你多发送反而可能因为路径构建问题导致校验失败。我以前就遇到过某CDN自动补了根证书导致部分客户端不认的奇葩案例。
3.4 公钥基础设施(PKI)——整个信任体系
证书、CA、证书链、吊销列表……这些东西合在一起就是PKI。它解决的核心问题:在一个不安全的网络上,如何安全地分配公钥?
PKI的四大组件:
- CA(证书颁发机构):签发、吊销证书。
- RA(注册机构):验证申请者的身份,跟CA解耦。(很多情况下RA和CA合并了)
- 证书库:存放已签发证书的地方,通常是公开可访问的目录。
- CRL/OCSP:证书吊销列表或在线状态协议,用于检查证书是否被提前作废。
我在做内部微服务HTTPS改造时,自己搭了一套小型PKI(用cfssl)。跑起来后才发现,真正麻烦的不是签发证书,而是吊销、续期、自动化管理。后来我们上了cert-manager配合ACME协议,才算解脱。
3.5 实操:如何查看一条证书链?
以Nginx配置为例,你通常会有三个文件:
# 终端证书
server.crt
# 中间证书(如果CA给了)
intermediate.crt
# 私钥
server.key
正确的证书链文件应该合并为:
cat server.crt intermediate.crt > fullchain.pem
然后我在Nginx里这么配:
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/server.key;
验证链是否完整,用下面这个命令:
echo | openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null | grep -A2 "subject="
如果输出里没有中间证书的subject,说明你漏发了。我曾经就因为漏发中间证书,导致某个金融客户的内网监控一直报警,排查到凌晨才找到问题。
一键调试: openssl s_client -connect example.com:443 -CAfile /etc/ssl/certs/ca-certificates.crt 可以模拟浏览器验证整个链。验证返回 Verify return code: 0 (ok) 才算过关。
3.6 关于证书吊销,你可能没那么重视
证书万一私钥泄露怎么办?CA会将证书加入CRL(证书吊销列表)。浏览器遇到证书时会去检查CRL或通过OCSP实时查询。但这有个性能问题——OCSP必须在线,如果CA的OCSP服务器挂了,你的网站可能加载变慢甚至被某些客户端拒绝。
我建议大型服务开启OCSP Stapling,让Nginx缓存OCSP响应并同时发给客户端,避免客户端自己去查。
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
ssl_trusted_certificate /etc/nginx/certs/ca-chain.pem;
这个配置我遇到过一个坑:resolver设置不当导致nginx解析失败,OCSP Stapling直接失效。后来我换成本地dnsmasq才稳定。