How do I create and connect anonymous-DH TLS sockets with Python and securely authenticate over them without possibility of credential forwarding?

46 views Asked by At

I am writing a peer-to-peer protocol wherein nodes can connect to each other and the connection should be encrypted using TLS with session keys agreed with Diffie-Hellman exchange. However, using any kind of certificates would be tedious because after a TLS connection is established, each pair of nodes would still have to mutually authenticate using a ECDSA challenge-response protocol (this using a cipher-suite not supported by OpenSSL).

  1. How can I use Python standard library ssl module to make such a TLS connection that does not require setting up snake-oil certificates given that I can control the SSLContext on both client and server?

  2. How can I then authenticate using a challenge-response protocol after the TLS handshake has completed, in a way that would exclude the possibility of a Mallory using credential forwarding?

1

There are 1 answers

0
Antti Haapala -- Слава Україні On

For part 1, one possibility is to use OpenSSL cipher suite with AECDH for example AECDH-AES256-SHA. For server the code would be

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.set_ciphers("AECDH-AES256-SHA:@SECLEVEL=0")

with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    sock.bind(('127.0.0.1', 8443))
    sock.listen(5)
    with context.wrap_socket(sock, server_side=True) as ssock:
        conn, addr = ssock.accept()

and for client code you can use the following

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.set_ciphers("AECDH-AES256-SHA:@SECLEVEL=0")
context.check_hostname = False

with socket.create_connection(('localhost', 8443)) as sock:
    with context.wrap_socket(sock) as conn:
        ...

Notably, the :@SECLEVEL=0 needs to be specified for it to work.

For part 2 and the challenge-response protocol, you can use the channel binding function - the return value of

conn.get_channel_binding(cb_type='tls-unique')

as part of the signed messages in the challenge-response protocol. This is a 96-bit value.

However, the security of this solution is far from perfect. Notably, using the AECDH cipher seems to limit the connection to TLS 1.2, and there are attacks against the get_channel_binding(cb_type='tls-unique') that essentially halve the length of the value, i.e. the effective security would be only 48 bits.

In TLS 1.3 there would be option to derive a longer value, namely get_channel_binding with type tls-exporter, however it is not currently supported by Python 3 SSL module. On the other hand for TLS 1.3 it seems these anonymous Diffie-Hellman cipher suites are no longer a possibility.