urllib.request.urlopen:SSL:Windows> = Vista(7/8/10 / Server 2008)在Windows> = 3.4上的Windows上的SSL:CERTIFICATE_VERIFY_FAILED错误

时间:2018-08-29 09:55:54

标签: python windows python-3.x ssl urllib

尝试在最近(> = Vista)Windows计算机上的许多HTTPS站点上使用Python 3 urlopen时,在许多站点上(某些站点上)执行urllib.request.urlopen时,出现“ SSL:CERTIFICATE_VERIFY_FAILED”错误甚至可以https://www.google.com/来构建机器,但奇怪的是,从来没有https://www.microsoft.com/来构建机器。

>>> import urllib.request
>>> urllib.request.urlopen("https://www.google.com/")
Traceback (most recent call last):
  File "C:\Python35\lib\urllib\request.py", line 1254, in do_open
    h.request(req.get_method(), req.selector, req.data, headers)
  File "C:\Python35\lib\http\client.py", line 1106, in request
    self._send_request(method, url, body, headers)
  File "C:\Python35\lib\http\client.py", line 1151, in _send_request
    self.endheaders(body)
  File "C:\Python35\lib\http\client.py", line 1102, in endheaders
    self._send_output(message_body)
  File "C:\Python35\lib\http\client.py", line 934, in _send_output
    self.send(msg)
  File "C:\Python35\lib\http\client.py", line 877, in send
    self.connect()
  File "C:\Python35\lib\http\client.py", line 1260, in connect
    server_hostname=server_hostname)
  File "C:\Python35\lib\ssl.py", line 377, in wrap_socket
    _context=self)
  File "C:\Python35\lib\ssl.py", line 752, in __init__
    self.do_handshake()
  File "C:\Python35\lib\ssl.py", line 988, in do_handshake
    self._sslobj.do_handshake()
  File "C:\Python35\lib\ssl.py", line 633, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c
:645)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python35\lib\urllib\request.py", line 163, in urlopen
    return opener.open(url, data, timeout)
  File "C:\Python35\lib\urllib\request.py", line 466, in open
    response = self._open(req, data)
  File "C:\Python35\lib\urllib\request.py", line 484, in _open
    '_open', req)
  File "C:\Python35\lib\urllib\request.py", line 444, in _call_chain
    result = func(*args)
  File "C:\Python35\lib\urllib\request.py", line 1297, in https_open
    context=self._context, check_hostname=self._check_hostname)
  File "C:\Python35\lib\urllib\request.py", line 1256, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certifica
te verify failed (_ssl.c:645)>

最令人生气的是,这种情况几乎只发生在build / CI服务器上,并且在尝试调查问题后(例如,检查到给定站点的连接,当通过浏览器尝试正确响应时),这些错误通常消失了: >

>>> import urllib.request
>>> urllib.request.urlopen("https://www.google.com/")
<http.client.HTTPResponse object at 0x0000000002D930B8>

我听到了很多关于通过破坏SSL上下文来禁用证书验证的建议,但我想避免这种情况-我想保持我的HTTPS安全性不变!

此问题可能是什么原因?我该如何解决?

1 个答案:

答案 0 :(得分:0)

不幸的是,这是一个悲伤的故事,仍然没有圆满的结局,在https://bugs.python.org/issue20916中有详细介绍。

Python 3.3将cadefault参数添加到urllib.request.urlopen,默认为Truehttps://bugs.python.org/issue14780),这使HTTPS请求默认使用系统证书存储来验证服务器证书

Python 3.4在Windows(https://bugs.python.org/issue19292)上实现了SSLContext.set_default_verify_paths的工作,使Python可以使用Windows证书存储。

以前,Microsoft通过Windows Update推送了根证书更新,以确保始终更新系统根证书存储(只要用户安装了更新)。到目前为止,一切都很好。

但是,从Windows Vista开始,Windows仅与商店中的几个“核心”证书(IIRC少于20个)捆绑在一起,并且每当要求CryptoAPI对其无法在其中找到受信任根的证书进行验证时,在本地存储中,将与Microsoft服务器联系以检查它们是否具有受信任的根。如果是这样,则会提供根证书,并自动安装到系统证书存储中。

不幸的是,Python不使用Windows CryptoAPI,因此它无法从这种自动机制中受益。相反,它要求系统证书存储中的所有证书并尝试使用它们-但这意味着所获得的只是Windows附带的少数证书,手动安装的证书以及所有发生,已自动安装,通常是在使用Internet Explorer或Edge浏览Internet时。

这使问题特别隐蔽,因为会出现问题的站点在不同机器之间会有所不同(主要取决于其浏览历史记录!),并且通常会消失(对于该站点,所有站点都取决于其相同的根证书),如果您检查是否可以使用Windows CryptoAPI通过浏览器连接到该站点。由于这个原因,新的Windows安装,一般的构建机器和服务器(它们看不到太多的交互式Internet浏览)特别容易受到此问题的影响,而开发人员可能永远不会在其“普通”台式计算机上遇到此问题。


如何解决此问题?不幸的是,没有简单的解决方案。

  • 对于简单的情况(例如CI服务器),其中某些测试需要访问几乎从未更改的某些特定域,一个简单的解决方法是打开Internet Explorer并打开此类域上的页面。这将使它将所需的根证书提取到本地证书存储中,并且Python直到它到期之前都不会有问题(注意:我们在这里谈论的是 root 证书,该证书通常具有一个多年);
  • 您可以禁用证书验证 tout-court such as this已在许多不同的答案中对此进行了介绍。但是,这通常是不可取的,因为您正在放弃SSL提供的MITM保护;
  • 您可以手动将所有当前受信任的根证书安装到Windows证书存储中; here is a site that explains how(免责声明:解释的过程似乎很明智,但我从未尝试过);不幸的是,这是一个手动过程,您需要定期重复此过程,以确保获得新的根证书;
  • 您可以安装certifi软件包,该软件包提供自己的证书存储(IIRC是Mozilla证书存储的副本);然后可以像这样使用它:

    import certifi
    import urllib.request
    r = urllib.request.urlopen(url_website, cafile=certifi.where())
    

    这是流行的requests模块所采取的方法,实际上确实是“开箱即用”;不幸的是,这是另一个证书存储,必须对其进行更新,因此您必须确保通过certifi定期更新pip软件包,或者无论如何安装。


非常感谢this blog article的作者,这是我设法找到的第一个正确解释了此问题的人。