14.4.3. Zelfondertekende certificaten#

Een zelfondertekend certificaat is de snelste manier om TLS werkend te krijgen tussen twee apparaten die je beheert: beide uiteinden vertrouwen één enkel certificaat dat je zelf genereert. Het dekt elke uitrol waarbij je beide zijden van de verbinding configureert – een openbare Certificate Authority komt pas in beeld wanneer externe clients moeten verbinden zonder te worden verteld een aangepast certificaat te vertrouwen.

14.4.3.1. Een zelfondertekend certificaat aanmaken#

Voer OpenSSL uit op je ontwikkelmachine. De subjectAltName (SAN) is wat moderne TLS-clients controleren tijdens hostnaamverificatie, dus stel die in op de hostnaam(en) en/of het IP-adres(sen) die clients zullen gebruiken om de camera te bereiken (CN alleen is verouderd en wordt door veel clients genegeerd). Vervang DNS:openmv / IP:192.168.1.50 door het adres waarmee je clients daadwerkelijk verbinden.

ECDSA P-256 – aanbevolen:

# 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 – sterker, groter/langzamer:

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 – maximale compatibiliteit:

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"

Notitie

Een client-certificaat (gebruikt voor wederzijdse authenticatie, hieronder) wordt aangemaakt met exact dezelfde opdrachten – er is niets clientspecifieks aan het certificaat zelf. Genereer gewoon een tweede, onafhankelijk sleutel/certificaat-paar onder andere namen (bijv. client.key / client.crt) en gebruik het op de client zoals getoond in het mTLS-voorbeeld. De subjectAltName is alleen van belang voor de zijde wiens hostnaam de peer verifieert (de client controleert de naam van de server; niets controleert die van de client), dus kan worden weggelaten voor een uitsluitend client-certificaat. De -subj / CN is eveneens slechts een label op een clientcertificaat – de serverzijde controleert hier alleen of het certificaat naar een vertrouwde CA herleidt, hij matcht de naam nooit – dus stel die in op wat die client ook maar identificeert (bijv. /CN=sensor-01). Houd hoe dan ook een -subj-waarde aan, zodat OpenSSL het certificaat niet-interactief kan genereren.

De levensduur van het certificaat wordt ingesteld met -days; certificaten verlopen en moeten daarvoor opnieuw worden gegenereerd en uitgerold.

14.4.3.2. Converteren naar DER#

Converteer zowel het certificaat als de privésleutel naar DER voordat je ze naar de camera kopieert:

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. Bestanden naar de camera kopiëren#

Kopieer de DER-bestanden naar het bestandssysteem van de camera – bijvoorbeeld door ze naar de USB-schijf van de OpenMV Cam te slepen, of met mpremote cp server.der : en mpremote cp server.key.der :. Kopieer aan de verifiërende zijde ook het CA-/peer-certificaat in DER-vorm.

De DER-bestanden hoeven niet op het beschrijfbare bestandssysteem te staan. MicroPython kan ook een alleen-lezen ROMFS-image aankoppelen op /rom, en daar geplaatste certificaten worden precies zo geladen als elk ander bestand – bijv. ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). Een ROMFS-image wordt voorbereid op je ontwikkelmachine en is alleen-lezen tijdens runtime, dus het certificaat kan niet op het apparaat worden gewijzigd – handig om een productie-eenheid te vergrendelen. Let op dat een in ROMFS opgeslagen privésleutel nog steeds leesbaar is voor code die op de camera draait; ROMFS beschermt tegen wijziging, niet tegen uitlezing. Een in ROMFS aanwezig certificaat kan alleen worden vervangen door het image opnieuw te bouwen en te flashen.

14.4.3.4. Het certificaat gebruiken#

Een volledige client die de klok instelt, een socket opent, een zelfondertekende server verifieert en gegevens uitwisselt:

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

Een volledige server die zijn certificaat en sleutel aanbiedt:

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

Voor wederzijdse authenticatie (mTLS) vereist en verifieert de server bovendien een clientcertificaat, en biedt de client er een van zichzelf aan:

# 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")

Zie de documentatie van de ssl-module voor de volledige API.

Notitie

Alles op deze pagina is ongewijzigd van toepassing op DTLS (TLS over UDP). De sleutels, certificaten, het DER-formaat, het vertrouwensmodel, de verloopkwesties en de load_cert_chain- / load_verify_locations-aanroepen zijn identiek; alleen het transport verschilt – je omhult een socket.SOCK_DGRAM-socket en selecteert ssl.PROTOCOL_DTLS_CLIENT / ssl.PROTOCOL_DTLS_SERVER in plaats van de TLS-protocolconstanten. De enige extra complicatie is een anti-spoofing-cookie aan de serverzijde – van de eerste verbinding van een nieuwe client wordt verwacht dat die mislukt en de client probeert het simpelweg opnieuw; zie DTLS-ondersteuning voor details.