Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
故障排查阅读约 11 分钟

SSLCertVerificationError:诊断 Python 和 API 客户端中的 TLS 证书验证失败

一份实用指南,帮助诊断和修复 SSLCertVerificationError,涵盖证书过期、CA 证书包缺失、主机名不匹配以及企业代理等问题。

TL;DR

当 TLS 客户端无法根据其受信任的 CA 存储、请求的主机名或当前有效期验证服务器证书时,就会引发 SSLCertVerificationError。错误消息通常会指明具体的失败原因,例如证书过期、自签名证书或无法获取本地颁发者证书。本文解释了验证机制,逐步演示了使用 openssl 和 Python 的可复现诊断流程,并展示了如何在不关闭验证的情况下修复每一类故障。文章还涵盖了自定义 CA 证书包的权衡、代理拦截以及何时应升级至服务器运营商。

SSLCertVerificationError 的真正含义

ssl.SSLCertVerificationError 是 Python 中因证书验证导致 TLS 握手失败时抛出的异常。客户端已连接到服务器,收到了其证书链,但因一项或多项检查未通过而拒绝了它。该异常是 ssl.SSLError 的子类,因此捕获 SSLError 的代码也会捕获它。

CERTIFICATE_VERIFY_FAILED 之后的错误文本才是关键部分。常见的后缀包括 certificate has expired、self-signed certificate、unable to get local issuer certificate、hostname mismatch 和 certificate is not yet valid。每个后缀指向不同的修复方法,因此在更改任何代码之前请先阅读后缀。

此错误与连接被拒绝或超时不同。如果 TCP 连接从未完成,你会看到 ConnectionError 或 TimeoutError。验证错误意味着网络路径工作正常,而 TLS 层做出了信任决策。

  • 已过期或尚未生效:证书的有效期窗口不包含当前时间。
  • 自签名或不受信任的颁发者:证书链终止于客户端不信任的证书。
  • 无法获取本地颁发者证书:服务器未发送中间证书,或客户端的 CA 证书包不完整。
  • 主机名不匹配:证书的主题备用名称不包含你请求的主机。

诊断工作流的假设与前提条件

以下示例假设使用 Python 3.8 或更高版本、requests 库以及 openssl 命令行工具。它们还假设你可以通过端口 443 访问目标主机。如果你在容器或 CI 运行器中工作,请在调试证书之前确认允许出站 TLS。

对于区块链 RPC 端点,同样的验证规则适用。像 多链 RPC 端点指南 中描述的托管端点应提供公开受信任的证书。如果不是,应将其视为检查端点配置的信号,而不是关闭验证。

TLS 验证的心智模型

证书验证包含四项独立的检查。首先,客户端从服务器的叶子证书开始,通过任何中间证书构建一条链,直到其信任存储中的根证书。其次,检查链中的每个证书是否在其有效期内。第三,检查叶子证书的主题备用名称是否与连接中使用的主机名匹配。第四,检查叶子证书是否被授权用于服务器身份验证。

任何一项检查失败都会产生 SSLCertVerificationError。错误消息会指出哪项检查失败,但根本原因可能在任何一方。证书过期是服务器问题。CA 证书包过时是客户端问题。终止 TLS 的企业代理是网络策略问题。区分这些是诊断的核心。

使用最小 Python 客户端复现错误

从一个发起一次 HTTPS 请求并打印完整异常的小脚本开始。这将失败与应用程序逻辑和重试包装器隔离开来。

import requests
from requests.exceptions import SSLError

url = "https://RPC_HOST"  # replace with the host you are debugging

try:
    response = requests.get(url, timeout=10)
    print("status", response.status_code)
except SSLError as exc:
    print(type(exc).__name__)
    print(exc)

使用 openssl 检查服务器证书链

openssl s_client 命令准确显示服务器发送的内容。使用 -servername 运行以确保正确设置 SNI,并使用 -showcerts 打印完整链。

openssl s_client -connect RPC_HOST:443 -servername RPC_HOST -showcerts </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

解读验证结果

上一条命令的输出告诉你哪项检查可能失败。将 notBefore 和 notAfter 日期与当前 UTC 时间进行比较。将 subjectAltName 条目与你请求的主机名进行比较。将颁发者与客户端信任存储中的根证书进行比较。

下表是一个流程,而非一组测量结果。请从你自己的环境中填入观察到的值。

  • | 检查项 | 命令或字段 | 通过时的表现 | 失败时的含义 |
  • | 有效期 | -dates 输出 | 当前时间在 notBefore 和 notAfter 之间 | 服务器证书过期或时钟偏差 |
  • | 主机名 | subjectAltName | 请求的主机出现在列表中 | 部署了错误的证书或使用了错误的主机名 |
  • | 证书链 | -showcerts 输出 | 叶子证书加中间证书,终止于已知根证书 | 缺少中间证书或根证书不受信任 |
  • | 信任存储 | Python 中的 certifi.where() | 路径存在且文件较新 | CA 证书包过时或缺失 |

修复客户端 CA 证书包问题

如果错误是 unable to get local issuer certificate 且服务器链看起来完整,客户端的信任存储可能是罪魁祸首。Python 的 requests 默认使用 certifi,但某些环境会用 REQUESTS_CA_BUNDLE 或 SSL_CERT_FILE 覆盖它。在替换任何内容之前,请检查实际使用的是哪个证书包。

在 macOS 上,python.org 安装程序附带一个 Install Certificates.command 脚本,用于填充默认证书包。在 Linux 容器中,ca-certificates 包提供系统存储。在这两种情况下,更新证书包比固定单个服务器证书更安全。

import certifi
import os

print("certifi bundle:", certifi.where())
print("REQUESTS_CA_BUNDLE:", os.environ.get("REQUESTS_CA_BUNDLE"))
print("SSL_CERT_FILE:", os.environ.get("SSL_CERT_FILE"))

处理企业代理和自定义根证书

许多企业网络在代理处终止 TLS,并使用内部根证书重新签名流量。在这种情况下,服务器证书是有效的,但客户端不信任代理的根证书。正确的修复方法是将企业根证书添加到客户端的信任存储中,而不是关闭验证。

如果你必须为某个应用程序使用自定义证书包,请将 REQUESTS_CA_BUNDLE 设置为一个包含公共根证书和企业根证书的组合文件。将该文件置于配置管理之下,以免发生偏移。对于与区块链 RPC 端点通信的基础设施,请查看 专用节点 和 API 服务 选项,了解私有网络路径是否可以完全避免代理拦截。

当服务器端出现问题时

如果证书已过期、是自签名或缺少中间证书,修复责任在于服务器运营商。你可以通过检查 openssl s_client 是否仅显示叶子证书来确认缺少中间证书。浏览器有时通过缓存中间证书来隐藏此问题,这就是为什么 Python 客户端可能在浏览器成功的地方失败。

对于 RPC 提供商,证书问题是运营信号。托管端点应监控过期和链完整性。如果你正在评估提供商,RPC 端点指南 和 RPC 定价页面 描述了生产级服务的预期。

为什么关闭验证是最后的手段

在 requests 中设置 verify=False 会抑制异常,但也会移除对服务器的身份验证。网络路径上的攻击者随后可以出示任何证书并读取或修改流量。ssl._create_unverified_context() 和 curl -k 也是如此。

如果你需要在开发期间测试自签名端点,请使用 verify="/path/to/cert.pem" 传递特定的证书文件,而不是关闭验证。这样可以使信任决策明确且仅限于一个主机。

import requests

# Development only: trust one specific certificate file.
response = requests.get(
    "https://RPC_HOST",
    timeout=10,
    verify="/path/to/server-cert.pem",
)
print(response.status_code)

验证修复并监控复发

更改 CA 证书包或服务器证书后,重新运行最小客户端和 openssl s_client 命令。成功的请求返回 HTTP 状态码且没有 SSLError。成功的链检查显示叶子证书、任何中间证书以及与你信任存储中根证书匹配的颁发者。

证书过期是可预测的,因此请监控它。一个简单的检查是解析 notAfter 日期,并在其距今 30 天内时发出警报。对于运行许多端点的团队,请集中此检查,而不是依赖个别开发者注意到。网络目录 列出了 OnFinality 支持的链,这是清点的有用起点。

端点和提供商决策的后续步骤

一旦验证正常工作,剩下的问题是使用哪个端点。这是提供商和网络选择问题,而不是 TLS 问题。使用 OnFinality Learn 库 获取协议和集成指导,并咨询 RPC Assistant 获取关于端点和网络的简短直接建议。

如果你正在针对特定链进行构建,请从网络指南开始,然后使用上述流程确认端点的证书链。验证应该是无聊且自动的;如果不是,请在增加应用程序复杂性之前修复信任路径。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始