Home > Software engineering >  Cannot connect to remote secure web socket server using python 3 websocket library
Cannot connect to remote secure web socket server using python 3 websocket library

Time:09-22

I have a secure web socket server implemented in python 3 running on a RaspberryPI device at address RASPI_ADDRESS, exposed on port 8000. On the RaspberryPI device, this is what the ssl version is shown as:

>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.1d  10 Sep 2019

For testing purposes, I am using a self signed certificate, generated with openssl: certificate file cert.pem with the companion private key in key.pem.

On the client side, I am on a Windows machine and I implemented the client as follows (the same cert.pem file from above is available here as a local copy):

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)

I am getting this exception on ws.connect(...):

Exception:  [SSL] PEM lib (_ssl.c:4065)

(If I connect in a non-secure way using "ws://...", it works)

Unfortunately, I am not getting many relevant results when searching for this error. I have tried to also provide the private key in the sslopt as well ("keyfile": "key.pem"), but then the script seems caught in some sync blocking - no exception, nothing listed on the screen, but also nothing received on the server side.

Any pointers as to what I am doing wrong?

CodePudding user response:

In the end, I solved it by rewriting the server and client using the websockets library: https://pypi.org/project/websockets/

Perhaps it would also have run with the websocket-client library https://pypi.org/project/websocket-client/ I was using before, but the docs were partly inconsistent and confusing. Writing here a simplified working solution for future reference, in the form of a dummy echo server.

Server running on RasPI (visible in LAN at IP address 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())

Note the "0.0.0.0" host IP in websockets.serve()! If we set this to "localhost", the client will see a stack trace ending with this error:

ConnectionRefusedError: [WinError 1225] The remote computer refused the network connection

Client running on Windows machine:

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())

This got me at least a reaction compared to the original implementation, as then I ran into this error:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for *RASPI_IP*. (_ssl.c:1129)

This is solved by generating a certificate with SAN instead of only CN: https://serverfault.com/a/880809 Additionally, I combined the certificate and the key into one file: cat key.pem cert.pem > key_cert.pem

  • Related