Curl可访问自签名证书内部地址,Python Requests遇SSL验证失败
问题场景
访问内部自签名证书地址时,curl https://myhost.internal --cacert ./mycert.crt返回200,但使用Python requests库:
import requests requests.get('https://myhost.internal', verify='./mycert.crt')
抛出SSLError(SSLError(1, u'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:727)'),subprocess调用curl正常,关闭验证(verify=False)也能成功,但不符合安全要求。
可能的原因
1. 证书格式不兼容
curl对证书格式的容错性更高,可自动识别并处理DER/PEM格式,甚至忽略文件内的非证书内容。但requests依赖的urllib3/ssl模块要求证书必须是标准PEM格式(文件开头包含-----BEGIN CERTIFICATE-----)。如果你的mycert.crt是DER二进制格式,或格式存在异常,requests会验证失败。
验证方法:
openssl x509 -in ./mycert.crt -text
若命令报错,说明格式有问题,可转换为PEM格式:
openssl x509 -inform der -in ./mycert.crt -out ./mycert_pem.crt
2. 证书链不完整
当服务器返回的证书由中间CA签发时,curl会自动从--cacert指定的文件中查找缺失的中间证书,补全证书链。但requests要求提供的CA文件必须包含完整的信任链(从服务器证书的上级中间CA到根CA),否则会因无法追溯到信任根而验证失败。
解决方法:
将中间CA证书与根CA证书按顺序拼接至同一个.crt文件(根CA放在最后),再提供给verify参数。
3. SSL验证逻辑版本差异
Python的requests依赖系统OpenSSL库,不同版本的OpenSSL对证书验证规则不同:
- 部分Python版本(如3.7+)默认要求服务器证书必须包含
subjectAltName字段,仅靠CN字段匹配会被拒绝; - curl使用的OpenSSL版本可能仍允许
CN字段匹配,因此能成功建立连接。
验证方法:
查看服务器证书的subjectAltName字段:
openssl s_client -connect myhost.internal:443 | openssl x509 -noout -text
若输出中无X509v3 Subject Alternative Name,则需重新签发包含该字段的证书。
4. 相对路径解析错误
requests的verify参数使用相对路径时,路径是相对于Python脚本的当前工作目录,而非脚本文件所在目录。如果运行Python脚本的目录与执行curl时的目录不同,./mycert.crt会指向错误路径,导致无法加载证书。
解决方法:
使用证书的绝对路径,例如:
requests.get('https://myhost.internal', verify='/home/user/mycert.crt')
5. 证书文件权限不足
curl对证书文件的权限要求较低,但Python运行进程的用户可能没有mycert.crt的读取权限,导致无法加载证书,进而触发验证失败。
解决方法:
调整证书文件权限,确保运行Python的用户有读权限:
chmod 644 ./mycert.crt
内容的提问来源于stack exchange,提问作者Djoby

