接口 HTTPS 接入的证书链校验规范:四类失败成因与客户端分流处置

5 阅读4分钟

一、信任链结构

叶子证书(depth 0)绑域名,服务器必须下发;中间证书(depth 1)承接信任传递,必须随叶子一起下发;根证书(depth 2)预装在客户端信任库,通常不下发。链上缺任何一级,验证即断。

判定依据: 浏览器内置 AIA(Authority Information Access)补链机制,链不完整时会按证书内的地址自动下载中间证书后继续验证,且该动作不留日志;脚本、SDK、移动端网络层绝大多数不做补链,缺一级即失败。两条客户端路径的差异即「浏览器打得开、脚本打不开」的全部机制。

二、四类成因判别表

成因现象特征责任方
只下发了叶子证书诊断只看到 1 条链;浏览器正常、命令行客户端失败对端:拼接 fullchain 重新加载
链拼接顺序反了链有 2 条以上仍失败;s:/i: 关系不连续对端:叶子在前、中间在后
客户端信任库缺失换任何客户端都失败;精简镜像 /etc/ssl/certs 为空己方:更新 CA 包或显式指定路径
地址与证书域名不一致报 hostname mismatch 而非 issuer 类错误己方:改用证书覆盖域名或补 SAN

判定依据: 报 unable to get local issuer 优先怀疑链不全;报 hostname 优先怀疑地址写错;报 expired 才是时间问题。信任库缺根时链完整仍失败,只能己方补根或对端换公共 CA。

三、诊断步骤

步骤一,看链有几层: openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts,输出中 s:/i: 应有 2 组以上且首尾相接;只有 1 组即只下发了叶子证书。

步骤二,看验证结论: openssl s_client -connect api.example.com:443 -verify_return_error,Verify return code: 0 为通过;20/21 为链断裂信号。

步骤三,用生产同款客户端复验: curl -vI https://api.example.com/health --cacert /etc/ssl/certs/ca-certificates.crt。命令行失败而浏览器正常,定性完成。

四、错误分流实现

关闭校验(verify=False)会让中间人、过期、域名不符全部静默。规范做法是按错误类型分流,让每种失败给出可执行下一步:

import requests
from requests.exceptions import SSLError


def fetch(url: str, ca_bundle: str = "/etc/ssl/certs/ca-certificates.crt"):
    """按错误类型分流,不关闭校验。"""
    try:
        with requests.get(url, timeout=(3.05, 10), verify=ca_bundle) as resp:
            resp.raise_for_status()
            return resp.json()
    except SSLError as exc:
        text = str(exc)
        if "unable to get local issuer certificate" in text:
            raise RuntimeError("证书链不完整,先查对端是否下发中间证书") from exc
        if "certificate has expired" in text:
            raise RuntimeError("证书已过期,联系对端续期并确认生效") from exc
        if "hostname" in text.lower():
            raise RuntimeError("证书域名与调用地址不一致,检查 SAN") from exc
        if "self-signed" in text or "self signed" in text:
            raise RuntimeError("对端为自签证书,需显式指定 CA 文件") from exc
        raise

实现约束: 其一,verify 接受 CA 文件路径而非布尔值,自签场景传入独立 PEM 路径;其二,timeout 用元组分别设建连与读超时;其三,握手失败属确定性错误,不加重试,重试只用于连接重置类瞬时错误。

判定依据: 分流后每类错误对应唯一处置动作,且代码中不存在关闭校验的兜底路径。

五、续期生效清单

换证书后集中的第二类故障:证书正确、配置正确,生效链路有一段没跟上。

  • 网关监听器绑定的是证书 ID,控制台更新后未必自动同步——比对实际下发指纹与控制台指纹;
  • 加速层与反向代理证书独立配置,不同步表现为「部分地域正常、部分报错」;
  • 长连接池持有旧 TLS 会话,进程重启前持续复用——换证书后重启长连接服务,本质是清空旧会话。

六、实施检查清单

  • 链层数 ≥ 2 且 s:/i: 首尾相接
  • Verify return code: 0
  • fullchain 顺序为叶子 → 中间,无空行
  • 生产同款客户端实测通过,不以浏览器为准
  • 精简镜像 CA 包非空
  • 续期后比对实际下发指纹
  • 换证书后重启长连接服务
  • 无 verify=False 兜底路径
  • SSL 错误按四类分流告警

七、结论

证书问题的判断标准只有一条:用生产环境里真实的客户端去验。「浏览器打得开」在证书链问题上不构成证据,因为浏览器会替你补链,而你的业务客户端不会。

以上证书链校验与分流方案已在多个平台对接场景中验证,相关接口与数据服务方案如下。

相关参考: 跨境电商平台接口与数据服务 方案