ssl — modulul SSL/TLS

Acest modul oferă acces la facilitățile de criptare și de autentificare a partenerilor de tip Transport Layer Security (cunoscut anterior și pe scară largă drept „Secure Sockets Layer”) pentru socket-uri de rețea, atât pe partea de client, cât și pe partea de server.

Sfat

Lucrezi pentru prima dată cu TLS pe cameră? Începe cu tutorialul Lucrul cu certificate TLS. Acesta te ghidează prin alegerea tipurilor de chei, crearea și conversia certificatelor în formatul DER cerut de cameră, transferul lor pe dispozitiv și verificarea serverelor și a clienților – cu exemple complete și funcționale.

Notă

MicroPython nu implementează ssl.SSLError. Eșecurile SSL/TLS sunt generate în schimb ca OSError.

Exemple

Client TLS, care verifică certificatul serverului față de un certificat CA (în format DER) stocat pe sistemul de fișiere:

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()

Pentru o conexiune rapidă și nesigură (fără validarea certificatului) se poate folosi în schimb funcția de comoditate ssl.wrap_socket()

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

Server TLS, care prezintă propriul certificat și cheie privată (format 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()

Funcții

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

Înfășoară sock-ul dat și returnează un nou obiect wrapped-socket. Implementarea acestei funcții constă în a crea mai întâi un SSLContext și apoi a apela metoda SSLContext.wrap_socket() pe acel obiect context. Argumentele sock, server_side și server_hostname sunt transmise neschimbate către apelul metodei. Argumentul do_handshake este transmis ca do_handshake_on_connect. Argumentele rămase au următorul comportament:

  • cert_reqs determină dacă partenerul (serverul sau clientul) trebuie să prezinte un certificat valid. Reține că ssl.CERT_NONE și ssl.CERT_OPTIONAL nu validează niciun certificat; numai ssl.CERT_REQUIRED face acest lucru.

  • cadata este un obiect bytes care conține lanțul de certificate CA (în format DER) ce va valida certificatul partenerului. În prezent este acceptat doar un singur certificat codificat DER.

Clase

class ssl.SSLContext(protocol: int, /)

Creează o nouă instanță SSLContext. Argumentul protocol trebuie să fie una dintre constantele PROTOCOL_*.

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

Încarcă o cheie privată și certificatul corespunzător. certfile este un șir cu calea fișierului certificatului. keyfile este un șir cu calea fișierului cheii private.

Diferență față de CPython

Extensie MicroPython: certfile și keyfile pot fi obiecte bytes în loc de șiruri, caz în care sunt interpretate ca datele efective ale certificatului/cheii.

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

Încarcă lanțul de certificate CA care va valida certificatul partenerului. cafile este calea fișierului cu certificatele CA. cadata este un obiect bytes care conține certificatele CA. Trebuie furnizat doar unul dintre aceste argumente.

get_ciphers() List[str]

Obține o listă a cifrurilor activate, returnată ca o listă de șiruri.

set_ciphers(ciphers: List[str]) None

Setează cifrurile disponibile pentru socket-urile create cu acest context. ciphers trebuie să fie o listă de șiruri în formatul IANA pentru suite de cifruri .

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

Preia un stream sock (de obicei o instanță socket.socket de tip SOCK_STREAM) și returnează o instanță de ssl.SSLSocket, care înfășoară stream-ul subiacent. Obiectul returnat are metodele uzuale ale interfeței stream, precum read(), write() etc.

  • server_side selectează dacă socket-ul înfășurat se află pe partea de server sau de client. Un socket SSL pe partea de server ar trebui creat dintr-un socket normal returnat de accept() pe un socket de server non-SSL aflat în ascultare.

  • do_handshake_on_connect determină dacă handshake-ul este efectuat ca parte a wrap_socket sau dacă este amânat pentru a fi efectuat ca parte a citirilor sau scrierilor inițiale. Pentru socket-urile cu blocare, efectuarea imediată a handshake-ului este practica standard. Pentru socket-urile fără blocare (adică atunci când sock-ul transmis către wrap_socket este în mod fără blocare), handshake-ul ar trebui de regulă amânat, deoarece altfel wrap_socket se blochează până la finalizarea acestuia.

  • server_hostname este destinat utilizării ca client și setează numele de gazdă care va fi verificat față de certificatul de server primit. De asemenea, setează numele pentru Server Name Indication (SNI), permițând serverului să prezinte certificatul corespunzător.

  • client_id este un argument de extensie specific MicroPython, folosit numai la implementarea unui server DTLS. Vezi Suport DTLS pentru detalii.

Atenționare

În mod implicit nu se efectuează nicio validare a certificatului (ssl.CERT_NONE). Pentru o conexiune sigură trebuie să verifici certificatul partenerului setând cert_reqs / SSLContext.verify_mode la ssl.CERT_REQUIRED; în caz contrar, conexiunea este vulnerabilă la atacuri de tip man-in-the-middle.

Funcția wrap_socket din CPython returnează un obiect SSLSocket care are metode tipice pentru socket-uri, precum send, recv etc. Funcția wrap_socket din MicroPython returnează un obiect mai asemănător cu SSLObject din CPython, care nu are aceste metode de socket.

verify_mode

Setează sau obține comportamentul de verificare a certificatelor partenerilor. Trebuie să fie una dintre constantele CERT_*.

Notă

ssl.CERT_REQUIRED necesită ca data/ora dispozitivului să fie setate corect, de exemplu folosind mpremote rtc --set sau ntptime, iar server_hostname trebuie specificat atunci când se află pe partea de client.

Suport DTLS

Diferență față de CPython

Aceasta este o extensie MicroPython.

Acest modul acceptă DTLS în mod client și server prin constantele PROTOCOL_DTLS_CLIENT și PROTOCOL_DTLS_SERVER, care pot fi folosite ca argument protocol al SSLContext.

În acest caz se așteaptă ca socket-ul subiacent să se comporte ca un socket de datagrame (adică precum socket-ul deschis cu socket.socket având socket.AF_INET ca af și socket.SOCK_DGRAM ca type).

Suport pentru server DTLS

Suportul de server DTLS din MicroPython este configurat cu „Hello Verify”, așa cum este cerut pentru DTLS 1.2. Acest lucru este transparent pentru clienții DTLS, dar există considerații relevante atunci când implementezi un server DTLS în MicroPython:

  • Serverul ar trebui să transmită un argument suplimentar client_id la apelarea SSLContext.wrap_socket(). Acest ID trebuie să fie un obiect bytes (sau similar) cu un identificator specific transportului care reprezintă clientul.

    Cea mai simplă abordare este de a converti tuplul (client_ip, client_port) returnat de socket.recv_from() într-un șir de octeți, adică:

    _, 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())
    
  • Prima dată când un client se conectează, apelul serverului către wrap_socket va eșua cu o eroare OSError „Hello Verify Required”. Acest lucru se întâmplă deoarece cookie-ul DTLS „Hello Verify” nu este încă cunoscut de client. Dacă același client se conectează a doua oară, atunci wrap_socket va reuși.

  • Cookie-urile DTLS pentru „Hello Verify” sunt asociate cu obiectul SSLContext, așa că același obiect SSLContext ar trebui folosit pentru a înfășura o conexiune ulterioară de la același client. Implementarea cookie-ului include un timeout și are un consum de memorie constant, indiferent câți clienți se conectează, așa că este în regulă să reutilizezi același obiect SSLContext pe toată durata de viață a serverului.

Constante

ssl.PROTOCOL_TLS_CLIENT: int

Valoare acceptată pentru parametrul protocol, care selectează modul client TLS.

ssl.PROTOCOL_TLS_SERVER: int

Valoare acceptată pentru parametrul protocol, care selectează modul server TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Valoare acceptată pentru parametrul protocol, care selectează modul client DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Valoare acceptată pentru parametrul protocol, care selectează modul server DTLS.

ssl.CERT_NONE: int

Valoare acceptată pentru parametrul cert_reqs și pentru atributul SSLContext.verify_mode. Nu se efectuează nicio verificare a certificatului partenerului.

ssl.CERT_OPTIONAL: int

Valoare acceptată pentru parametrul cert_reqs și pentru atributul SSLContext.verify_mode. Verificarea certificatului este opțională. Reține că pe OpenMV Cam aceasta se comportă ca ssl.CERT_NONE.

ssl.CERT_REQUIRED: int

Valoare acceptată pentru parametrul cert_reqs și pentru atributul SSLContext.verify_mode. De la partener se cere un certificat valid.