ssl — moduł SSL/TLS

Ten moduł zapewnia dostęp do mechanizmów szyfrowania Transport Layer Security (wcześniej i powszechnie znanego jako „Secure Sockets Layer”) oraz uwierzytelniania drugiej strony dla gniazd sieciowych, zarówno po stronie klienta, jak i serwera.

Wskazówka

Dopiero zaczynasz z TLS na kamerze? Zacznij od samouczka Praca z certyfikatami TLS. Przeprowadza on przez wybór typów kluczy, tworzenie i konwersję certyfikatów do formatu DER wymaganego przez kamerę, przeniesienie ich na urządzenie oraz weryfikację serwerów i klientów – wraz z kompletnymi, działającymi przykładami.

Informacja

MicroPython nie implementuje ssl.SSLError. Błędy SSL/TLS są zgłaszane jako OSError.

Przykłady

Klient TLS, weryfikujący certyfikat serwera względem certyfikatu CA (w formacie DER) zapisanego w systemie plików:

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

W przypadku szybkiego, niezabezpieczonego połączenia (bez walidacji certyfikatu) można zamiast tego użyć funkcji pomocniczej ssl.wrap_socket()

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

Serwer TLS, prezentujący własny certyfikat i klucz prywatny (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()

Funkcje

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

Opakowuje podane gniazdo sock i zwraca nowy obiekt opakowanego gniazda. Implementacja tej funkcji polega na utworzeniu najpierw obiektu SSLContext, a następnie wywołaniu metody SSLContext.wrap_socket() na tym obiekcie kontekstu. Argumenty sock, server_side i server_hostname są przekazywane do wywołania metody bez zmian. Argument do_handshake jest przekazywany jako do_handshake_on_connect. Pozostałe argumenty mają następujące działanie:

  • cert_reqs określa, czy druga strona (serwer lub klient) musi przedstawić prawidłowy certyfikat. Należy pamiętać, że ssl.CERT_NONE i ssl.CERT_OPTIONAL nie walidują żadnego certyfikatu; robi to tylko ssl.CERT_REQUIRED.

  • cadata to obiekt bytes zawierający łańcuch certyfikatów CA (w formacie DER), który posłuży do walidacji certyfikatu drugiej strony. Obecnie obsługiwany jest tylko pojedynczy certyfikat zakodowany w DER.

Klasy

class ssl.SSLContext(protocol: int, /)

Tworzy nową instancję SSLContext. Argument protocol musi być jedną ze stałych PROTOCOL_*.

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

Wczytuje klucz prywatny i odpowiadający mu certyfikat. certfile to łańcuch znaków ze ścieżką pliku certyfikatu. keyfile to łańcuch znaków ze ścieżką pliku klucza prywatnego.

Różnica względem CPythona

Rozszerzenie MicroPython: certfile i keyfile mogą być obiektami bytes zamiast łańcuchów znaków, w którym to przypadku są interpretowane jako rzeczywiste dane certyfikatu/klucza.

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

Wczytuje łańcuch certyfikatów CA, który posłuży do walidacji certyfikatu drugiej strony. cafile to ścieżka pliku certyfikatów CA. cadata to obiekt bytes zawierający certyfikaty CA. Należy podać tylko jeden z tych argumentów.

get_ciphers() List[str]

Pobiera listę włączonych szyfrów, zwracaną jako lista łańcuchów znaków.

set_ciphers(ciphers: List[str]) None

Ustawia dostępne szyfry dla gniazd tworzonych z tym kontekstem. ciphers powinno być listą łańcuchów znaków w formacie pakietów szyfrów IANA .

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

Przyjmuje strumień stream sock (zwykle instancję socket.socket typu SOCK_STREAM) i zwraca instancję ssl.SSLSocket, opakowującą bazowy strumień. Zwrócony obiekt posiada zwykłe metody interfejsu stream, takie jak read(), write() itd.

  • server_side wybiera, czy opakowane gniazdo znajduje się po stronie serwera, czy klienta. Gniazdo SSL po stronie serwera powinno być utworzone ze zwykłego gniazda zwróconego przez accept() na nasłuchującym gnieździe serwera bez SSL.

  • do_handshake_on_connect określa, czy uzgadnianie (handshake) jest wykonywane w ramach wrap_socket, czy też jest odroczone do wykonania w ramach początkowych odczytów lub zapisów. W przypadku gniazd blokujących standardem jest natychmiastowe wykonanie uzgadniania. W przypadku gniazd nieblokujących (tj. gdy gniazdo sock przekazane do wrap_socket jest w trybie nieblokującym) uzgadnianie powinno być zwykle odroczone, ponieważ w przeciwnym razie wrap_socket blokuje aż do jego zakończenia.

  • server_hostname jest przeznaczone do użytku jako klient i ustawia nazwę hosta do sprawdzenia względem otrzymanego certyfikatu serwera. Ustawia również nazwę dla Server Name Indication (SNI), pozwalając serwerowi przedstawić właściwy certyfikat.

  • client_id to argument rozszerzenia specyficzny dla MicroPython, używany tylko przy implementacji serwera DTLS. Szczegóły zob. Obsługa DTLS.

Ostrzeżenie

Domyślnie nie jest wykonywana żadna walidacja certyfikatu (ssl.CERT_NONE). Aby uzyskać bezpieczne połączenie, należy zweryfikować certyfikat drugiej strony, ustawiając cert_reqs / SSLContext.verify_mode na ssl.CERT_REQUIRED; w przeciwnym razie połączenie jest podatne na ataki typu man-in-the-middle.

wrap_socket w CPythonie zwraca obiekt SSLSocket, który ma metody typowe dla gniazd, takie jak send, recv itd. wrap_socket w MicroPython zwraca obiekt bardziej podobny do SSLObject z CPythona, który nie posiada tych metod gniazda.

verify_mode

Ustawia lub pobiera zachowanie weryfikacji certyfikatów drugiej strony. Musi być jedną ze stałych CERT_*.

Informacja

ssl.CERT_REQUIRED wymaga prawidłowego ustawienia daty/czasu urządzenia, np. za pomocą mpremote rtc --set lub ntptime, a po stronie klienta musi być podana wartość server_hostname.

Obsługa DTLS

Różnica względem CPythona

To jest rozszerzenie MicroPython.

Ten moduł obsługuje DTLS w trybie klienta i serwera za pomocą stałych PROTOCOL_DTLS_CLIENT i PROTOCOL_DTLS_SERVER, których można użyć jako argumentu protocol klasy SSLContext.

W tym przypadku oczekuje się, że bazowe gniazdo zachowuje się jak gniazdo datagramowe (tj. jak gniazdo otwarte przez socket.socket z socket.AF_INET jako af i socket.SOCK_DGRAM jako type).

Obsługa serwera DTLS

Obsługa serwera DTLS w MicroPython jest skonfigurowana z „Hello Verify” zgodnie z wymaganiami DTLS 1.2. Jest to przezroczyste dla klientów DTLS, ale przy implementacji serwera DTLS w MicroPython należy uwzględnić pewne istotne kwestie:

  • Serwer powinien przekazać dodatkowy argument client_id przy wywoływaniu SSLContext.wrap_socket(). Ten identyfikator musi być obiektem bytes (lub podobnym) zawierającym specyficzny dla transportu identyfikator reprezentujący klienta.

    Najprostszym podejściem jest konwersja krotki (client_ip, client_port) zwróconej przez socket.recv_from() na łańcuch bajtów, tj.:

    _, 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())
    
  • Przy pierwszym połączeniu klienta wywołanie wrap_socket po stronie serwera zakończy się niepowodzeniem z błędem OSError „Hello Verify Required”. Dzieje się tak, ponieważ ciasteczko „Hello Verify” DTLS nie jest jeszcze znane klientowi. Jeśli ten sam klient połączy się po raz drugi, wrap_socket zakończy się powodzeniem.

  • Ciasteczka DTLS dla „Hello Verify” są powiązane z obiektem SSLContext, więc do opakowania kolejnego połączenia od tego samego klienta należy użyć tego samego obiektu SSLContext. Implementacja ciasteczek obejmuje limit czasu i ma stałe zużycie pamięci niezależnie od liczby łączących się klientów, więc można bezpiecznie używać tego samego obiektu SSLContext przez cały okres działania serwera.

Stałe

ssl.PROTOCOL_TLS_CLIENT: int

Obsługiwana wartość parametru protocol, wybierająca tryb klienta TLS.

ssl.PROTOCOL_TLS_SERVER: int

Obsługiwana wartość parametru protocol, wybierająca tryb serwera TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Obsługiwana wartość parametru protocol, wybierająca tryb klienta DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Obsługiwana wartość parametru protocol, wybierająca tryb serwera DTLS.

ssl.CERT_NONE: int

Obsługiwana wartość parametru cert_reqs oraz atrybutu SSLContext.verify_mode. Nie jest wykonywana żadna weryfikacja certyfikatu drugiej strony.

ssl.CERT_OPTIONAL: int

Obsługiwana wartość parametru cert_reqs oraz atrybutu SSLContext.verify_mode. Weryfikacja certyfikatu jest opcjonalna. Należy pamiętać, że na OpenMV Cam zachowuje się to jak ssl.CERT_NONE.

ssl.CERT_REQUIRED: int

Obsługiwana wartość parametru cert_reqs oraz atrybutu SSLContext.verify_mode. Wymagany jest prawidłowy certyfikat od drugiej strony.