1. 后量子密码部署排障:TLS 1.3 混合密钥交换握手失败定位
故障现象与定位思路
在 Nginx + OpenSSL 3.x 上启用 X25519MLKEM768 混合密钥交换后,部分客户端握手失败。要快速定位根因,需要把"客户端能力、服务端组配置、证书链"三条线索分开验证,而不是一上来就改配置。核心判断依据来自两处:抓包中的 ClientHello/ServerHello 组协商结果,以及 openssl s_client 的握手输出。
第一步:用 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 部署,删除混合组只是掩盖问题;应先确认客户端能力,再决定是升级客户端还是保留回退组。