我已将自签名根 ca 证书安装到 debian 的
/usr/share/ca-certificates/local
中,并使用 sudo dpkg-reconfigure ca-certificates
安装它们。此时 true | gnutls-cli mysite.local
很高兴,true | openssl s_client -connect mysite.local:443
很高兴,但 python2 和 python3 请求模块坚持认为它对证书不满意。
python2:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/local/lib/python2.7/site-packages/requests/api.py", line 70, in get
return request('get', url, params=params, **kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/api.py", line 56, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 488, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 609, in send
r = adapter.send(request, **kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/adapters.py", line 497, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'ssl3_get_server_certificate', 'certificate verify failed')],)",)
python3
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/local/bin/python3.5/site-packages/requests/api.py", line 70, in get
return request('get', url, params=params, **kwargs)
File "/usr/local/bin/python3.5/site-packages/requests/api.py", line 56, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/local/bin/python3.5/site-packages/requests/sessions.py", line 488, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/bin/python3.5/site-packages/requests/sessions.py", line 609, in send
r = adapter.send(request, **kwargs)
File "/usr/local/bin/python3.5/site-packages/requests/adapters.py", line 497, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'ssl3_get_server_certificate', 'certificate verify failed')],)",)
来自https://stackoverflow.com/a/33717517/1695680
要使 python 请求使用系统 ca-certificates 捆绑包,需要告诉它在自己的嵌入式捆绑包上使用它
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
Requests 将其捆绑包嵌入此处,以供参考:
/usr/local/lib/python2.7/site-packages/requests/cacert.pem
/usr/lib/python3/dist-packages/requests/cacert.pem
或者在较新的版本中使用附加包从以下位置获取证书: https://github.com/certifi/python-certifi
要验证从哪个文件加载证书,您可以尝试:
Python 3.8.5 (default, Jul 28 2020, 12:59:40)
>>> import certifi
>>> certifi.where()
'/etc/ssl/certs/ca-certificates.crt'
最近我为此苦苦挣扎了一周左右。我终于找到了在 Python 中验证自签名或私签名证书的方法。您需要创建自己的证书捆绑文件。每次更新库或向系统证书存储添加任何内容时,无需更新晦涩的证书包。
首先运行之前运行的 openssl 命令,但添加 -showcerts。
openssl s_client -connect mysite.local:443 -showcerts
这将为您提供很长的输出,在顶部您将看到整个证书链。通常,这意味着三个证书,依次为网站证书、中间证书和根证书。我们只需将根证书和中间证书以相反的顺序放入下一个文件中。
将最后一个证书(根证书)复制到新的文本文件。只抓住之间的东西,包括:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
将中间证书(又名中间证书)复制到根证书下的新文本文件中。再次,抓住开始和结束证书行以及之间的所有内容。
将此文本文件保存到 Python 脚本所在的目录中。我的建议是称其为
CertBundle.pem
。 (如果您给它一个不同的名称,或者将其放在文件夹结构中的其他位置,请确保验证行反映了这一点。)更新您的脚本以引用新的证书包:
response = requests.post("https://www.example.com/", headers=headerContents, json=bodyContents, verify="CertBundle.pem")
就是这样。如果您只有根证书或只有中间证书,那么 Python 无法验证整个证书链。但是,如果您将两个证书都包含在您创建的证书包中,则 Python 可以验证中间证书是否由根证书签名,然后当它访问网站时,它可以验证网站的证书是否由中间证书签名.
编辑:修复了证书包的文件扩展名。此外,还修复了一些语法错误。
requests
使用certifi
作为默认根证书包,内置了很多好的CA,但无法修改。
Debian(和 Ubuntu)维护者更改了
certifi
的行为与默认行为不同:
def where():
return "/etc/ssl/certs/ca-certificates.crt"
所以如果你使用 apt-installed
requests
和 certifi
就没有问题了。
但是 pip3 在虚拟环境中安装了 certifi,使用内置 CA。所以无法使用
update-ca-certificates
机制。除了在应用程序代码中手动指定根证书(如果通过第 3 方接口间接调用 request
可能无法实现)之外,它还可以使用环境变量 REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
覆盖以模拟 Debian 化行为。
尝试了一切之后,我发现这在 Ubuntu 上对我有用
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
即使
certifi
显示了相同的路径,我也必须这样做。
@fryads 的答案最适合我。作为一家具有 https 拦截功能的 VPN 公司,我在使用本地 python 脚本时也遇到了类似的问题。以下脚本(在正确指导后使用帮助 gpt 完成:) - 简单地完成工作。
#!/bin/bash
# Description:
# This script fetches the entire certificate chain for a given domain using openssl.
# It then removes the server's certificate and reverses the order of the remaining certificates.
# The output is saved to a file named `certbundle.pem`.
#
# Usage:
# ./scriptname.sh <domain_name>
#
# Example:
# ./scriptname.sh example.com
# Check if a URL has been provided as an argument
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <domain_name>"
exit 1
fi
# Extract the domain name from the argument
DOMAIN="$1"
# Fetch only the certificate chain using openssl.
# We use sed to filter out everything except the certificates.
openssl s_client -connect "$DOMAIN":443 -showcerts | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > certbundle.pem
# Remove the first certificate (from the server itself) from the certbundle.pem file.
# In macOS, the -i option for sed requires an extension, but we're providing an empty string to modify the file in-place without creating a backup.
sed -i '' '1,/-END CERTIFICATE-/d' certbundle.pem
# Reverse the order of the certificates and save it back to certbundle.pem.
awk 'BEGIN {last = 0} /-----BEGIN CERTIFICATE-----/ { if(last) {print lastcert} last=1 } { lastcert = (lastcert $0 RS) } END {print lastcert}' certbundle.pem > reversed_certbundle.pem && mv reversed_certbundle.pem certbundle.pem
echo "Certificate chain has been saved in certbundle.pem."
在 python 中只需简单地做:
import request
response = requests.get("your url goes here", verify='certbundle.pem')
我不想使用静态文件或额外的我不理解的 pip 包来解决完全相同的问题。幸运的是,标准的
ssl
包,尤其是 load_default_certs()
函数,可以解决这个问题:
import ssl
import requests
from requests.adapters import HTTPAdapter
class LocalSSLContext(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
context = ssl.create_default_context()
context.load_default_certs()
kwargs['ssl_context'] = context
return super(LocalSSLContext, self).init_poolmanager(*args, **kwargs)
session = requests.Session()
sslContext = LocalSSLContext()
session.mount('https://www.example.com/', sslContext)
response = session.get(url='https://www.example.com/')
print(response.status_code)
在 Windows 和 Linux 环境中为我工作。