3. 后量子密码部署排障:混合证书链中 PQC 证书与 RSA 中间证书顺序错误

故障现象与链结构判断

部署完成后,服务端在 TLS 握手中下发的证书链为:ML-DSA 终端证书 → RSA 中间证书,但根 CA(RSA 信任锚)未随链下发。部分客户端(尤其是只信任系统根库、不做 AIA 补链的客户端)报 unable to get local issuer certificate 或“证书链不完整”。核心问题有两层:链内顺序(终端证书必须在前,签发者依次在后)与信任锚可达性(链尾的 RSA 中间证书必须能由客户端本地根库中的根 CA 签发验证)。

正确链(自终端向根,签发关系逐级向上) ML-DSA 终端证书 leaf · 公钥算法 ML-DSA subject = server.example RSA 中间证书 intermediate · RSA-3072 issuer = Root CA RSA 根 CA(信任锚) root · 本地信任库 self-signed 签发 签发 故障链(顺序颠倒 + 信任锚缺失) RSA 中间证书 被错误放在链首 客户端先验它 → 找不到签发者 ML-DSA 终端证书 被放在链尾 客户端未把它当 leaf RSA 根 CA 未随链下发 客户端本地库若无此根 → 断链 顺序错 缺锚 openssl verify 判读要点 · 用 -untrusted chain.pem 传入中间证书,-CAfile root.pem 指定信任锚;顺序错时报 unable to get issuer certificate · 缺锚时报 unable to get local issuer certificate;链尾证书的 issuer 必须能在 CAfile 中找到 subject 匹配 · 修复后应输出 chain.pem: OK,且 openssl s_client -showcerts 中 leaf 位于第一段

定位过程:用 openssl verify 与链构建日志分离两类问题

先取服务端实际下发的链,再分别验证“顺序”和“信任锚”两个假设。以下命令在服务端证书目录执行,fullchain.pem 为当前下发链,root.pem 为根 CA。

# 1) 查看链中各证书的 subject / issuer,确认顺序
openssl crl2pkcs7 -nocrl -certfile fullchain.pem \
  | openssl pkcs7 -print_certs -noout

# 期望输出(正确顺序):
# subject=C = CN, O = Demo, CN = server.example        <- leaf (ML-DSA)
# issuer=C = CN, O = Demo, CN = Demo RSA Intermediate
# subject=C = CN, O = Demo, CN = Demo RSA Intermediate
# issuer=C = CN, O = Demo, CN = Demo Root CA

# 2) 按“链内顺序 + 信任锚”验证
openssl verify -CAfile root.pem -untrusted fullchain.pem leaf.pem
# 顺序错时典型输出:
#   error 20 at 0 depth lookup: unable to get local issuer certificate
# 缺锚时典型输出:
#   error 2 at 1 depth lookup: unable to get issuer certificate

若 crl2pkcs7 输出中第一段是 RSA 中间证书、第二段才是 ML-DSA 终端证书,即为顺序错误;若顺序正确但 verify 仍报 error 2,则检查 root.pem 是否真的是链尾中间证书的签发者(比对 subject/issuer 与 AKI/SKI)。

修复:重排链并显式配置信任锚

把终端证书放第一段、中间证书依次在后,根 CA 不放入下发链(由客户端本地信任库提供),并在服务端显式指定信任锚文件用于 OCSP/验证辅助。

# 重排:leaf 在前,intermediate 在后
cat leaf.pem intermediate.pem > fullchain.pem

# Nginx 配置:下发链只含 leaf + intermediate
ssl_certificate     /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/leaf.key;
ssl_trusted_certificate /etc/nginx/certs/root.pem;   # 仅用于验证/OCSP,不下发

# 重载并复核
nginx -t && nginx -s reload
openssl verify -CAfile root.pem -untrusted fullchain.pem leaf.pem
# 期望:leaf.pem: OK
若客户端仍报链不完整,用 openssl s_client -connect host:443 -showcerts 确认第一段证书的 subject 等于服务域名,且链尾 issuer 能在客户端根库中找到;必要时在服务端补发 AIA 或提供中间证书下载。

验证与判读

检查项命令/观察点通过判据
链内顺序pkcs7 -print_certs 首段 subject等于服务域名(leaf 在前)
签发关系逐段比对 issuer 与下一段 subject逐级匹配,无断点
信任锚verify -CAfile root.pem输出 OK
实际下发s_client -showcerts第一段为 ML-DSA leaf,链尾为 RSA 中间证书