Python requests使用自存服务器证书验证SSL连接失败问题
问题描述
先向https://google.com发起HTTP GET请求,获取对等证书并保存为PEM格式的cert.pem文件,随后再次连接该域名时将cert.pem指定为verify参数。代码如下:
import requests from cryptography import x509 from cryptography.hazmat.primitives import serialization with requests.get("https://google.com", stream=True) as response: cert = response.raw.connection.sock.getpeercert(binary_form =True) cert_der = x509.load_der_x509_certificate(cert) cert_pem = cert_der.public_bytes(serialization.Encoding.PEM) with open("cert.pem", "wb") as f: f.write(cert_pem) with requests.get("https://google.com", stream=True, verify='./cert.pem') as response: cert = response.raw.connection.sock.getpeercert(binary_form =True)
执行时第二次请求失败,报错:
requests.exceptions.SSLError: HTTPSConnectionPool(host='google.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)')))
但用curl命令请求却能正常验证:
curl -v --cacert ./cert.pem --capath ./ https://www.google.com
原因分析
- 证书链完整性要求差异:
你保存的cert.pem只是服务器的叶子证书(终端实体证书),没有包含完整的证书链(缺少中间证书、根证书)。Python的requests在SSL验证时,要求必须能从服务器证书追溯到受信任的根证书,完整链缺一不可,因此验证失败。
而curl的验证逻辑更宽松:当指定--cacert时,它会把该证书当作信任锚,只要服务器证书能和这个锚建立关联(哪怕中间链不全)就会通过验证;另外curl可能默认加载系统根证书,相当于间接补充了链的缺失,所以能成功。 - 请求域名存在区别:
注意到curl请求的是https://www.google.com,而Python代码请求的是https://google.com,这两个域名返回的证书链可能有差异,但核心问题还是证书链不完整。
验证与解决方法
- 用curl请求
https://google.com并禁用系统根证书,会和Python一样验证失败,可验证该逻辑:curl -v --cacert ./cert.pem --capath ./ --no-system-ca-path https://google.com - 解决方式是保存完整的证书链:从服务器获取所有证书(叶子+中间+根),合并到同一个PEM文件中,再作为
verify参数传入requests.get。
内容的提问来源于stack exchange,提问作者jwa
相关产品推荐
相关产品推荐

