PSA: Python SSL wrap_socket has a bad default behavior for servers
The SSLContext.wrap_socket method has an optional do_handshake_on_connect
argument. When True
(the default), it performs the TLS handshake (where the client and server agree on a protocol, exchange certificates, etc) directly after the socket is connected.
This is fine for client sockets (as long as you are prepared to handle SSL errors when calling socket.connect
— they are a subclass of OSError
so that is not too difficult). But on server sockets, the handshake is performed on the newly connected socket returned by socket.accept, before it’s actually returned.
This causes two problems:
- If a client fails the TLS handshake,
accept
will throw aSSLError
. You can catch it, but you will not know the address of the client that caused the error, because this address is normally returned byaccept
andaccept
threw instead of returning. - If a client connects and then proceeds to do nothing, the SSL library will wait indefinitely for the handshake to proceed. Your code stays blocked in
accept
and you cannot handle more clients while this happens! You may think that you could set a timeout on the listening socket, but this timeout is not transferred to the connected socket.
Once you know that, the solution is simple:
- Pass
do_handshake_on_connect=False
when wrapping the socket - Once
accept
returns a socket, set a timeout on it (or set it to non-blocking and use asynchronous I/O), then calldo_handshake
. If that fails with aSSLError
(or a socket error…), you can properly log it sinceaccept
returned the address of the client.