尝试在最近(> = 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安全性不变!
此问题可能是什么原因?我该如何解决?
答案 0 :(得分:0)
不幸的是,这是一个悲伤的故事,仍然没有圆满的结局,在https://bugs.python.org/issue20916中有详细介绍。
Python 3.3将cadefault
参数添加到urllib.request.urlopen
,默认为True
(https://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浏览)特别容易受到此问题的影响,而开发人员可能永远不会在其“普通”台式计算机上遇到此问题。
如何解决此问题?不幸的是,没有简单的解决方案。
您可以安装certifi
软件包,该软件包提供自己的证书存储(IIRC是Mozilla证书存储的副本);然后可以像这样使用它:
import certifi
import urllib.request
r = urllib.request.urlopen(url_website, cafile=certifi.where())
这是流行的requests
模块所采取的方法,实际上确实是“开箱即用”;不幸的是,这是另一个证书存储,必须对其进行更新,因此您必须确保通过certifi
定期更新pip
软件包,或者无论如何安装。
非常感谢this blog article的作者,这是我设法找到的第一个正确解释了此问题的人。