14.4.5. Einen öffentlichen Server verifizieren (Kamera als Client)

Alles auf der vorherigen Seite darüber, dass ein Client „die Root bereits besitzt“, trifft auf Browser, Telefone und PCs zu – auf die Kamera trifft es nicht zu. Das ssl von MicroPython wird ohne eingebauten Trust-Store ausgeliefert: Eine frisch geflashte Kamera vertraut überhaupt keiner CA, und die Voreinstellung (ssl.CERT_NONE) verifiziert nichts und ist einem Man-in-the-Middle weit geöffnet. Wenn die Kamera also der Client ist, der sich nach außen zu einem öffentlichen TLS-Server verbindet (eine HTTPS-API, ein MQTT-Broker, …), und Sie möchten, dass sie diesen Server wirklich verifiziert, müssen Sie den Vertrauensanker selbst bereitstellen.

Die Mechanik ist dieselbe wie beim Beispiel mit selbstsigniertem Client unter Selbstsignierte Zertifikate; der einzige Unterschied besteht darin, dass die Datei, die Sie laden, ein echtes CA-Zertifikat ist statt des eigenen Zertifikats der Gegenstelle:

  1. Beschaffen Sie das CA-Zertifikat, das die Kette des Servers verankert. „Verankert“ bedeutet das Zertifikat an (oder nahe) der Spitze der Serverkette, das Sie als Ausgangspunkt Ihres Vertrauens wählen. Ein TLS-Server sendet sein Leaf und üblicherweise seine Intermediate(s); seine Root sendet er niemals. Sie müssen diesen Vertrauensanker selbst und unabhängig vom Server beschaffen – einfach allem zu vertrauen, was ein Server Ihnen überreicht, würde den gesamten Sinn der Verifizierung zunichtemachen.

    Finden Sie zunächst heraus, welche CA das Zertifikat des Servers tatsächlich ausgestellt hat. Zum Beispiel gegen openmv.io:

    openssl s_client -connect openmv.io:443 -showcerts < /dev/null
    

    Der Certificate chain-Block listet jedes Zertifikat mit seinem Subject (s:) und Issuer (i:) auf; neuere OpenSSL-Versionen geben außerdem a:-Zeilen (Schlüsseltyp) und v:-Zeilen (Gültigkeit) aus, die Sie hier ignorieren können:

    Certificate chain
     0 s:CN=openmv.io
       i:C=US, O=Let's Encrypt, CN=E8
     1 s:C=US, O=Let's Encrypt, CN=E8
       i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
    

    Eintrag 0 ist das Leaf (openmv.io), ausgestellt durch das Intermediate E8. Eintrag 1 ist dieses Intermediate, ausgestellt durch die Root ISRG Root X1. Der Issuer (i:) des obersten Eintrags nennt die Root – hier ISRG Root X1. (Das Intermediate ist E8 statt des R10 / R11, das Sie anderswo gesehen haben mögen, weil openmv.io ein ECDSA-Zertifikat verwendet; Let’s Encrypt signiert ECDSA-Leaves mit seinen E-Reihen-Intermediates und RSA-Leaves mit seinen R-Reihen-Intermediates. Beide verketten sich zu ISRG Root X1.)

    OpenSSL gibt außerdem depth=-Zeilen aus und meldet die Root möglicherweise mit Verification: OK. Das geschieht nur, weil Ihr PC ISRG Root X1 bereits vertraut – der Server hat sie nicht gesendet (ein Server sendet seine Root niemals), und die Kamera, die keinen Trust-Store hat, wird sie ebenfalls nicht besitzen. Genau deshalb müssen Sie sie bereitstellen.

    Laden Sie diese Root von den eigenen veröffentlichten Roots der CA herunter. Let’s Encrypt katalogisiert alle ihre Roots auf der Let’s Encrypt certificates page; die direkte Datei für ISRG Root X1 ist isrgrootx1.pem (sie bieten sie auch vorkodiert als isrgrootx1.der an). Andere CAs veröffentlichen ihre Roots auf einer ähnlichen „root certificates“- / „repository“-Seite; die kanonische öffentliche Sammlung ist das Mozilla CA program (CCADB). Vergewissern Sie sich, dass Sie die richtige Datei abgerufen haben, indem Sie ihren Fingerprint mit dem von der CA veröffentlichten Wert vergleichen (fügen Sie -inform DER hinzu, falls Sie die .der heruntergeladen haben):

    openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256
    

    Falls Sie lieber keine Root nachverfolgen möchten, können Sie stattdessen das Intermediate direkt aus der -showcerts-Ausgabe kopieren (der zweite -----BEGIN CERTIFICATE------Block), diesem vertrauen und akzeptieren, dass Sie es jedes Mal aktualisieren müssen, wenn die CA das Intermediate rotiert – weit häufiger als die Root (siehe den Kompromiss weiter unten).

  2. Konvertieren Sie es nach DER, genau wie zuvor:

    openssl x509 -in isrgrootx1.pem -outform DER -out ca.der
    
  3. Kopieren Sie ca.der auf die Kamera (Dateisystem oder ROMFS) und laden Sie es als Vertrauensanker:

    import socket
    import ssl
    import ntptime
    
    ntptime.settime()                  # validity check needs the clock
    
    addr = socket.getaddrinfo("api.example.com", 443)[0][-1]
    sock = socket.socket()
    sock.connect(addr)
    
    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="api.example.com")
    

    server_hostname ist hier erforderlich: Es steuert SNI und ist der Name, der gegen den subjectAltName des Serverzertifikats geprüft wird.

Tipp

Abkürzung für den Normalfall. Let’s Encrypt ist die am weitesten verbreitete öffentliche CA, und sowohl ihre RSA- als auch ihre ECDSA-Zertifikate verketten sich derzeit zu ISRG Root X1 (wie das obige openmv.io-Beispiel zeigt). Falls die Server, mit denen Ihre Kamera spricht, Let’s Encrypt verwenden, können Sie die Inspektion vollständig überspringen: Legen Sie einfach isrgrootx1.der auf die Kamera und führen Sie load_verify_locations damit aus.

Dies bringt TLS nicht zu jeder Site zum Funktionieren. Ein Server, dessen Zertifikat von einer anderen CA stammt (DigiCert, Google Trust Services, Amazon, Sectigo, …), wird die Verifizierung weiterhin nicht bestehen, und da die Kamera pro ssl.SSLContext einem einzigen DER-Zertifikat vertraut, können Sie nicht jede Root bündeln, wie es ein Browser tut. Im Zweifelsfall ermitteln Sie die tatsächliche CA des Servers wie oben gezeigt und vertrauen dieser Root.

Welchem Zertifikat Sie vertrauen, ist ein Kompromiss:

  • Die Root (empfohlen). Langlebig – oft jahrzehntelang – sodass sich ca.der selten ändert. Es erfordert, dass der Server sein Intermediate sendet, damit mbedTLS den Pfad Leaf → Intermediate → Ihre vertrauenswürdige Root aufbauen kann; praktisch jeder korrekt konfigurierte öffentliche Server tut dies.

  • Das Intermediate. Funktioniert ebenfalls und funktioniert weiterhin, selbst wenn ein Server das Intermediate auslässt, aber Intermediates werden weit häufiger rotiert als Roots, sodass Sie ca.der häufiger aktualisieren müssen.

  • Das Leaf selbst (Certificate Pinning). Am strengsten, aber das Leaf ändert sich bei jeder Erneuerung – bei Let’s Encrypt etwa alle 90 Tage – sodass dies nur dann sinnvoll ist, wenn Sie auch den Server kontrollieren und den neuen Pin im Gleichschritt auf jede Kamera ausrollen können. Genau das tut das Beispiel mit selbstsigniertem Client.

Bemerkung

ssl.SSLContext.load_verify_locations() nimmt ein einzelnes DER-kodiertes CA-Zertifikat entgegen, sodass die Kamera jeweils genau einem Anker vertraut. Um Server unter verschiedenen CAs zu erreichen, verwenden Sie pro Anker einen separaten ssl.SSLContext. Und da dieses Zertifikat selbst irgendwann abläuft oder von der CA rotiert wird, behandeln Sie es wie jedes andere Zertifikat auf dem Gerät.