当 ssl 设置看起来正常时,为啥我在 Python 中得到 [SSL: CERTIFICATE_VERIFY_FAILED]?

Posted

技术标签:

【中文标题】当 ssl 设置看起来正常时,为啥我在 Python 中得到 [SSL: CERTIFICATE_VERIFY_FAILED]?【英文标题】:Why do I get [SSL: CERTIFICATE_VERIFY_FAILED] in Python when ssl setup looks OK?当 ssl 设置看起来正常时,为什么我在 Python 中得到 [SSL: CERTIFICATE_VERIFY_FAILED]? 【发布时间】:2019-05-06 18:06:07 【问题描述】:

我正在开发一个 Python 应用程序,以通过安全的 websocket 协议与在 localhost 上运行的服务进行通信。 这是一个示例代码:

import json
import asyncio
import websockets
import ssl
import certifi


ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations(certifi.where())
ssl_context.load_default_certs()

query =  
    "jsonrpc": "2.0",
    "method": "queryHeadsets",
    "params": ,
    "id": 1
    
json = json.dumps(query)

async def query(json):

    async with websockets.connect("wss://emotivcortex.com:54321") as ws:
        await ws.send(json)
        response = await ws.recv()
        print(response)

asyncio.get_event_loop().run_until_complete(query(json))

问题是 ssl 握手一直失败并出现以下错误:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)

我正在运行 Windows 10,Python 3.7.3 64 位

$pip list
Package    Version
---------- --------
certifi    2019.3.9
pip        19.0.3
setuptools 40.8.0
websockets 7.0

我已经检查了服务提供的证书。它似乎有效并由 COMODO 签名。 我试过了:

ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations(certifi.where())
print(ssl.get_default_verify_paths())
print(ssl_context.cert_store_stats())
ssl_context.load_default_certs()
print(ssl_context.get_ca_certs())

发现有几个 COMODO CA 证书可用于 python。但我仍然得到错误。

如果有帮助,这里是完整的错误消息:

SSL handshake failed on verifying the certificate
protocol: <asyncio.sslproto.SSLProtocol object at 0x0000020C11283048>
transport: <_SelectorSocketTransport fd=508 read=polling write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "C:\Users\Matyas2\Python\lib\asyncio\sslproto.py", line 625, in _on_handshake_complete
    raise handshake_exc
  File "C:\Users\Matyas2\Python\lib\asyncio\sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "C:\Users\Matyas2\Python\lib\ssl.py", line 763, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)
SSL error in data received
protocol: <asyncio.sslproto.SSLProtocol object at 0x0000020C11283048>
transport: <_SelectorSocketTransport closing fd=508 read=idle write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "C:\Users\Matyas2\Python\lib\asyncio\sslproto.py", line 526, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "C:\Users\Matyas2\Python\lib\asyncio\sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "C:\Users\Matyas2\Python\lib\ssl.py", line 763, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)
Traceback (most recent call last):
  File "test.py", line 37, in <module>
    asyncio.get_event_loop().run_until_complete(query(json))
  File "C:\Users\Matyas2\Python\lib\asyncio\base_events.py", line 584, in run_until_complete
    return future.result()
  File "test.py", line 32, in query
    async with websockets.connect("wss://emotivcortex.com:54321") as ws:
  File "C:\Users\Matyas2\Python\lib\site-packages\websockets\py35\client.py", line 2, in __aenter__
    return await self
  File "C:\Users\Matyas2\Python\lib\site-packages\websockets\py35\client.py", line 12, in __await_impl__
    transport, protocol = await self._creating_connection
  File "C:\Users\Matyas2\Python\lib\asyncio\base_events.py", line 986, in create_connection
    ssl_handshake_timeout=ssl_handshake_timeout)
  File "C:\Users\Matyas2\Python\lib\asyncio\base_events.py", line 1014, in _create_connection_transport
    await waiter
  File "C:\Users\Matyas2\Python\lib\asyncio\sslproto.py", line 526, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "C:\Users\Matyas2\Python\lib\asyncio\sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "C:\Users\Matyas2\Python\lib\ssl.py", line 763, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)

与 Internet 中的服务器的 SSL 连接工作正常。 我错过了什么? 我做错了什么?

如果需要,我很乐意提供其他信息。

编辑: 该证书适用于 emotivcortex.com,由 COMODO RSA 域验证安全服务器 CA 颁发,因此我认为它不是自签名证书。 OpenSSL:

$python -c "import ssl; print(ssl.OPENSSL_VERSION)"
OpenSSL 1.1.0j  20 Nov 2018

【问题讨论】:

您使用的是自签名证书吗? 我遇到了类似的错误:SSLV3_ALERT_HANDSHAKE_FAILURE。我使用的是 Mac,这是在 virtualenv 中的 Python 2 上。我必须从我的机器上卸载现有的 python,然后使用 brew:brew install python@2 --with-brewed-openssl 再次安装它。然后我用这个python创建了一个新环境,一切都修复了。 同时检查你是否安装了最新的 Open SSL 版本。打开 python 控制台并运行此脚本以检查已安装的 OpenSSL 版本:import sslssl.OPENSSL_VERSION 我认为作为诊断的第一步,您应该验证您安装了正确的 CA 证书。使用例如openssl 命令行来验证证书,使用与您在 Python 代码中使用的相同的权限。如果您无法首先确认证书是否正确验证,则无法判断问题是否出在您的 Python 代码中。 一个典型的错误是服务器没有提供验证证书所需的所有链证书。桌面浏览器通常会尝试通过从缓存中填充丢失的证书或从 Internet 下载这些证书来解决此问题,但 Python、Java ......不要。 【参考方案1】:

问题是由于缺少中间 CA 证书造成的。

通过检查 OpenSSL 中服务提供的证书,我发现该证书是由“COMODO RSA Domain Validation Secure Server CA”颁发的。这个特定机构的 CA 证书实际上存在于 python 包 certifi 的 CA 包中(有不同的 COMODO...证书)。

解决方案

从 CA 的网页手动下载 PEM 格式的缺失证书,并将其添加到代码中使用的 CA 包中。

另外,应用代码中存在错误: 调用函数websockets.connect() 时,传递一个关键字参数ssl=ssl_context,以便实际使用前面指定的CA 包。 正确的代码如下所示:

import json
import asyncio
import websockets
import ssl
import certifi


ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations(certifi.where())


query =  
    "jsonrpc": "2.0",
    "method": "queryHeadsets",
    "params": ,
    "id": 1
    
json = json.dumps(query)

async def query(json):
    
    async with websockets.connect("wss://emotivcortex.com:54321", ssl=ssl_context) as ws:
        await ws.send(json)
        response = await ws.recv()
        print(response)

asyncio.get_event_loop().run_until_complete(query(json))

非常感谢 larsksSteffen Ullrich 为我指明了正确的方向。

【讨论】:

以上是关于当 ssl 设置看起来正常时,为啥我在 Python 中得到 [SSL: CERTIFICATE_VERIFY_FAILED]?的主要内容,如果未能解决你的问题,请参考以下文章

当需要 ssl 为真时无法调用 EWS

为啥当代码编译正常并设置类路径时找不到base64类

当我在 ngOnInit() 中使用 router.getCurrentNavigation() 时,它会给我类型错误,但是当我在构造函数中使用它时,它工作正常,为啥?

为啥当我将我的国际象棋项目作为可运行的 jar 文件运行时,我得到一个奇怪的行为,而当我在 eclipse 中运行它时,一切正常?

为啥红隼一直抛出这个调试 ssl 异常?

为啥这个组合框看起来像这样?