Python 请求 - 如何使用系统 ca 证书(debian/ubuntu)?

Posted

技术标签:

【中文标题】Python 请求 - 如何使用系统 ca 证书(debian/ubuntu)?【英文标题】:Python Requests - How to use system ca-certificates (debian/ubuntu)? 【发布时间】:2017-08-16 08:48:57 【问题描述】:

我已经在 debian 的 /usr/share/ca-certificates/local 中安装了一个自签名的根 ca 证书,并使用 sudo dpkg-reconfigure ca-certificates 安装了它们。此时true | gnutls-cli mysite.local 很高兴,true | openssl s_client -connect mysite.local:443 很高兴,但 python2 和 python3 requests 模块坚持认为它对证书不满意。

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')],)",)

python为什么会忽略系统ca-certificates bundle,如何集成?

【问题讨论】:

你不只是传递requests.get('https://example.com', verify='/usr/share/ca-certificates/local')吗? 【参考方案1】:

来自https://***.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'

【讨论】:

删除请求捆绑的默认 cacert.pem 后,请求似乎在不设置环境变量的情况下拾取系统 ca-certifications 捆绑包。 言语无法形容这个答案让我在解决我数周来一直在寻找的问题上度过了多么美好的一天。 @rwarner 说了什么! @Smedegaard 说了什么! 设置环境变量 REQUESTS_CA_BUNDLE 有效。但是,它不会更改 certifi 模块中的 crt 路径。答案暗示确实如此,但我在 python 3.7 和 3.8 中的测试显示并非如此。我建议使用 os.getenv 来检查路径。【参考方案2】:

最近我为此苦苦挣扎了一周左右。我终于找到了在 Python 中验证自签名或私人签名证书的方法。您需要创建自己的证书捆绑文件。每次更新库或向系统证书存储中添加任何内容时,都无需更新晦涩的证书包。

首先运行您之前运行的 openssl 命令,但添加 -showcerts。 openssl s_client -connect mysite.local:443 -showcerts 这会给你一个很长的输出,在顶部你会看到整个证书链。通常,这意味着三个证书,依次是网站证书、中间证书和根证书。我们只需要将根证书和中间证书以相反的顺序放入下一个文件中。

将最后一个证书(根证书)复制到一个新的文本文件中。只抓取介于两者之间的东西,包括:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

将中间证书(也称为中间证书)复制到根证书下的新文本文件中。再次,抓住 Begin 和 End Certificate 行以及介于两者之间的所有内容。

将此文本文件保存到 Python 脚本所在的目录。我的建议是称它为CertBundle.pem。 (如果您给它一个不同的名称,或者将它放在文件夹结构中的其他位置,请确保验证行反映了这一点。)更新您的脚本以引用新的证书包:

response = requests.post("https://www.example.com/", headers=headerContents, json=bodyContents, verify="CertBundle.pem")

就是这样。如果您只有根证书或只有中间证书,则 Python 无法验证整个证书链。但是,如果您在创建的证书包中包含这两个证书,则 Python 可以验证中间证书是否由根签名,然后当它访问网站时,它可以验证网站的证书是否由中间证书签名.

编辑:修复了证书包的文件扩展名。此外,修正了几个语法错误。

【讨论】:

我不确定 .p7b 是否是所述捆绑包的正确语义扩展。 (尽管我不是真正的专家)只是习惯于看到 .pem 和 .crt 用于 CA Bundles。我知道 debian ca-certificates 软件包对将证书作为 .crt 扩展名添加到系统提供的证书存储区很挑剔。 我几乎在我的帖子中说了些什么。我选择 p7b 是因为我认为这是正确的扩展,但为此目的并不重要。它可以是 .txt,或者根本没有扩展名。重要的是您的 Python 脚本可以找到该文件。 @fryad 是正确的;这个文件应该有.pem 扩展名,一些工具会因为它有错误的扩展名而错误地处理它。 .pemde facto standard extension for this base64-encoded certificate format,而相同格式的二进制版本是 .der.p7b不同 base64 编码格式。关于如何使用openssl CLI 工具在它们之间进行转换的方便参考:knowledge.digicert.com/solution/SO26449.html【参考方案3】:

我的两分钱:

感谢另一个answer,它让我检查了实际的请求代码,我发现您不必使用 env 变量,而是可以在您的请求中设置“验证”参数:

requests.get("https://whatever", verify="/my/path/to/cacert.crt", ...)

它也是documented,虽然我在发现之后才能找到文档(并且pypi项目指向doc的死链接):D

【讨论】:

【参考方案4】:

requests 使用certifi 作为默认的根证书包,它内置了很多好的 CA 但无法修改。

Debian(和 Ubuntu)维护者更改了 certifi 的行为与默认不同:

def where():
    return "/etc/ssl/certs/ca-certificates.crt"

所以如果你使用 apt-installed requestscertifi 没有问题。

但是 pip3 在虚拟环境中使用内置 CA 安装了证书。所以无法使用update-ca-certificates机制。除了在应用程序代码中手动指定根证书(如果通过 3rd 方接口间接调用 request 可能无法实现),它还可以覆盖环境变量 REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt 以模拟 Debianized 行为。

【讨论】:

【参考方案5】:

终于找到了一个可行的解决方案。所有其他方法都失败了。

sudo apt install ca-certificates
sudo update-ca-certificates --fresh
export SSL_CERT_DIR=/etc/ssl/certs

【讨论】:

以上是关于Python 请求 - 如何使用系统 ca 证书(debian/ubuntu)?的主要内容,如果未能解决你的问题,请参考以下文章

直接在文件系统上使用 PEM 编码的 CA 证书进行 HTTPS 请求?

如何使用未知 CA 自签名的证书让 Android Volley 执行 HTTPS 请求?

如何使用https 客户端证书

adb安装CA证书

如何创建一个自签名的SSL证书(X509)

Linux系统下weblogic服务下部署CA证书(免费),实现https请求