Python urllib self-signed certificate in certificate chain
从上周开始, 本地一个 Python 的项目里面一只报下面的错:
requests.exceptions.SSLError: (MaxRetryError("HTTPSConnectionPool(host='huggingface.co', port=443): Max retries exceeded with url: /bert-base-uncased/resolve/main/tokenizer_config.json (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1006)')))"), '(Request ID: 6d1db4c8-5589-4dfd-a14f-b95e47b9864d)')
从出错栈看, 当连接任何 https 网站的时候, 都会报这个错. 即便使用 pip install
的时候, 一样报这个错. 所以, 是本地连接任何 SSL 站点时候, 验证证书出错了. 由于连接任何 https 网站都有问题, 所以是本地的问题, 而不是各个 https 网站的问题.
Google 了一圈, 也没有找到解决方案. 但是有人提供了 truststore
这个包, 可以把当前操作系统的所有证书替换 Python 环境的, 如下代码, 可以暂时解决这个问题.
import truststore
truststore.inject_into_ssl()
后来发现, 其实使用 requests 包是不会出错的, 正常连接并返回. 可是某些不使用 requests 包的请求会出错.
import requests
print(requests.get("http://www.google.com")) # 一切正常
网上继续查, 很多人用下面的代码去验证使用的哪些 CA:
import certifi; print(certifi.where())
import _ssl; print(_ssl.get_default_verify_paths())
因为 Python 默认使用 openssl 的CA, 通过上面的 _ssl.get_default_verify_paths()
可以获得. 安装 certifi
因为它带了权威的 CA. 所以一般在安装完 Python 之后, 会有个脚本把 openssl 的 CA 指向 certifi 再带的证书. 详情: https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error
我这个问题, 即便把证书指向 certifi, 或者使用 certifi 生成 SSLContext 依然不能解决. 虽然不能解决, 但是发现它其实有个共同点, 就是都是用了 Python 自带的 urllib
来连接. 使用 requests 包来连接的都没问题.
看 urllib 有关这个问题的讨论, 发现有些人使用了 VPN 或者 代理, 但是我机器或代码并没有设置代理.
最终发现公司在所有人电脑上装了 Netskope
, 这个东西相当于一个代理, 所有本地网络都会走它, 并且公司的软件中心有个 Netskope Certificates Apply Fix
.
安装完这个之后, 我只想说 TMD. 耽误我8个多小时.
后续
后面发现事情的真相.
通常情况下, 我在公司的电脑访问 https://www.google.com 的证书是由 WR2 (Google Trust Services) 签发的.
但是, 由于安装了 Netskope, 那么它拦截了所有出入的请求, 现在访问 https://www.google.com, Google 的证书是由公司的根证书的二级证书签发的, 所以本地不包含公司CA的 CA 列表是无法认识这个由公司签发的证书的.