Mat*_*lia 8 python windows ssl urllib python-3.x
尝试在最近(>=Vista)Windows 计算机上的许多 HTTPS 站点上使用 Python 3urlopen
时,尝试在许多站点上执行操作时出现“SSL:CERTIFICATE_VERIFY_FAILED”错误urllib.request.urlopen
(甚至在某些构建计算机上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)>
Run Code Online (Sandbox Code Playgroud)
最令人愤怒的是,这种情况几乎只发生在构建/CI 服务器上,并且通常在尝试调查问题后这些错误就会消失(例如检查给定站点的连接,当通过浏览器尝试时该站点会正确响应):
>>> import urllib.request
>>> urllib.request.urlopen("https://www.google.com/")
<http.client.HTTPResponse object at 0x0000000002D930B8>
Run Code Online (Sandbox Code Playgroud)
我听到了很多关于通过扰乱 SSL 上下文来禁用证书验证的建议,但我想避免这种情况 - 我想保持我的 HTTPS 安全完好无损!
导致此问题的原因可能是什么?我该如何修复它?
Mat*_*lia 14
不幸的是,这是一个悲伤的故事,仍然没有美好的结局,详细信息请参见https://bugs.python.org/issue20916。
Python 3.3 添加了该cadefault
参数urllib.request.urlopen
,默认为True
(https://bugs.python.org/issue14780),这使得 HTTPS 请求默认使用系统证书存储来验证服务器证书。
Python 3.4SSLContext.set_default_verify_paths
在 Windows 上做了一些工作(https://bugs.python.org/issue19292),使 Python 能够使用 Windows 证书存储。
此前,微软通过Windows Update推送根证书更新,这确保了系统根证书存储始终更新(只要用户安装了更新)。到目前为止,一切都很好。
然而,从 Windows Vista 开始,Windows 只与存储中的少数“核心”证书捆绑在一起(少于 20 个,IIRC),并且每当要求 CryptoAPI 验证在本地存储中找不到可信根的证书时,联系 Microsoft 服务器以检查它们是否具有受信任的根。如果是这样,则会提供根证书并自动安装到系统证书存储中。
不幸的是,Python 不使用 Windows SChannel/CryptoAPI,因此它无法从这种自动机制中受益;相反,它会要求系统证书存储中的所有证书并尝试使用它们 - 但这意味着它所获得的只是 Windows 附带的少数证书、手动安装的证书以及碰巧拥有的所有证书通常在使用 Internet Explorer 或 Edge 浏览 Internet 时自动安装。
这使得这个问题特别阴险,因为出现问题的站点在不同的机器之间会有所不同(主要取决于它们的浏览历史记录!),并且通常会消失(对于该站点,以及依赖于其相同根证书的所有站点),如果您检查是否可以使用 SChannel 通过浏览器连接到该站点。由于这个原因,新的 Windows 安装、构建机器和服务器(看不到太多交互式 Internet 浏览)特别容易受到此问题的影响,而开发人员可能永远不会在他们的“普通”台式机上遇到此问题。
如何解决这个问题?不幸的是,没有简单的解决方案。
对于简单的情况,例如 CI 服务器,其中一些测试需要访问一些几乎永远不会改变的特定域,一个简单的解决方法可以是打开 Internet Explorer 并打开此类域上的页面。这将使其将所需的根证书获取到本地证书存储区,并且Python在其过期之前不会出现问题(注意:我们这里讨论的是根证书,其一般有很多年的有效期);在默认情况下提供curl
使用 SChannel 作为 SSL 后端的版本的现代 Windows 版本上,也可以使用它
您可以禁用证书验证tout-court;许多不同的答案已经涵盖了这一点,例如这个。然而,这通常是不可取的,因为您放弃了 SSL 提供的 MITM 保护;
您可以手动将当前所有受信任的根证书安装到Windows证书存储区;这是一个解释如何操作的网站(免责声明:解释的过程看起来很合理,但我从未尝试过);不幸的是,这是一个手动过程,您需要定期重复该过程以确保获得新的根证书;
您可以安装该certifi
软件包,它提供自己的证书存储(IIRC,它是 Mozilla 证书存储的副本);然后你可以像这样使用它:
import certifi
import urllib.request
r = urllib.request.urlopen(url_website, cafile=certifi.where())
Run Code Online (Sandbox Code Playgroud)
这是流行模块所走的路requests
,它确实通常“开箱即用”;不幸的是,这是另一个证书存储,必须保持更新,因此您必须确保certifi
通过pip
或安装它的方式定期更新包。
非常感谢这篇博客文章的作者,这是我找到的第一篇正确解释这个问题的文章。
归档时间: |
|
查看次数: |
4745 次 |
最近记录: |