“ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed” 后面跟着的原因决定了该怎么修,但教程里的万能办法常常是“关掉验证”。这篇在本机用 openssl 造出自签名、私有 CA、过期、主机名错、缺中间证书、缺 AKI 几种证书,逐个看 Python 的报错和每种的正确修法。
ssl.get_default_verify_paths(): cafile=/opt/homebrew/etc/openssl@4/cert.pem
capath=/opt/homebrew/etc/openssl@4/certs 环境变量名: SSL_CERT_FILE、SSL_CERT_DIR
cafile 文件存在: True;create_default_context() 加载的根证书数 = 188
设置 SSL_CERT_FILE=<只有一个根证书的文件> 之后(子进程里): 加载的根证书数 = 1默认从 OpenSSL 指定位置的 cert.pem 加载根证书,可由 SSL_CERT_FILE / SSL_CERT_DIR 覆盖。
自签名证书(selfsig):
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1144)
verify_code=18 verify_message='self-signed certificate'
私有 CA 签发、客户端不认识这个 CA:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1144)
verify_code=20 verify_message='unable to get local issuer certificate'
连接的名字不在证书里(证书只给 other.test,却用 localhost 去连):
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1144)
verify_code=20 verify_message='unable to get local issuer certificate'
用 IP 地址 127.0.0.1 去连,证书的 SAN 里有 IP:127.0.0.1(对照,仍因 CA 不认识而失败):
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1144)
verify_code=20 verify_message='unable to get local issuer certificate'
create_default_context().verify_flags 里带 VERIFY_X509_STRICT: True
再换成“客户端信任了根证书”后的几种失败(这时只剩证书本身的问题):
证书过期:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1144)
verify_code=10 verify_message='certificate has expired'
主机名对不上(证书只给 other.test):
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'localhost'. (_ssl.c:1144)
verify_code=62 verify_message="Hostname mismatch, certificate is not valid for 'localhost'."
服务器只发了叶子证书、没发中间证书(根已信任):
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1144)
verify_code=20 verify_message='unable to get local issuer certificate'
手工签的证书缺少 Authority Key Identifier 扩展:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Missing Authority Key Identifier (_ssl.c:1144)
verify_code=85 verify_message='Missing Authority Key Identifier'
对照:发了完整链: 成功,收到 'hello'
对照:用 IP 连、证书 SAN 里有这个 IP: 成功,收到 'hello'用 verify_code 和 verify_message 区分:自签名、找不到签发者、过期、主机名不对、缺 AKI。unable to get local issuer certificate 既可能是 CA 不被信任,也可能是服务器没发中间证书。
私有 CA:cafile=根证书 -> 成功 缺 Authority Key Identifier:cafile=根证书 -> 失败:Missing Authority Key Identifier 自签名:cafile=它自己的证书 -> 成功 只发叶子证书:cafile=根证书 -> 失败:unable to get local issuer certificate 只发叶子证书:cafile=根证书 + 把中间证书也加进信任 -> 成功 过期的证书:cafile=根证书 -> 失败:certificate has expired 主机名不对:cafile=根证书 -> 失败:Hostname mismatch, certificate is not valid for 'localhost'. 缺 Authority Key Identifier + 去掉 VERIFY_X509_STRICT 标志 -> 成功(能连,但这是在放宽校验,正确做法是重新签发带 AKI 的证书) 不改代码,只设环境变量 SSL_CERT_FILE=<根证书> -> 成功 b'hello' 不设环境变量 -> ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:114
CA 不认识就加载 CA;自签名就信任它自己;缺中间证书就让服务器发完整链;过期和主机名不对要换证书或用对的名字;缺 AKI 要重新签发。
关闭校验去连一个“冒充 localhost”的服务器:连接成功,收到 'hello';对方证书 subject=CN=attacker.example; issuer=CN=attacker.example
同一个冒充者,开着校验(信任了我们的根证书)-> 失败:self-signed certificate
CERT_NONE 下 getpeercert() 返回 {},要看对方证书得用 getpeercert(binary_form=True)(拿到 791 字节的 DER)CERT_NONE 会接受冒充者的证书,等于不再检查对方是谁。
urllib.request.urlopen(url) -> URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1144)>
e.reason 的类型 = SSLCertVerificationError
urlopen(url, context=信任自签名证书的上下文) -> b'hi'异常被包在 URLError 里,真正的 SSLCertVerificationError 在 .reason 上;可用 context= 指定信任。