bigywater.tech
网络协议 · 91

X.509 证书与信任链:怎样验证一张证书

TLS 的安全最终落在一张证书上:你怎么知道对面真的是 example.com?这篇取下 example.com 的证书链,看每一张证书的字段和扩展,亲自用上一级公钥验证每个签名,找到它在信任库里的根,再用几个故意配错的站点看各种验证失败,最后看 SNI 与主机名验证怎么配合。

90 天
example.com 叶子证书的有效期长度(根证书为 8759 天,约 24 年)
10 / 62 / 18 / 19
expired、wrong.host、self-signed、untrusted-root 四个测试站在 Python 里的验证错误码
revoked 握手成功
Python 默认不检查吊销:revoked.badssl.com 的证书已被吊销,却没有报错

证书链

实测 · 本机执行输出
  TLSv1.3,TLS_AES_256_GCM_SHA384;ssl.get_verified_chain() 返回 4 张证书(从叶子到根方向)
  [0] 主体 CN=example.com
      签发者 C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3;公钥 EC secp256r1;签名算法 ecdsa-with-SHA256;DER 1002 字节
      有效期 2026-09-26 ~ 2026-12-25(共 90 天;现在起还剩 71 天);序列号 0x1eee6aabb521d5e14f…
  [1] 主体 C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
      签发者 C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2;公钥 EC secp256r1;签名算法 ecdsa-with-SHA384;DER 742 字节
      有效期 2025-05-29 ~ 2035-05-27(共 3649 天;现在起还剩 3146 天);序列号 0x31eee88afb87cd9ef8…
  [2] 主体 C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
      签发者 C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022;公钥 EC secp384r1;签名算法 ecdsa-with-SHA384;DER 824 字节
      有效期 2022-10-21 ~ 2037-10-17(共 5474 天;现在起还剩 4020 天);序列号 0x604d8af8d00ba8748b…
  [3] 主体 C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022
      签发者 C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022;公钥 EC secp384r1;签名算法 ecdsa-with-SHA384;DER 574 字节
      有效期 2022-08-25 ~ 2046-08-19(共 8759 天;现在起还剩 7248 天);序列号 0x1403f5abfb378b1740…
  整条链 DER 总计 3142 字节(这是握手时服务器要发的证书数据的量级)

叶子由中间 CA 签发,中间 CA 由根签发;越靠近叶子有效期越短(90 天 / 10 年 / 15 年 / 24 年)。

扩展

实测 · 本机执行输出
  basicConstraints           critical=True  CA = False
  authorityKeyIdentifier     critical=False  AuthorityKeyIdentifier
  authorityInfoAccess        critical=False  [('caIssuers', 'http://i.cf-i.ssl.com/Cloudflare-TLS-I-E3.cer'), ('OCSP', 'http://o.cf-i.ssl.com')]
  subjectAltName             critical=False  2 个名字:['example.com', '*.example.com']
  certificatePolicies        critical=False  2 项
  extendedKeyUsage           critical=False  ['serverAuth']
  cRLDistributionPoints      critical=False  ['http://c.cf-i.ssl.com/ae801ed1c55bb579d79208b0d772acfb8cc3a208.crl']
  keyUsage                   critical=True  digital_signature=True,key_encipherment=False
  Unknown OID                critical=False  UnrecognizedExtension
  signedCertificateTimestampList critical=False  2 项

CA = False、serverAuth、SAN、OCSP / CRL 地址、SCT 等扩展决定证书的用途和附属信息。

验证签名

实测 · 本机执行输出
  [0] 用 [1] 的公钥验证签名:True
  [1] 用 [2] 的公钥验证签名:True
  [2] 用 [3] 的公钥验证签名:True
  链的最后一张 [3]:主体 == 签发者?True(= 自签名根)
  本机 Python 的信任库里有 192 张根 / 受信 CA 证书
  链的最后一张的签发者 "C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022" 在信任库里:找到 1 张同名的;用它验证最后一张的签名:True
  信任库里 EC 根 60 张,RSA 根 132 张;最早到期的 2026-11-27,最晚到期的 2054-12-09

每张证书用上一级公钥验签;根在信任库里找到并验签通过。

验证失败

实测 · 本机执行输出
  expired.badssl.com          (证书过期):验证失败 错误码 10:certificate has expired
  wrong.host.badssl.com       (证书是给别的主机名签的):验证失败 错误码 62:Hostname mismatch, certificate is not valid for 'wrong.host.badssl.com'.
  self-signed.badssl.com      (自签名证书):验证失败 错误码 18:self-signed certificate
  untrusted-root.badssl.com   (根不在信任库里):验证失败 错误码 19:self-signed certificate in certificate chain
  revoked.badssl.com          (证书已被吊销):握手成功(TLSv1.2)——没有报错
  注意 revoked.badssl.com:证书其实已被吊销,Python 的默认设置却让握手成功——默认不检查吊销(CRL / OCSP),这是常见的盲区

过期 10、主机名 62、自签名 18、未知根 19;吊销默认不检查。

SNI 与主机名

实测 · 本机执行输出
  SNI = example.com           → 默认验证:通过;不验证时服务器给的证书 SAN:['example.com', '*.example.com']
  SNI = www.example.com       → 默认验证:通过;不验证时服务器给的证书 SAN:['example.com', '*.example.com']
  SNI = example.org           → 默认验证:通过;不验证时服务器给的证书 SAN:['example.org', '*.example.org']
  SNI = totally-wrong.invalid → 默认验证:握手失败:SSLError [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] ssl/tls alert handshake;不验证时服务器给的证书 SAN:握手失败:SSLError [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] ssl/tls alert handshake
  完全不发 SNI:握手失败 SSLError [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] ssl/tls alert handshake
  验证由两部分组成:(1)证书链到受信任的根;(2)证书的 SAN 里有我要连的名字(通配符 *.example.com 匹配 www.example.com)。关掉第二项(check_hostname=False)只会得到“链是好的”,而不知道是不是对的站点

SNI 决定给哪张证书,SAN 里要有要连的名字;不认识的 SNI 直接 handshake_failure。

分步演示

X.509 与信任链
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。对 example.com:443 与 badssl.com 的真实连接;证书解析和手动验签用 Python 的 cryptography 库;信任库是 Python 3.14 默认上下文加载的 192 张;revoked.badssl.com 只是 Python 默认设置下的结果,浏览器会做吊销检查
    • 没有验证:OCSP / CRL 的实际查询与 OCSP stapling;证书透明度(SCT)的验证;路径长度和名字约束;交叉签名;证书的吊销在浏览器里的处理(CRLite 等);证书固定;线上服务器实际发送了几张证书(这里只看到 Python 构造的已验证链);ECH
    京ICP备2026067502号-1京公网安备11010502063487号