14.4.3. Selbstsignierte Zertifikate#

Ein selbstsigniertes Zertifikat ist der schnellste Weg, TLS zwischen zwei von Ihnen kontrollierten Geräten zum Laufen zu bringen: Beide Enden vertrauen einem einzelnen Zertifikat, das Sie selbst erzeugen. Es deckt jede Bereitstellung ab, bei der Sie beide Seiten der Verbindung konfigurieren – eine öffentliche Zertifizierungsstelle kommt erst ins Spiel, wenn sich Drittanbieter-Clients verbinden müssen, ohne angewiesen zu werden, einem benutzerdefinierten Zertifikat zu vertrauen.

14.4.3.1. Ein selbstsigniertes Zertifikat erstellen#

Führen Sie OpenSSL auf Ihrem Entwicklungsrechner aus. Der subjectAltName (SAN) ist das, was moderne TLS-Clients bei der Hostnamen-Verifizierung prüfen, setzen Sie ihn also auf die Hostnamen und/oder IP-Adressen, über die Clients die Kamera erreichen (CN allein ist veraltet und wird von vielen Clients ignoriert). Ersetzen Sie DNS:openmv / IP:192.168.1.50 durch die Adresse, mit der sich Ihre Clients tatsächlich verbinden.

ECDSA P-256 – empfohlen:

# 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 – stärker, größer/langsamer:

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 Kompatibilität:

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"

Bemerkung

Ein Client-Zertifikat (verwendet für die gegenseitige Authentifizierung, siehe unten) wird mit genau denselben Befehlen erstellt – am Zertifikat selbst ist nichts Client-spezifisch. Erzeugen Sie einfach ein zweites, unabhängiges Schlüssel-/Zertifikatspaar unter anderen Namen (z. B. client.key / client.crt) und verwenden Sie es auf dem Client wie im mTLS-Beispiel gezeigt. Der subjectAltName ist nur für die Seite relevant, deren Hostnamen der Peer verifiziert (der Client prüft den Namen des Servers; den Client prüft niemand), daher kann er bei einem reinen Client-Zertifikat weggelassen werden. Das -subj / CN ist bei einem Client-Zertifikat ebenfalls nur eine Bezeichnung – die Serverseite prüft hier nur, dass das Zertifikat zu einer vertrauenswürdigen CA verkettet ist, sie gleicht den Namen nie ab – setzen Sie es also auf das, was diesen Client identifiziert (z. B. /CN=sensor-01). Behalten Sie in jedem Fall einen -subj-Wert bei, damit OpenSSL das Zertifikat nicht-interaktiv erzeugen kann.

Die Zertifikatslebensdauer wird über -days festgelegt; Zertifikate laufen ab und müssen vorher neu erzeugt und erneut bereitgestellt werden.

14.4.3.2. Nach DER konvertieren#

Konvertieren Sie sowohl das Zertifikat als auch den privaten Schlüssel nach DER, bevor Sie sie auf die Kamera kopieren:

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. Dateien auf die Kamera kopieren#

Kopieren Sie die DER-Dateien in das Dateisystem der Kamera – zum Beispiel, indem Sie sie auf das USB-Laufwerk der OpenMV Cam ziehen, oder mit mpremote cp server.der : und mpremote cp server.key.der :. Kopieren Sie auf der verifizierenden Seite zusätzlich das CA-/Peer-Zertifikat in DER-Form.

Die DER-Dateien müssen nicht im beschreibbaren Dateisystem liegen. MicroPython kann auch ein schreibgeschütztes ROMFS-Image unter /rom einhängen, und dort abgelegte Zertifikate werden genau wie jede andere Datei geladen – z. B. ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). Ein ROMFS-Image wird auf Ihrem Entwicklungsrechner vorbereitet und ist zur Laufzeit schreibgeschützt, sodass das Zertifikat auf dem Gerät nicht verändert werden kann – nützlich, um eine Produktionseinheit abzuriegeln. Beachten Sie, dass ein in ROMFS gespeicherter privater Schlüssel weiterhin von auf der Kamera laufendem Code lesbar ist; ROMFS schützt vor Änderung, nicht vor Extraktion. Ein in ROMFS residentes Zertifikat lässt sich nur durch Neubau und erneutes Flashen des Images ersetzen.

14.4.3.4. Das Zertifikat verwenden#

Ein vollständiger Client, der die Uhr stellt, einen Socket öffnet, einen selbstsignierten Server verifiziert und Daten austauscht:

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

Ein vollständiger Server, der sein Zertifikat und seinen Schlüssel vorlegt:

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

Für die gegenseitige Authentifizierung (mTLS) verlangt und verifiziert der Server zusätzlich ein Client-Zertifikat, und der Client legt ein eigenes vor:

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

Die vollständige API finden Sie in der Dokumentation des Moduls ssl.

Bemerkung

Alles auf dieser Seite gilt unverändert für DTLS (TLS über UDP). Die Schlüssel, Zertifikate, das DER-Format, das Vertrauensmodell, die Ablaufproblematik sowie die Aufrufe load_cert_chain / load_verify_locations sind identisch; nur der Transport unterscheidet sich – Sie umhüllen einen socket.SOCK_DGRAM-Socket und wählen ssl.PROTOCOL_DTLS_CLIENT / ssl.PROTOCOL_DTLS_SERVER anstelle der TLS-Protokollkonstanten. Die eine zusätzliche Feinheit ist ein serverseitiger Anti-Spoofing-Cookie – die erste Verbindung eines neuen Clients schlägt erwartungsgemäß fehl, und der Client versucht es einfach erneut; Einzelheiten siehe DTLS-Unterstützung.