python-3.x - 无法使用 python 3 websocket 库连接到远程安全 web socket服务器
问题描述
我有一个在 python 3 中实现的安全 Web 套接字服务器,该服务器在地址为 RASPI_ADDRESS 的 RaspberryPI 设备上运行,暴露在端口 8000 上。在 RaspberryPI 设备上,ssl 版本显示为:
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.1d 10 Sep 2019
出于测试目的,我使用的是使用 openssl 生成的自签名证书:证书文件cert.pem以及key.pem中的配套私钥。
在客户端,我在一台 Windows 机器上,我按如下方式实现了客户端(上面的相同 cert.pem 文件可在此处作为本地副本获得):
import ssl
import websocket
ws = websocket.WebSocket(sslopt={"ssl_version": ssl.PROTOCOL_TLSv1, "certfile": "cert.pem"})
try:
ws.connect("wss://RASPI_ADDRESS:8000")
ws.send("Hello, Server")
print(ws.recv())
ws.close()
except Exception as e:
print("Exception: ", e)
我在 ws.connect(...) 上收到此异常:
Exception: [SSL] PEM lib (_ssl.c:4065)
(如果我使用“ws://...”以非安全方式连接,它可以工作)
不幸的是,在搜索此错误时,我没有得到很多相关结果。我也尝试在 sslopt 中提供私钥(“keyfile”:“key.pem”),但随后脚本似乎陷入了一些同步阻塞 - 无一例外,屏幕上没有列出任何内容,但也没有收到任何内容在服务器端。
关于我做错了什么的任何指示?
解决方案
最后,我通过使用websockets库重写服务器和客户端来解决它:https ://pypi.org/project/websockets/
也许它也可以与我之前使用的websocket-client库https://pypi.org/project/websocket-client/一起运行,但文档部分不一致且令人困惑。在这里以虚拟回声服务器的形式编写一个简化的工作解决方案以供将来参考。
在 RasPI 上运行的服务器(在 IP 地址为 RASPI_IP 的 LAN 中可见)
import asyncio
import pathlib
import ssl
import websockets
async def hello(websocket, path):
name = await websocket.recv()
print(f"<<< {name}")
greeting = f"Hello {name}!"
await websocket.send(greeting)
print(f">>> {greeting}")
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
localhost_pem = pathlib.Path(__file__).with_name("key_cert.pem")
ssl_context.load_cert_chain(localhost_pem)
async def main():
async with websockets.serve(hello, "0.0.0.0", 8765, ssl=ssl_context):
await asyncio.Future() # run forever
asyncio.run(main())
注意 websockets.serve() 中的“0.0.0.0”主机 IP!如果我们将其设置为“localhost”,客户端将看到一个堆栈跟踪以该错误结尾:
ConnectionRefusedError: [WinError 1225] The remote computer refused the network connection
在 Windows 机器上运行的客户端:
import asyncio
import pathlib
import ssl
import websockets
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
localhost_pem = pathlib.Path(__file__).with_name("key_cert.pem")
ssl_context.load_verify_locations(localhost_pem)
uri_linux = "wss://RASPI_IP:8765"
async def hello():
uri = uri_linux
async with websockets.connect(uri, ssl=ssl_context) as websocket:
name = input("What's your name? ")
await websocket.send(name)
print(f">>> {name}")
greeting = await websocket.recv()
print(f"<<< {greeting}")
asyncio.run(hello())
与原始实现相比,这至少让我有所反应,然后我遇到了这个错误:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for *RASPI_IP*. (_ssl.c:1129)
这可以通过使用 SAN 而不是仅 CN 生成证书来解决:https ://serverfault.com/a/880809
此外,我将证书和密钥合并到一个文件中:cat key.pem cert.pem > key_cert.pem
推荐阅读
- android - 从 URI 获取图像路径时出现空指针异常错误
- python - 如何在单独的进程中运行 Python 自定义对象,所有这些都在共享事件队列上工作?
- java - 使用 mysql 驱动程序使用 mysql 数据库设置 spring-boot 项目
- angularjs - 在 Ionic3 中使用 InAppbrowser 打开浏览器时如何禁用后退按钮
- javascript - 使用比较表。添加多个表格时的移动响应问题?
- java - 为什么 nextLine() 在我的 Java 程序中不一直等待输入?
- emacs - 如何在我的程序中清除 Emacs 回显区域?
- ansible - 从 Ansible 中的地图列表中提取属性列表的正确方法
- codeblocks - 为什么我的 MSYS2/MinGW64 设置链接没有 -l 指令的 SDL2,而不是 SDL2_ttf?
- npm - 为什么`npm install` 在版本号中添加/删除插入符号(^)?