为什么python请求默认不使用系统ssl证书?

atl*_*ine 5 python ubuntu ssl https python-requests

背景:

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

$ cp -rf my.crt /usr/local/share/ca-certificates/
$ update-ca-certificates
Run Code Online (Sandbox Code Playgroud)

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

$ curl https://example.com
Run Code Online (Sandbox Code Playgroud)

问题:

但是,当我使用python3requests访问时,它报告下一个错误:

>>> 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)
Run Code Online (Sandbox Code Playgroud)

额外的:

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

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

问题:

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

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

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

atl*_*ine 7

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

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

选项1:

apt-get install -y python3-certifi
Run Code Online (Sandbox Code Playgroud)

选项2:

pip3 install certifi
Run Code Online (Sandbox Code Playgroud)

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

不过,看起来Canonical(Ubuntu的支持者)对 进行了一些更改certifi,因此如果使用python3-certifi from apt,则下一个或类似的代码def where因版本不同而有所不同:

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

但如果使用pip3安装,则会是:

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

/usr/local/lib/python3.6/dist-packages/certifi/cacert.pem在这个包里。

所以ubuntu上的解决办法是:使用apt-get install python3-certifi安装ubuntu版本certifi,卸载pip版本。然后,我们就无法更改应用程序代码的任何内容。

更新:

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

export REQUESTS_CA_BUNDLE='/etc/ssl/certs/ca-certificates.crt'
Run Code Online (Sandbox Code Playgroud)