python-websockets / websockets

Library for building WebSocket servers and clients in Python
https://websockets.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
5.19k stars 514 forks source link

Checking TLS version and cipher suites during handshake #737

Closed MarcMueltin closed 4 years ago

MarcMueltin commented 4 years ago

Hi,

I'm trying to figure out how to specifically catch and log a connection error that occurs when the TLS server doesn't have a valid TLS version or a valid cipher suite. I am currently implementing a protocol that demands that I need to log security events, among them InvalidTLSVersion and InvalidTLSCipherSuite.

I tried to invoke such a situation by editing the server-side cipher suite in such a way that it doesn't match the client-side cipher suite. As a result, the only output I get from websockets is this:

"WebSocket connection closed with ConnectionResetError: "

Not even a hint what exactly when wrong. Sure, the TCP connection wasn't available because the handshake must have failed. But I would have expected an InvalidHandshake exception instead of a ConnectionResetError.

Can you please advise me on the steps I need to implement in order to find out whether or not

I thought that logging the websockets events with

logger = logging.getLogger('websockets') logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler())

would be more informative, but all I get is the above statement about the ConnectionResetError.

Any help is much appreciated.

adriansev commented 4 years ago

I'm not an expert but from my usage of websockets, the SSL related stuff happens either at the level of creating the ssl context or when actually creating socket (i create the socket separately then i pass the socket to websocket creation), so you might want to do try and catch exceptions at that level.

MarcMueltin commented 4 years ago

I'm catching all exceptions at the time of creating the websocket, the result of which I have explained in my post above. I do create the SSLContext before creating the websocket client connection. But at the time of creating the SSLContext on the client side, there are no exceptions to catch that would relate to the TLS handshake. So not sure how that would help me.

adriansev commented 4 years ago

well, you could try to create the network socket by hand and then to pass it to websocket.connect like this: create socket and then use it in websocket connect but i do a normal socket and let the websocket api to ssl wrap it.. maybe you could try to create the ssl socket directly and then websocket.connect

aaugustin commented 4 years ago

Like @adriansev said, it's best to create the TLS connection outside of websockets, so you can control exactly what happens there. Then use the sock argument to pass it to websockets.

MarcMueltin commented 4 years ago

Thanks for the hint. However, I'm still stuck as I can't get the socket to connect. I might have a false understanding of how this is supposed to work. But I thought I create the socket (without connecting it yet) and hand it over to the WebSocket, which then performs the connection. Yet, on the client side I get the error message: "WebSocket connection closed with OSError: [Errno 57] Socket is not connected"

Here's the client code (the ssl_context was created properly):

url = 'wss://127.0.0.1:8443/csms/ocpp_cs_001'

try:
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
        with ssl_context.wrap_socket(sock, server_hostname='127.0.0.1') as secure_sock:
            async with websockets.client.connect(
                    uri=url,
                    sock=secure_sock,
                    server_hostname='127.0.0.1') as websocket:
                global ws
                ws = websocket
                await asyncio.gather(on_open(websocket), on_message(websocket), on_close())
except (ConnectionClosedOK, ConnectionAbortedError, ConnectionClosedError, ConnectionRefusedError, ValueError, SSLCertVerificationError
        ConnectionResetError, InvalidStatusCode, InvalidHandshake, InvalidURI, SSLCertVerificationError, OSError) as err:
    logger.error(f'WebSocket connection closed with {type(err).__name__}: {err}')
finally:
    if ws is not None:
        await ws.close()

Here's the server part (the ssl_context was created properly):

hostname = '127.0.0.1'
port = 8443

try:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
        sock.bind((hostname, port))
        sock.listen(5)  # Queue up as many as 5 connect requests (the normal max)
        with ssl_context.wrap_socket(sock, server_side=True) as secure_sock:
            logger.info(f'Waiting for client connection at {hostname}:{port} ...')
            while True:
                client_sock, address = await secure_sock.accept()
                logger.info(f'Client connected. Address: {address}')
                server = await websockets.serve(handler, sock=client_sock)
                logger.info('Websocket server started')
                await server.wait_closed()
                logger.info('Websocket server closed')
except SSLError as err:
    logger.error(f'{type(err).__name__}: {err}')
    return

The server code is still awaiting the client socket to connect, so the code doesn't run past the line client_sock, address = await secure_sock.accept() I run both client and server on my laptop (with MacOS Catalina).

I'm glad for any help that points me in the right direction.

By the way: what does the third parameter '0' mean in socket.socket? Found it here: https://docs.python.org/3/library/ssl.html.

aaugustin commented 4 years ago

I'm sorry, I'm a bit at a loss about helping you here.

All this is handled by asyncio, a module from the standard library. websockets builds on top of asyncio but doesn't do anything with TLS (besides asking asyncio or ssl for reasonable defaults when establishing client connections to servers and the user didn't specify anything).

Could you show me how you would do what you want just with asyncio? Use any function you like to create a TLS connection and handle errors.

Once you do this, probably you have a socket that you can give to websockets. Then I can help if you still have trouble.

Your examples with the socket module are on the right track, but you should really do this with asyncio to make sure the socket is properly configured for asynchronous I/O. Look at create_connection or open_connection.