ssl — modulo SSL/TLS

Questo modulo fornisce l’accesso alle funzionalità di crittografia e autenticazione dei peer del Transport Layer Security (precedentemente e ampiamente noto come “Secure Sockets Layer”) per i socket di rete, sia lato client che lato server.

Suggerimento

Sei alle prime armi con TLS sulla camera? Inizia dal tutorial Lavorare con i certificati TLS. Ti guida nella scelta dei tipi di chiave, nella creazione e conversione dei certificati nel formato DER richiesto dalla camera, nel trasferimento sul dispositivo e nella verifica di server e client – con esempi completi e funzionanti.

Nota

MicroPython non implementa ssl.SSLError. I fallimenti SSL/TLS vengono invece sollevati come OSError.

Esempi

Client TLS, che verifica il certificato del server rispetto a un certificato CA (in formato DER) memorizzato sul filesystem:

import socket
import ssl
import ntptime

# CERT_REQUIRED checks the certificate's validity dates, so the clock
# must be set (see the certificates tutorial linked above).
ntptime.settime()

# Open a plain TCP connection.
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket()
sock.connect(addr)

# Wrap it for TLS and require a valid certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="ca.der")
ssock = ctx.wrap_socket(sock, server_hostname="example.com")

ssock.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(ssock.read())
ssock.close()

Per una connessione rapida e non sicura (senza convalida del certificato) è possibile usare invece la funzione di comodità ssl.wrap_socket()

ssock = ssl.wrap_socket(sock, server_hostname="example.com")

Server TLS, che presenta il proprio certificato e la chiave privata (formato DER):

import socket
import ssl

sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(socket.getaddrinfo("0.0.0.0", 8443)[0][-1])
sock.listen(1)

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key")

while True:
    client, addr = sock.accept()
    sclient = ctx.wrap_socket(client, server_side=True)
    sclient.write(b"hello\n")
    sclient.close()

Funzioni

ssl.wrap_socket(sock: Any, server_side: bool = False, key: bytes | None = None, cert: bytes | None = None, cert_reqs: int = CERT_NONE, cadata: bytes | None = None, server_hostname: str | None = None, do_handshake: bool = True) Any

Incapsula il sock fornito e restituisce un nuovo oggetto wrapped-socket. L’implementazione di questa funzione consiste nel creare prima un SSLContext e poi chiamare il metodo SSLContext.wrap_socket() su tale oggetto context. Gli argomenti sock, server_side e server_hostname vengono passati invariati alla chiamata del metodo. L’argomento do_handshake viene passato come do_handshake_on_connect. Gli argomenti rimanenti hanno il seguente comportamento:

  • cert_reqs determina se il peer (server o client) debba presentare un certificato valido. Nota che ssl.CERT_NONE e ssl.CERT_OPTIONAL non convalidano alcun certificato; solo ssl.CERT_REQUIRED lo fa.

  • cadata è un oggetto bytes che contiene la catena di certificati CA (in formato DER) che convaliderà il certificato del peer. Attualmente è supportato un solo certificato codificato in DER.

Classi

class ssl.SSLContext(protocol: int, /)

Crea una nuova istanza di SSLContext. L’argomento protocol deve essere una delle costanti PROTOCOL_*.

load_cert_chain(certfile: str | bytes, keyfile: str | bytes) None

Carica una chiave privata e il certificato corrispondente. certfile è una stringa con il percorso del file del certificato. keyfile è una stringa con il percorso del file della chiave privata.

Differenza rispetto a CPython

Estensione di MicroPython: certfile e keyfile possono essere oggetti bytes anziché stringhe, nel qual caso vengono interpretati come i dati effettivi del certificato/della chiave.

load_verify_locations(cafile: str | None = None, cadata: bytes | None = None) None

Carica la catena di certificati CA che convaliderà il certificato del peer. cafile è il percorso del file dei certificati CA. cadata è un oggetto bytes che contiene i certificati CA. Deve essere fornito solo uno di questi argomenti.

get_ciphers() List[str]

Ottiene un elenco delle cifre abilitate, restituito come elenco di stringhe.

set_ciphers(ciphers: List[str]) None

Imposta le cifre disponibili per i socket creati con questo context. ciphers deve essere un elenco di stringhe nel formato IANA cipher suite .

wrap_socket(sock: Any, *, server_side: bool = False, do_handshake_on_connect: bool = True, server_hostname: str | None = None, client_id: bytes | None = None) Any

Prende uno sock di tipo stream (di solito un’istanza socket.socket di tipo SOCK_STREAM) e restituisce un’istanza di ssl.SSLSocket, che incapsula lo stream sottostante. L’oggetto restituito ha i consueti metodi dell’interfaccia stream come read(), write(), ecc.

  • server_side seleziona se il socket incapsulato si trovi lato server o lato client. Un socket SSL lato server dovrebbe essere creato a partire da un socket normale restituito da accept() su un socket di ascolto non SSL.

  • do_handshake_on_connect determina se l’handshake venga eseguito come parte di wrap_socket o se venga differito per essere eseguito come parte delle letture o scritture iniziali. Per i socket bloccanti è standard eseguire l’handshake immediatamente. Per i socket non bloccanti (cioè quando il sock passato a wrap_socket è in modalità non bloccante) l’handshake dovrebbe generalmente essere differito, altrimenti wrap_socket si blocca fino al suo completamento.

  • server_hostname è destinato all’uso come client e imposta il nome host da verificare rispetto al certificato del server ricevuto. Imposta inoltre il nome per il Server Name Indication (SNI), consentendo al server di presentare il certificato corretto.

  • client_id è un argomento di estensione specifico di MicroPython usato solo quando si implementa un server DTLS. Vedi Supporto DTLS per i dettagli.

Avvertimento

Per impostazione predefinita non viene eseguita alcuna convalida del certificato (ssl.CERT_NONE). Per una connessione sicura devi verificare il certificato del peer impostando cert_reqs / SSLContext.verify_mode su ssl.CERT_REQUIRED; in caso contrario la connessione è vulnerabile ad attacchi man-in-the-middle.

Il wrap_socket di CPython restituisce un oggetto SSLSocket che possiede metodi tipici dei socket, come send, recv, ecc. Il wrap_socket di MicroPython restituisce un oggetto più simile all”SSLObject di CPython, che non possiede questi metodi dei socket.

verify_mode

Imposta o ottiene il comportamento per la verifica dei certificati dei peer. Deve essere una delle costanti CERT_*.

Nota

ssl.CERT_REQUIRED richiede che la data/ora del dispositivo sia impostata correttamente, ad esempio usando mpremote rtc --set o ntptime, e che server_hostname sia specificato quando si è lato client.

Supporto DTLS

Differenza rispetto a CPython

Questa è un’estensione di MicroPython.

Questo modulo supporta DTLS in modalità client e server tramite le costanti PROTOCOL_DTLS_CLIENT e PROTOCOL_DTLS_SERVER che possono essere usate come argomento protocol di SSLContext.

In questo caso ci si aspetta che il socket sottostante si comporti come un socket datagram (cioè come il socket aperto con socket.socket con socket.AF_INET come af e socket.SOCK_DGRAM come type).

Supporto server DTLS

Il supporto server DTLS di MicroPython è configurato con «Hello Verify» come richiesto per DTLS 1.2. Questo è trasparente per i client DTLS, ma vi sono considerazioni rilevanti quando si implementa un server DTLS in MicroPython:

  • Il server dovrebbe passare un argomento aggiuntivo client_id quando chiama SSLContext.wrap_socket(). Questo ID deve essere un oggetto bytes (o simile) con un identificatore specifico del trasporto che rappresenti il client.

    L’approccio più semplice consiste nel convertire la tupla (client_ip, client_port) restituita da socket.recv_from() in una stringa di byte, cioè:

    _, client_addr = sock.recvfrom(1, socket.MSG_PEEK)
    sock.connect(client_addr)  # Connect back to the client
    sock = ssl_ctx.wrap_socket(sock, server_side=True,
                               client_id=repr(client_addr).encode())
    
  • La prima volta che un client si connette, la chiamata del server a wrap_socket fallirà con un errore OSError «Hello Verify Required». Questo accade perché il cookie DTLS «Hello Verify» non è ancora noto al client. Se lo stesso client si connette una seconda volta, allora wrap_socket avrà successo.

  • I cookie DTLS per «Hello Verify» sono associati all’oggetto SSLContext, quindi lo stesso oggetto SSLContext dovrebbe essere usato per incapsulare una connessione successiva dello stesso client. L’implementazione dei cookie include un timeout e ha un utilizzo costante della memoria indipendentemente da quanti client si connettono, quindi va bene riutilizzare lo stesso oggetto SSLContext per tutta la durata del server.

Costanti

ssl.PROTOCOL_TLS_CLIENT: int

Valore supportato per il parametro protocol, che seleziona la modalità client TLS.

ssl.PROTOCOL_TLS_SERVER: int

Valore supportato per il parametro protocol, che seleziona la modalità server TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Valore supportato per il parametro protocol, che seleziona la modalità client DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Valore supportato per il parametro protocol, che seleziona la modalità server DTLS.

ssl.CERT_NONE: int

Valore supportato per il parametro cert_reqs e per l’attributo SSLContext.verify_mode. Non viene eseguita alcuna verifica del certificato sul peer.

ssl.CERT_OPTIONAL: int

Valore supportato per il parametro cert_reqs e per l’attributo SSLContext.verify_mode. La verifica del certificato è opzionale. Nota che sulla OpenMV Cam questo si comporta come ssl.CERT_NONE.

ssl.CERT_REQUIRED: int

Valore supportato per il parametro cert_reqs e per l’attributo SSLContext.verify_mode. È richiesto un certificato valido dal peer.