14.4.3. Certyfikaty self-signed

Certyfikat self-signed to najszybszy sposób na uruchomienie TLS między dwoma urządzeniami, które kontrolujesz: oba końce ufają jednemu certyfikatowi, który sam generujesz. Obejmuje on każde wdrożenie, w którym konfigurujesz obie strony połączenia – publiczny urząd certyfikacji wchodzi w grę dopiero wtedy, gdy klienci zewnętrzni muszą się połączyć bez polecenia, by zaufali niestandardowemu certyfikatowi.

14.4.3.1. Tworzenie certyfikatu self-signed

Uruchom OpenSSL na swojej maszynie deweloperskiej. subjectAltName (SAN) to to, co nowoczesne klienty TLS sprawdzają podczas weryfikacji nazwy hosta, więc ustaw je na nazwę(y) hosta i/lub adres(y) IP, których klienci będą używać, by dotrzeć do kamery (samo CN jest przestarzałe i ignorowane przez wiele klientów). Zastąp DNS:openmv / IP:192.168.1.50 adresem, z którym twoi klienci faktycznie się łączą.

ECDSA P-256 – zalecane:

# Generate a P-256 private key.
openssl ecparam -name prime256v1 -genkey -noout -out server.key

# Self-signed certificate valid for one year, with a SAN.
openssl req -new -x509 -key server.key -out server.crt -days 365 \
    -subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"

ECDSA P-384 – silniejsze, większe/wolniejsze:

openssl ecparam -name secp384r1 -genkey -noout -out server.key

openssl req -new -x509 -key server.key -out server.crt -days 365 \
    -subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"

RSA-2048 – maksymalna kompatybilność:

openssl req -new -x509 -newkey rsa:2048 -nodes -keyout server.key \
    -out server.crt -days 365 -subj "/CN=openmv" \
    -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"

Informacja

Certyfikat klienta (używany do wzajemnego uwierzytelniania, poniżej) tworzy się dokładnie tymi samymi poleceniami – w samym certyfikacie nie ma niczego specyficznego dla klienta. Wystarczy wygenerować drugą, niezależną parę klucz/certyfikat pod innymi nazwami (np. client.key / client.crt) i użyć jej po stronie klienta, jak pokazano w przykładzie mTLS. subjectAltName ma znaczenie tylko dla strony, której nazwę hosta peer weryfikuje (klient sprawdza nazwę serwera; nic nie sprawdza nazwy klienta), więc dla certyfikatu wyłącznie klienckiego można je pominąć. -subj / CN jest podobnie jedynie etykietą na certyfikacie klienta – strona serwera sprawdza tu tylko, czy certyfikat prowadzi łańcuchem do zaufanego CA, nigdy nie dopasowuje nazwy – więc ustaw je na cokolwiek, co identyfikuje danego klienta (np. /CN=sensor-01). Tak czy inaczej zachowaj jakąś wartość -subj, aby OpenSSL mógł wygenerować certyfikat nieinteraktywnie.

Czas życia certyfikatu ustawia się za pomocą -days; certyfikaty wygasają i przed tym terminem muszą zostać ponownie wygenerowane i wdrożone.

14.4.3.2. Konwersja do DER

Przekonwertuj zarówno certyfikat, jak i klucz prywatny do DER przed skopiowaniem ich na kamerę:

openssl x509 -in server.crt -outform DER -out server.der
openssl pkey -in server.key -outform DER -out server.key.der

14.4.3.3. Kopiowanie plików na kamerę

Skopiuj pliki DER do systemu plików kamery – na przykład przeciągając je na dysk USB OpenMV Cam lub za pomocą mpremote cp server.der : oraz mpremote cp server.key.der :. Po stronie weryfikującej skopiuj również certyfikat CA / peera w postaci DER.

Pliki DER nie muszą znajdować się w zapisywalnym systemie plików. MicroPython może również zamontować obraz ROMFS tylko do odczytu pod /rom, a certyfikaty umieszczone tam są wczytywane dokładnie jak każdy inny plik – np. ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). Obraz ROMFS przygotowuje się na maszynie deweloperskiej i jest on tylko do odczytu w czasie działania, więc certyfikatu nie da się zmienić na urządzeniu – przydatne do zablokowania egzemplarza produkcyjnego. Zauważ, że klucz prywatny przechowywany w ROMFS nadal jest odczytywalny przez kod działający na kamerze; ROMFS chroni przed modyfikacją, a nie wydobyciem. Certyfikat rezydujący w ROMFS można wymienić tylko przez przebudowanie i ponowne wgranie obrazu.

14.4.3.4. Użycie certyfikatu

Kompletny klient, który ustawia zegar, otwiera gniazdo, weryfikuje serwer self-signed i wymienia dane:

import socket
import ssl
import ntptime

ntptime.settime()                 # correct clock for the validity check

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

# Wrap it for TLS, trusting the server's self-signed certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
ssock = ctx.wrap_socket(sock, server_hostname="openmv")

ssock.write(b"hello\n")
print(ssock.read())
ssock.close()

Kompletny serwer przedstawiający swój certyfikat i klucz:

import socket
import ssl
import ntptime

ntptime.settime()                 # correct clock for the validity check

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

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)

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

W przypadku wzajemnego uwierzytelniania (mTLS) serwer dodatkowo wymaga i weryfikuje certyfikat klienta, a klient przedstawia swój własny:

# Server side: also demand and verify a client certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="client.der")

# Client side: present a certificate of our own.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_cert_chain("client.der", "client.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")

Pełne API znajdziesz w dokumentacji modułu ssl.

Informacja

Wszystko na tej stronie odnosi się bez zmian do DTLS (TLS po UDP). Klucze, certyfikaty, format DER, model zaufania, kwestie wygasania oraz wywołania load_cert_chain / load_verify_locations są identyczne; różni się tylko transport – opakowujesz gniazdo socket.SOCK_DGRAM i wybierasz ssl.PROTOCOL_DTLS_CLIENT / ssl.PROTOCOL_DTLS_SERVER zamiast stałych protokołu TLS. Jedynym dodatkowym niuansem jest serwerowe ciasteczko anty-spoofingowe – oczekuje się, że pierwsze połączenie od nowego klienta zakończy się niepowodzeniem, a klient po prostu ponawia próbę; szczegóły zobacz w Obsługa DTLS.