问题描述
使用 Python requests 库访问一个 HTTPS URL 会报错,提示自签名错误。但是在浏览器中访问这个 URL 就没问题,在私人电脑上也没问题。
Sample code:
import requests
import certifi
print(certifi.where())
response = requests.get('https://chroma-onnx-models.s3.amazonaws.com/all-MiniLM-L6-v2/onnx.tar.gz', verify=certifi.where())
报错如下:
......
requests.exceptions.SSLError: HTTPSConnectionPool(host='chroma-onnx-models.s3.amazonaws.com', port=443): Max retries exceeded with url: /all-MiniLM-L6-v2/onnx.tar.gz (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1006)')))
原因分析
根据Copilot的回答,我理解的根本原因是代理服务器拦截和不同客户端使用不同的证书储存位置。这意味着不同场景中建立的证书链是不一样的,有的有效,有的无效。
代理服务器会修改HTTPS服务器返回的原证书,生成一个新证书。这个新证书指向代理服务器,并最终指向公司的根证书。
这个根证书是自签名的,存在于系统自带的证书管理器,但不在Python requests库使用的证书管理器中。所以Python requests库会提示“自签名错误”。
所以,解决方案是添加你公司的根证书(也就是requests lib提示的那个自签名证书)到 Python 正在用的 PEM 文件,目的是使新证书链有效。
可以尝试pip install pip_system_certs
,或者手动将公司的ROOT CA添加到...Libsite-packagescertificacert.pem
。
拓展阅读 (From Copilot)
详细解释一下,为什么“在公司网络中,代理服务器可能会拦截和检查 HTTPS 流量,这可能会导致 SSL 证书验证失败。”
在公司网络中,代理服务器可能会拦截和检查 HTTPS 流量,这种行为通常被称为“中间人攻击”(Man-in-the-Middle Attack, MITM),但在企业环境中,这种技术被合法地用于监控和保护网络安全。以下是详细解释:
1. 代理服务器的工作原理
代理服务器充当客户端(如你的计算机)和目标服务器(如你访问的网站)之间的中介。当你在公司网络中访问 HTTPS 网站时,请求首先发送到代理服务器,然后代理服务器再将请求转发给目标服务器。
2. SSL/TLS 加密
HTTPS 使用 SSL/TLS 协议来加密数据传输,确保数据在传输过程中不被窃取或篡改。正常情况下,客户端和目标服务器之间建立一个安全的加密通道。
3. 代理服务器的拦截和检查
为了检查 HTTPS 流量,代理服务器会在客户端和目标服务器之间插入自己。具体步骤如下:
代理服务器生成自签名证书:代理服务器会为目标网站生成一个自签名证书(ZZZ: 不一定是自签名,更可能是中间证书,指向公司的自签名的根证书),并将其发送给客户端。
客户端信任代理服务器的证书:如果客户端信任代理服务器的证书(通常通过在客户端设备上预装代理服务器的根证书),客户端会接受这个自签名证书,并与代理服务器建立加密连接。
代理服务器与目标服务器建立连接:代理服务器再与目标服务器建立一个独立的加密连接。
4. 证书验证失败的原因
由于代理服务器使用的是自签名证书,而不是目标服务器的真实证书,客户端在验证证书时可能会失败,具体原因包括:
缺少信任:如果客户端没有预装代理服务器的根证书,客户端会认为代理服务器的证书不可信,从而导致 SSL 证书验证失败。
证书链不完整:代理服务器生成的自签名证书可能不包含完整的证书链,导致验证失败。
证书不匹配:客户端期望的证书与代理服务器提供的证书不匹配,导致验证失败。
解决方法
安装代理服务器的根证书:在客户端设备上安装并信任代理服务器的根证书。
配置代理设置:确保在使用 requests
库时正确配置代理设置,并指定正确的证书路径。
代理服务器会为目标服务器生成一个新的证书签名请求
是的,你理解得很正确。代理服务器在拦截和检查 HTTPS 流量时,会为目标服务器生成一个新的证书签名请求(CSR),然后使用自己的证书颁发机构(CA)对其进行签发。具体步骤如下:
代理服务器的操作步骤
生成 CSR:
当客户端请求一个 HTTPS 网站时,代理服务器会生成一个新的 CSR,这个 CSR 包含了目标服务器的域名信息和公钥。
签发证书:
代理服务器使用自己的中间证书或根证书对生成的 CSR 进行签发,生成一个新的服务器证书。
返回给客户端:
代理服务器将签署好的新证书返回给客户端。这个新证书的签发人是代理服务器的 CA,而不是目标服务器的原始 CA。
证书链的变化
原始证书链:
服务器A的证书 -> 中间证书 -> 根证书(受信任的CA)
代理服务器修改后的证书链:
代理服务器生成的证书 -> 代理服务器的中间证书(如果有) -> 代理服务器的根证书
客户端验证
客户端会验证代理服务器返回的证书链。如果客户端信任代理服务器的根证书或中间证书,SSL验证就会成功。
通过这种方式,代理服务器可以拦截和检查 HTTPS 流量,同时保持客户端与目标服务器之间的加密连接。如果你有更多问题或需要进一步的帮助,请告诉我!