Skip to content

Lab:TLS 1.2 ECDHE 握手与 CA 信任链验证 ​

回到专题页:TLS 1.2 / ECDHE / CA 信任链所属模块:00 基础知识(Foundations)

语言无关实验,无需 Android / Flutter / Web 宿主工程。两条路径任选:

  • Part A(必做,零依赖) :node ecdhe-forward-secrecy.mjs 离线演示 ECDHE 临时密钥协商与前向安全。
  • Part B(推荐,需 openssl) :本地起 TLS 服务 + openssl s_client -tls1_2 抓真实握手报文,识别 CipherSuite / ServerKeyExchange 临时密钥,并复现「漏发中间证书 → 校验失败」。

一句话目标 ​

亲手看到两件事:(1) ECDHE 的会话密钥只来自临时 EC 密钥,长期证书私钥泄露也不影响历史会话(前向安全);(2) 证书校验必须落到内置根 CA,漏发中间证书就会握手失败。


Part A — ECDHE 前向安全(Node,可跑) ​

bash
node ecdhe-forward-secrecy.mjs

脚本会:

  1. 双方各自生成临时 P-256 密钥(用完即弃,不落盘);
  2. 临时 ECDH 协商出会话密钥,验证两端结果一致;
  3. 说明长期证书密钥只签名临时参数、不接触会话密钥 —— 这就是前向安全;
  4. 从该会话密钥用 HKDF 派生 AES-256-GCM 密钥并做加解密往返,呼应「记录层用对称加密」。

预期输出(节选):

2) 临时 ECDH 协商会话密钥 ...
   => 两端一致: true
4) 派生 AES-256-GCM 密钥并加解密往返: OK

观察重点:第 2 步两端独立算出的密钥完全一致;第 3 步强调临时私钥握手后即弃,所以长期私钥泄露无法回溯解密历史流量。


Part B — 真实握手抓包与证书链(openssl,离线) ​

B.1 建一条 CA → 中间 → 叶子 证书链 ​

bash
# 在 lab 目录下建临时工作区
mkdir -p demo && cd demo

# 1) 根 CA(自签,模拟手机/系统内置的可信根)
openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt \
  -days 3650 -subj "/CN=Lab Root CA"

# 2) 中间 CA(由根签发)
openssl req -newkey rsa:2048 -nodes -keyout inter.key -out inter.csr \
  -subj "/CN=Lab Intermediate CA"
openssl x509 -req -in inter.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out inter.crt -days 1825

# 3) 叶子(站点证书,由中间签发,带 SAN)
openssl req -newkey rsa:2048 -nodes -keyout leaf.key -out leaf.csr \
  -subj "/CN=example.com"
printf "subjectAltName=DNS:example.com\n" > ext.cnf
openssl x509 -req -in leaf.csr -CA inter.crt -CAkey inter.key -CAcreateserial \
  -out leaf.crt -days 825 -extfile ext.cnf

B.2 起本地 TLS 服务(完整链:叶子 + 中间) ​

bash
# 服务端证书链 = 叶子 + 中间(顺序:leaf 在上,中间在下)
cat leaf.crt inter.crt > chain.pem
openssl s_server -accept 8443 -cert chain.pem -key leaf.key \
  -cert_chain inter.crt -tls1_2 -www

另开一个终端,用 s_client 抓握手(强制 TLS 1.2 + ECDHE):

bash
openssl s_client -connect localhost:8443 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256

观察重点(在 s_client 输出里找):

  • Cipher : ECDHE-RSA-AES128-GCM-SHA256 —— CipherSuite:ECDHE 负责密钥交换、RSA 负责证书签名、AES128-GCM 负责记录层加密。
  • Server Temp Key: ECDH, P-256 —— 这就是临时 EC 公钥(ServerKeyExchange 报文),每次握手都不同。
  • 证书块列出 example.com(叶子)→ Lab Intermediate CA → Lab Root CA(根),链完整。

B.3 复现「漏发中间证书 → 校验失败」 ​

bash
# 只发叶子,不发中间(很多「浏览器能连、App 连不上」就是这个坑)
openssl s_server -accept 8444 -cert leaf.crt -key leaf.key -tls1_2 -www
bash
# 用根 CA 作为信任锚去校验
openssl s_client -connect localhost:8444 -tls1_2 -CAfile ca.crt
# => verify error:num=21:unable to verify the first certificate

现象解释:客户端手里只有「叶子」和「内置根」,中间的跳断了,无法用根公钥直接验证叶子(叶子是中间签的)。补上中间证书(回到 B.2 的 chain.pem)即恢复 Verify return code: 0 (ok)。

B.4 移动端抓取提示(呼应专题页问题 3) ​

浏览器默认信任系统根池,安装用户 CA 即可 MITM;而 Android 7+ 默认不信任用户 CA、iOS 需装描述文件并手动信任,且 App 还要在网络安全性配置里显式信任用户 CA —— 所以同一代理在手机上常「只能看到加密隧道」。


对应专题页知识点 ​

本实验看到的现象专题页小节
ECDHE-* CipherSuite + Server Temp Key底层机制 · ECDHE 密钥交换
临时密钥每次不同、长期私钥只签名复习检查题 1(前向安全)
漏发中间 → num=21 校验失败复习检查题 2(信任链)
证书块 叶子←中间←根速记 · 信任链

常见问题 ​

  • s_client 连不上:确认 s_server 还在跑、端口未被占用;本实验全部 localhost,无需联网。
  • unknown option -cert_chain:说明是 LibreSSL(macOS 系统自带),换成完整 OpenSSL 3.x(本机 openssl version 显示 OpenSSL 3.6.2 即可正常用 -cert_chain);或直接用 B.2 的 chain.pem(已含叶子+中间)配 -cert chain.pem 即可,无需 -cert_chain。
  • Part A 报错:Node ≥ 15 才有 X509Certificate/webcrypto 派生 API;本机 Node 22 可直接跑。

站点构建时间:2026/8/24 23:43:17