Search code examples
pythonsslopensslmicropython

TLS communication between Python 3.11 and MicroPython 1.20 - fails with [SSL: NO_SHARED_CIPHER] no shared cipher


I'm trying to send text between this to devices. One has a Python 3.11 (Server) and the other one a Micropython 1.20 (Client). Both devices have their own key and the server has a server-cert. Both Keys where created with:

openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout server-key.pem -out server-cert.pem```

I also try, as mentioned at the documentation of Micropython, to convert the cert of the server into the DER format.

openssl.exe x509 -in server-cert.pem -out server-cert.der -outform DER

My client code:

# main.py -- put your code here!
import usocket as socket
import ussl as ssl

# Server-IP-Adresse und Port
server_ip = '192.168.178.67'
server_port = 2002
server_address = (server_ip, server_port)
server_ca = "/flash/cert/server-cert-test.der"
server_key = "/flash/cert/server-key.pem"

# Verbindung zum Server herstellen
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(server_address)

# SSL-Verbindung einrichten
ssl_context = ssl.wrap_socket(client_socket, cert=(server_ca, server_key))

# Daten senden
ssl_context.write("Hallo, Server!".encode('utf-8'))

# Antwort empfangen und dekodieren
response = ssl_context.read(1024)
print("Antwort vom Server:", response.decode('utf-8'))

# Verbindung schließen
ssl_context.close()
client_socket.close()

My Server Code:

from socket import socket
from ssl import wrap_socket

def main():
    server_certfile = "server-cert.pem"
    server_keyfile = "server-key.pem"

    s = socket()
    s.bind(('192.168.178.67', 2002))
    s.listen(5)
    wrap_socket(s.accept()[0], 'server-key.pem', 'server-cert.pem', True)

if __name__ == "__main__":
    main()

As I understand the Wireshark analysys, the Micropython says "hello" and offers TLS1.2. The Server thinks it's ok, but then they don`t get clear about, which cipher they should use. Python:

ssl.SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:992)
Micropython: OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')


6768    11:49:56,791402 192.168.178.126 192.168.178.67  TCP 60  55778 → 2002 [SYN] Seq=0 Win=6400 Len=0 MSS=800
6769    11:49:56,791478 192.168.178.67  192.168.178.126 TCP 58  2002 → 55778 [SYN, ACK] Seq=0 Ack=1 Win=64800 Len=0 MSS=1460
6770    11:49:56,791810 192.168.178.126 192.168.178.67  TCP 60  55778 → 2002 [ACK] Seq=1 Ack=1 Win=6400 Len=0
6771    11:49:57,118967 192.168.178.126 192.168.178.67  TLSv1.2 176 Client Hello
6772    11:49:57,119175 192.168.178.67  192.168.178.126 TLSv1.2 61  Alert (Level: Fatal, Description: Handshake Failure)
6773    11:49:57,119284 192.168.178.67  192.168.178.126 TCP 54  2002 → 55778 [FIN, ACK] Seq=8 Ack=123 Win=64678 Len=0
6774    11:49:57,119367 192.168.178.126 192.168.178.67  TCP 60  55778 → 2002 [ACK] Seq=123 Ack=9 Win=6392 Len=0

What a bummer!

I switched cert's and keys. Used different implementation and writing methods of wrap_socket. Wireshark was always with me...

Do I have mistakes in my implementation? Or isn't there a way yet for Python and MicroPython to communicate secure? Does anybody has an working implementation? Thanks in advance!

Edit: The TLS hadshake CLIENT_HELLO shows this supported ciphers:

enter image description here


Solution

  • It's because the server doesn't accept weak algorithms that the client suggested. You can check algorithms that the server supports using the SSLContext API like the following, instead of using the deprecated ssl.wrap_socket().

    ...
    from ssl import SSLContext
    ...
    def main():
        ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        for cipher in ssl_ctx.get_ciphers():
            print(cipher['name'])
        ssl_ctx.load_cert_chain(certfile = server_certfile, keyfile=server_keyfile)
        ...
        accepted_sock, _ = s.accept()
        ssl_sock = ssl_ctx.wrap_socket(accepted_sock, server_side=True)
        ...
    

    In your case, the client Micropython suggested TLS_ECDHE_ECDSA_* algorithms and TLS_RSA_* algorithms. But the server cannot choose TLS_ECDHE_ECDSA_* algorithms because you configured an RSA key and certificate. And probably the server also didn't choose TLS_RSA_* algorithms because they are weak and deprecated. (They do not use an ephemeral key.)

    There are two possible solutions. The first one is to use an ECDSA key and certificate. And the second one is to fix Micropython so it suggests TLS_ECDSA_RSA_* or TLS_DHE_RSA_* algorithms. I strongly recommend not to force the server to use TLS_RSA_* algorithms as in the comment on the question.

    As a side note, you need to do like the following in your client. (Use the cadata argument and pass the data instead of the file path.)

    import ussl as ssl
    ...
    with open(server_ca, 'rb') as f:
        server_ca_bytes = f.read()
    ssl_sock = ssl.wrap_socket(client_socket, cadata=server_ca_bytes)
    ...