【问题标题】:Why python requests not use the system ssl cert by default?为什么python请求默认不使用系统ssl证书?
【发布时间】:2021-03-29 03:16:40
【问题描述】:

背景:

我正在使用 Ubuntu 18.04.1 LTS,使用 next 安装自签名证书:

$ cp -rf my.crt /usr/local/share/ca-certificates/
$ update-ca-certificates

一切正常,因为现在我可以使用 next 成功访问我的网站:

$ curl https://example.com

问题:

但是,当我使用python3requests访问时,报next error:

>>> requests.get("https://example.com")
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/urllib3/connectionpool.py", line 706, in urlopen
    chunked=chunked,
  File "/usr/local/lib/python3.6/dist-packages/urllib3/connectionpool.py", line 382, in _make_request
    self._validate_conn(conn)
  File "/usr/local/lib/python3.6/dist-packages/urllib3/connectionpool.py", line 1010, in _validate_conn
    conn.connect()
  File "/usr/local/lib/python3.6/dist-packages/urllib3/connection.py", line 421, in connect
    tls_in_tls=tls_in_tls,
  File "/usr/local/lib/python3.6/dist-packages/urllib3/util/ssl_.py", line 429, in ssl_wrap_socket
    sock, context, tls_in_tls, server_hostname=server_hostname
  File "/usr/local/lib/python3.6/dist-packages/urllib3/util/ssl_.py", line 472, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.6/ssl.py", line 407, in wrap_socket
    _context=self, _session=session)
  File "/usr/lib/python3.6/ssl.py", line 817, in __init__
    self.do_handshake()
  File "/usr/lib/python3.6/ssl.py", line 1077, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib/python3.6/ssl.py", line 689, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:847)

补充:

但是,如果我明确指定 crt,它会成功:

>>> requests.get("https://example.com", verify='/etc/ssl/certs/ca-certificates.crt')
<Response [200]>

问题:

我正在使用第三方代码,这意味着我无法更改 python 代码。

所以,我的问题是:为什么 python 请求不默认在 /etc/ssl/certs/ca-certificates.crt 中找到 verify?我认为这是 ubuntu 上的默认认证,因为 curl 默认成功找到这个...

而且,如果可能的话,我可以使用export 在 python 代码之外设置任何变量,那么 python 请求将默认查找认证?

【问题讨论】:

  • 不仅 requests 默认不使用 Ubuntu 上的系统证书,Firefox、Chrome、Java 等也不使用。在其他系统上并没有太大的不同。部分原因是 CA 存储的不同格式。部分原因是开发人员认为在不同的操作系统上保持一致(它们都有不同的位置和信任存储格式的想法)比与同一系统上的其他应用程序保持一致更重要。不幸的是,人们不得不忍受一团糟。
  • @SteffenUllrich 那么,在我无法更改第三方代码的情况下,我该怎么办...
  • “那么,我应该怎么做?” - 如果你想使用系统 CA 存储而不是像你一样明确地让你的应用程序使用它。如果您只想获得特定应用程序信任的特定 CA,请不要使用系统 CA 商店,而是将其保留在应用程序本地。如果您无法更改应用程序,请更改应用程序实际使用的证书存储。它可能是 certifi (常见于请求),也可能是其他东西。另见incognitjoe.github.io/adding-certs-to-requests.html
  • 您要么必须编辑第三方代码,要么必须将 CA 添加到第三方代码使用的信任库中 - 无论此信任库是什么。您可以使用 strace 找出它在哪里寻找证书。但它也可能依赖于应用程序中设置的固定证书,在这种情况下您需要更改应用程序。
  • @SteffenUllrich,感谢您的提示 :) 最后,我发现 ubuntu 官方已经按照您的建议进行了类似的操作,使用 certifi 的变体更改应用程序的 CA 路径,然后我无法更改任何内容这行得通。谢谢!

标签: python ubuntu ssl https python-requests


【解决方案1】:

我找到了根本原因,requests 实际上会使用from certifi import where 调用where 以在这台电脑上找到正确的CA。

但是,在 ubuntu 中安装certifi 模块有两种方式:

选项 1:

apt-get install -y python3-certifi

选项 2:

pip3 install certifi

注意:如果直接使用pip3 install requests,如果没有为python3-certifi安装debian包,它将使用pip3隐式安装certifi

不过,看起来 Canonical(Ubuntu 的支持者)对 certifi 做了一些更改,所以如果使用 python3-certifi from aptdef where 的代码是 next 或类似的代码因不同版本而异:

root@4e1aab76e082:/# cat /usr/lib/python3/dist-packages/certifi/core.py
def where():
    return "/etc/ssl/certs/ca-certificates.crt"

但是如果使用pip3来安装,那就是:

root@4e1aab76e082:/# cat /usr/local/lib/python3.6/dist-packages/certifi/core.py
def where():
    _CACERT_CTX = get_path("certifi", "cacert.pem")

这是这个包中的/usr/local/lib/python3.6/dist-packages/certifi/cacert.pem

所以在ubuntu上的解决方法是:使用apt-get install python3-certifi安装certifi的ubuntu变种,卸载pip那个。然后,我们就无法更改应用代码的任何内容。

更新:

我找到了另一种与官方证书一起使用的方法,使用下一个变量,我也可以让python requests module 从我指定的位置获取 CA,而无需更改应用程序代码:

export REQUESTS_CA_BUNDLE='/etc/ssl/certs/ca-certificates.crt'

【讨论】:

  • 非常感谢!
猜你喜欢
  • 2015-08-15
  • 1970-01-01
  • 1970-01-01
  • 2017-08-16
  • 1970-01-01
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 2013-10-12
相关资源
最近更新 更多