0%

HTTPS, Zertifikate und Sicherheits-Header · Übung

Ein Kontext, der verifiziert

In Python leben die drei Prüfungen der letzten Lektion in einem aus dem ssl, einem Kontext. Ein Kontext ist ein Bündel von -Einstellungen. Du erstellst ihn einmal und verwendest ihn für viele Verbindungen. with Python hat gezeigt, wie ein Kontext verwendet wird. In dieser Lektion erstellst du zwei: einen für einen Client und einen für einen Server. Beide kommen in eine neue Datei in deinem Projekt, tls_contexts.py.

Der Kontext des Clients

Ein Aufruf baut einen Client-Kontext mit sicheren Einstellungen:

import ssl

context = ssl.create_default_context(cafile="certs/ca.pem")
print(context.verify_mode)
print(context.check_hostname)
print(context.minimum_version)

cafile ist die der vertrauenswürdigen Aussteller des Clients, als Datei mit . Wenn du eine Datei übergibst, vertraut der Kontext den Ausstellern in dieser Datei und keinen anderen. Wenn du nichts übergibst, lädt er die Liste, die mit dem kam. Dieses Kapitel übergibt immer eine Datei: certs/ca.pem enthält einen Testaussteller, der für diesen Kurs erstellt wurde. Die Datei certs/README.md beschreibt ihn.

Drei Einstellungen des Kontexts entsprechen dem, was du in Lektion 1 gelernt hast:

  • verify_mode ist ssl.CERT_REQUIRED. Der Server muss ein Zertifikat vorlegen, und der Kontext prüft dessen Aussteller und Daten.

  • check_hostname ist True. Der Kontext vergleicht die Namen des Zertifikats mit dem Namen, nach dem der Client gefragt hat.

  • minimum_version sagt, welche TLS-Versionen der Client akzeptiert. Versionen vor TLS 1.2 haben bekannte Schwächen, also setzt deine ssl.TLSVersion.TLSv1_2 und verlässt sich nicht auf den Standardwert.

Ein Kontext prüft von sich aus nichts. Die Prüfungen passieren im Handshake, wenn ein Client einen verbundenen mit dem Kontext umhüllt und den Host nennt, den er erwartet:

secure = context.wrap_socket(sock, server_hostname="reading.example")

Der Name in server_hostname ist der, den die Namensprüfung mit dem Zertifikat vergleicht. Wenn eine der drei Prüfungen fehlschlägt, löst dieser Aufruf einen Fehler aus, und der Client bekommt nie eine Verbindung, auf der er eine Anfrage senden könnte. Das siehst du in der nächsten Lektion passieren. In dieser baust du nur die Kontexte, und zwar so, dass alle drei Prüfungen eingeschaltet sind.

Drei Arten, sie auszuschalten

Die Verifizierung steht im Weg, wenn ein Zertifikat falsch ist, und auf einem Testserver ist ein Zertifikat oft falsch. Also schalten Leute die Verifizierung aus, meist mit einer nahen Deadline und dem Plan, sie wieder einzuschalten. Dann bleibt die Zeile. Du solltest sie in einem Review finden können. Sie hat drei Formen.

Die erste ist context.check_hostname = False. Der Aussteller wird weiterhin geprüft, der Name nicht. Die zweite ist context.verify_mode = ssl.CERT_NONE, womit das Zertifikat gar nicht mehr geprüft wird. Die dritte ist ein Kontext, der mit einem bloßen ssl.SSLContext() gebaut wird, der mit beiden Prüfungen ausgeschaltet beginnt.

Bibliotheken haben denselben Schalter unter anderem Namen. In requests und httpx ist es verify=False.

Mit ausgeschalteter Verifizierung verschlüsselt TLS weiterhin. Aber der Client weiß nicht mehr, wer das andere Ende hält, also verschlüsselt er sein Passwort für den, der geantwortet hat. Das ist der ganze Angriff: Jemand auf dem Weg antwortet anstelle des Servers, und der Client merkt es nicht.

Vier Fragmente bauen jeweils einen Kontext. Welches prüft noch sowohl den Aussteller als auch den Namen?

# A
context = ssl.create_default_context()
context.check_hostname = False

# B
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

# C
context = ssl.SSLContext()

# D
context = ssl.create_default_context(cafile="certs/ca.pem")

Ein Kontext hat check_hostname ausgeschaltet und verlangt weiterhin ein Zertifikat. Wer kann als reading.example antworten?

Was passiert in der zweiten Zeile?

context = ssl.create_default_context()
context.verify_mode = ssl.CERT_NONE

Der Kontext des Servers

Auch ein Server braucht einen Kontext, für die andere Hälfte des Handshakes. Er legt ein Zertifikat vor und beweist, dass er den Schlüssel besitzt:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile, keyfile)

ssl.PROTOCOL_TLS_SERVER erstellt einen Kontext für die Serverseite. load_cert_chain lädt das Zertifikat und seinen privaten Schlüssel. Ein Server-Kontext verifiziert niemanden. Ein Browser hat meist kein Zertifikat vorzuzeigen, und Zertifikate für Clients liegen außerhalb dieses Kurses.

Die Schlüsseldatei ist das Geheimnis des Servers, wie der Signaturschlüssel aus Kapitel 5. Ein echter Schlüssel liegt nie in einem Projektordner. Die Schlüssel in certs/ sind Testschlüssel, die jeder lesen kann, und deshalb hat jeder von ihnen TEST-ONLY im Namen.

Deine Aufgabe

Öffne tls_contexts.py. Die Datei enthält drei Funktionen, die noch nicht geschrieben sind. Schreib jetzt zwei davon:

  • client_context(cafile) gibt ssl.create_default_context(cafile=cafile) zurück, mit minimum_version auf ssl.TLSVersion.TLSv1_2 gesetzt. Er vertraut nur der Datei, die er bekommen hat.

  • server_context(certfile, keyfile) gibt einen mit ssl.PROTOCOL_TLS_SERVER erstellten Kontext zurück, mit derselben Mindestversion, der genau dieses Zertifikat und diesen Schlüssel geladen hat.

Lass tls_failure für die nächste Lektion. Dann führ die Datei aus:

python tls_contexts.py

Sie baut deinen Client-Kontext aus certs/ca.pem und gibt vier seiner Einstellungen aus:

verify_mode: CERT_REQUIRED
check_hostname: True
minimum_version: TLSv1_2
authorities: 1

Dann drück Check my work. Die Prüfung liest die Einstellungen deiner Kontexte und verbindet sich nirgendwohin. Einstellungen, die auf dem Papier stimmen, sind noch kein Handshake. In der nächsten Lektion siehst du zu, wie sich deine beiden Kontexte begegnen.

Aufgabe

Schreib die beiden Kontextfunktionen in tls_contexts.py.

  • client_context(cafile) gibt einen Client-Kontext zurück, der nur den Zertifizierungsstellen in der Datei cafile vertraut. Bau ihn mit ssl.create_default_context(cafile=cafile) und setz minimum_version auf ssl.TLSVersion.TLSv1_2. Lass verify_mode und check_hostname so, wie dieser Aufruf sie setzt. Lade nicht die der Zertifizierungsstellen des Systems.

  • server_context(certfile, keyfile) gibt einen Server-Kontext zurück: ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) mit minimum_version auf ssl.TLSVersion.TLSv1_2 gesetzt, nachdem load_cert_chain(certfile, keyfile) genau das Paar geladen hat, das er bekommen hat.

Lass tls_failure für die nächste Lektion.

Führ python tls_contexts.py im aus. Es gibt verify_mode: CERT_REQUIRED, check_hostname: True, minimum_version: TLSv1_2 und authorities: 1 aus.

Dann drück Check my work. Die Prüfung ruft client_context mit einer Datei von Zertifizierungsstellen auf, die du nicht gesehen hast, und liest die Einstellungen des Kontexts und die Zertifizierungsstellen, denen er vertraut. Sie ruft server_context mit einem anderen und Schlüssel aus certs/ auf und prüft, welches Paar geladen wurde. Sie macht keinen Handshake und öffnet keine Verbindung. Nichts aus deinem Terminal wird bewertet.