错误信息
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate
也可能输出为
- [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate
Python 会附加 C 源代码位置,例如“(_ssl.c:1082)”。不要与“self-signed certificate in certificate chain”混淆,后者表示 CA 证书链终止于不受信任的根证书。OpenSSL 1.1.1 将其拼写为“self signed”。
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT (18)
含义
服务器只提供了一张自签名证书(其颁发者就是它自己),而这张证书不在当前上下文受信任的 CA 集合中。没有可以查找的 CA,所以 OpenSSL 在深度 0 处就验证失败了。这常见于开发服务器、设备和新安装的系统;解决办法是有意识地信任这张证书,或者替换它。
原因
使用自动生成证书的开发或测试服务器
常见使用临时证书启动的本地服务器(Flask 的 ssl_context="adhoc"、使用生成密钥的 uvicorn、docker-compose 服务栈)会提供一张新生成的、不受任何一方信任的自签名证书。
使用出厂证书的设备或产品
常见NAS、路由器、Proxmox、Elasticsearch、Jenkins 以及许多厂商设备,在管理员安装正式证书之前,使用的都是自签名证书。
代理返回了内置的默认证书
偶见Ingress 控制器和反向代理会用自签名的默认证书响应未知的主机名;这时真正的问题在于路由或 SNI。
修复方法
- 1.
让这个客户端只信任这一张证书
把服务器自己的证书作为 cafile 加载。主机名和日期检查仍然开启,并且只接受这一张证书。
python import ssl, urllib.request # Trust exactly this certificate (fetched and fingerprint-checked out of band) ctx = ssl.create_default_context(cafile="api.example.com.pem") urllib.request.urlopen("https://api.example.com:8443/", context=ctx) - 2.
获取证书并核对指纹
在信任该文件之前,把 SHA-256 指纹与设备控制台上显示的值或管理员提供的值进行比对。
bash openssl s_client -connect api.example.com:8443 -servername api.example.com </dev/null 2>/dev/null \ | openssl x509 -out api.example.com.pem openssl x509 -in api.example.com.pem -noout -fingerprint -sha256 # compare with the server's console - 3.
开发环境:改用本地 CA
有了本地 CA,你只需信任一个根证书,就能为每台开发主机签发正确签名的证书,任何客户端都不需要为单张证书设置例外。
bash # Development: one local CA, many certificates (mkcert installs the CA for you) mkcert -install mkcert api.example.test export SSL_CERT_FILE="$(mkcert -CAROOT)/rootCA.pem" - 4.
临时诊断:在测试中关闭验证
check_hostname = False 和 CERT_NONE 会关闭所有检查并接受任何证书,包括攻击者提供的证书。只用它来确认问题仅在于信任,确认后就删掉。
python # Temporary diagnostic only: proves trust is the sole problem. Never ship this. import ssl ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE