无法使用 python 3 websocket 库连接到远程安全 web 套接字服务器
Cannot connect to remote secure web socket server using python 3 websocket library
我在地址 RASPI_ADDRESS 的 RaspberryPI 设备上 python 3 运行 中实现了一个安全的网络套接字服务器,暴露在端口 8000 上。
在 RaspberryPI 设备上,ssl 版本显示为:
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.1d 10 Sep 2019
出于测试目的,我使用的是使用 openssl 生成的自签名证书:证书文件 cert.pem 以及 [ 中的伴随私钥=33=].
在客户端,我在 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 上的服务器 运行ning(在 LAN 中可见 IP 地址 RASPI_IP)
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
客户端 运行ning 在 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
我在地址 RASPI_ADDRESS 的 RaspberryPI 设备上 python 3 运行 中实现了一个安全的网络套接字服务器,暴露在端口 8000 上。 在 RaspberryPI 设备上,ssl 版本显示为:
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.1d 10 Sep 2019
出于测试目的,我使用的是使用 openssl 生成的自签名证书:证书文件 cert.pem 以及 [ 中的伴随私钥=33=].
在客户端,我在 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 上的服务器 运行ning(在 LAN 中可见 IP 地址 RASPI_IP)
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
客户端 运行ning 在 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