1. 后量子密码部署排障:TLS 1.3 混合密钥交换握手失败定位

故障现象与定位思路

在 Nginx + OpenSSL 3.x 上启用 X25519MLKEM768 混合密钥交换后,部分客户端握手失败。要快速定位根因,需要把"客户端能力、服务端组配置、证书链"三条线索分开验证,而不是一上来就改配置。核心判断依据来自两处:抓包中的 ClientHello/ServerHello 组协商结果,以及 openssl s_client 的握手输出。

TLS 1.3 混合密钥交换握手失败定位路径 客户端 ClientHello supported_groups Nginx / OpenSSL 3.x ssl_ecdh_curve X25519MLKEM768 ① ClientHello(含组列表) ② ServerHello(选定组 / 或 alert) 分支 A:客户端不支持 ClientHello 组列表无 X25519MLKEM768 → 服务端回 handshake_failure 或降级到其它组 分支 B:组配置错误 ssl_ecdh_curve 拼写/顺序 或 OpenSSL 版本不支持该组 → 无共享组,握手失败 s_client 报 no suitable key share 分支 C:证书链问题 组协商成功但证书校验失败 → verify error / unknown CA 与密钥交换无关,需查链 openssl verify 定位 诊断命令 openssl s_client -connect host:443 -groups X25519MLKEM768 -tls1_3 tcpdump -i eth0 -w hs.pcap 'tcp port 443' → Wireshark 看 supported_groups / key_share 修复方向 A:客户端升级或保留 X25519 回退组;B:修正 ssl_ecdh_curve 并确认 OpenSSL 支持;C:补全中间证书、修正链顺序

第一步:用 s_client 复现并读取握手输出

先固定客户端视角,强制只提供混合组,观察服务端是否接受:

openssl s_client -connect pq.example.com:443 \
  -tls1_3 -groups X25519MLKEM768 -servername pq.example.com -brief

关键判读:

  • 输出 Negotiated TLS1.3 group: X25519MLKEM768 → 组协商成功,问题不在密钥交换,转查证书链(分支 C)。
  • 输出 no suitable key share 或 no suitable signature algorithm → 组不匹配(分支 A/B)。
  • 输出 verify error:num=20 unable to get local issuer certificate → 证书链缺失(分支 C)。

再反向验证:去掉 -groups 参数,让客户端用默认组列表,若握手成功,说明服务端配置本身可用,失败仅发生在强制混合组时,指向客户端能力或组配置。

第二步:抓包确认 ClientHello 的组列表

用 tcpdump 抓取握手包,在 Wireshark 中展开 TLS → Handshake Protocol: Client Hello → supported_groups:

  • 若列表里没有 X25519MLKEM768(0x11EC),而服务端 ssl_ecdh_curve 只配了它 → 分支 A,客户端不支持。
  • 若列表里有该组,但 ServerHello 未选中它 → 检查服务端配置是否拼写错误、OpenSSL 版本是否支持(分支 B)。
  • 若 ServerHello 已选中该组,但随后出现 alert certificate_unknown → 分支 C。

第三步:核对服务端组配置与 OpenSSL 支持

# 查看 OpenSSL 是否支持该混合组
openssl list -groups | grep -i mlkem

# Nginx 配置片段
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:P-256;

注意两点:ssl_ecdh_curve 中组名必须与 OpenSSL 输出完全一致(大小写敏感);保留 X25519 作为回退组,避免不支持混合组的客户端直接失败。修改后 nginx -t && nginx -s reload。

第四步:证书链独立验证

若组协商已成功,用以下命令单独验证链,排除证书干扰:

openssl s_client -connect pq.example.com:443 -servername pq.example.com -showcerts </dev/null \
  | openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt

返回 OK 说明链完整;返回 unable to get local issuer certificate 则需在 Nginx 的 ssl_certificate 中按"终端证书 → 中间证书"顺序拼接完整链。

定位顺序建议:先看 s_client 是否协商成功(区分密钥交换 vs 证书),再看抓包 supported_groups(区分客户端能力 vs 服务端配置),最后单独验证证书链。三步互斥,避免同时改多处配置导致无法归因。
不要为了"让握手通过"而直接删除 X25519MLKEM768。若目标是验证 PQC 部署,删除混合组只是掩盖问题;应先确认客户端能力,再决定是升级客户端还是保留回退组。