0%

HTTPS, Zertifikate und Sicherheits-Header · Übung

Sieh zu, wie die Verifizierung gelingt und scheitert

Deine beiden Kontexte haben die richtigen Einstellungen, und noch keine Verbindung hat sie benutzt. In dieser Lektion siehst du ihnen bei der Arbeit zu: vier Handshakes auf diesem Computer, einer, der gelingt, und drei, die der Client ablehnt. Dann schreibst du die , die sagt, welche Prüfung abgelehnt hat.

Lies zuerst certs/README.md in deinem Projekt. Die Datei beschreibt die Dateien in certs/. Eine davon, ca.pem, ist eine Test-Zertifizierungsstelle: ein Aussteller, der für diesen Kurs erstellt wurde. Kein Browser und kein vertraut ihr. Dein Client vertraut ihr aus genau einem Grund: weil du ihm die Datei gibst. Die anderen Dateien sind vier mit ihren Schlüsseln. Eins ist gut, und drei sind jeweils auf eine Weise falsch.

Behalte während der ganzen Lektion eins im Kopf. Diese Handshakes zeigen, wie ein Client entscheidet. Über eine echte Website zeigen sie nichts.

Führ es aus

python pl_sec.py tls

Das Hilfsprogramm startet viermal einen kleinen Listener auf diesem Computer, jedes Mal mit deinem server_context() und einem anderen Zertifikat. Jedes Mal verbindet es sich mit deinem client_context("certs/ca.pem") und fragt nach dem Namen reading.example:

pl_sec: tls, four handshakes on 127.0.0.1 with your client_context() and server_context()
The name reading.example exists only in the model. Every byte goes to this computer.
--- the client ---
trusts certs/ca.pem only, and expects the name reading.example
--- the server presents certs/reading.pem ---
handshake completed (TLSv1.3); the server said: hello over TLS
--- the server presents certs/unknown-issuer.pem ---
refused: verify_code 20: unable to get local issuer certificate
--- the server presents certs/wrong-name.pem ---
refused: verify_code 62: Hostname mismatch, certificate is not valid for 'reading.example'.
--- the server presents certs/expired.pem ---
refused: verify_code 10: certificate has expired
--- result ---
1 handshake completed and 3 were refused, each before any request was sent
pl_sec: every thread has stopped
pl_sec: tls_failure() is not written yet (Write this function in Lesson 3.), so the failed checks have no name yet

Der Client verbindet seinen mit 127.0.0.1 und erwartet reading.example. Das sind zwei verschiedene Dinge. Ein Client prüft den Namen, nach dem er gefragt hat, nicht die Adresse, mit der er sich verbunden hat, und hier wird der Name nirgends nachgeschlagen: Das Hilfsprogramm übergibt ihn direkt an den Handshake. Ein Zertifikat kann auch Adressen auflisten. Das gute Zertifikat des Kurses listet 127.0.0.1 neben seinen Namen.

Sieh dir den ersten Block an. Der Handshake wurde abgeschlossen, und erst dann hat der Server etwas gesagt: hello over TLS ist innerhalb der verschlüsselten Verbindung gereist. Die Zeile nennt außerdem die Protokollversion, auf die sich die beiden Kontexte geeinigt haben. Deine beiden erlauben 1.2 und neuer, und sie haben die neueste Version gewählt, die beide Seiten kennen.

Die drei Ablehnungen sind die drei Prüfungen aus Lektion 1. unknown-issuer.pem nennt reading.example, und seine Daten sind in Ordnung, aber sein Aussteller steht nicht in certs/ca.pem. wrong-name.pem kommt vom richtigen Aussteller, für other.example. expired.pem endete 2020. In allen drei Fällen hat der Client während des Handshakes aufgehört, also hat der Server am anderen Ende nie eine Anfrage erhalten.

Der Client hat expired.pem abgelehnt. Wie viel von einer HTTP-Anfrage hatte den Server bis dahin erreicht?

Der Fehler ist ein Objekt

Wenn eine Prüfung fehlschlägt, löst der Handshake ssl.SSLCertVerificationError aus. Die trägt zwei Attribute, die sagen, warum:

  • verify_code ist eine Zahl von OpenSSL, der Bibliothek, die die Prüfung erledigt. 20, 62 und 10 im Lauf oben sind Verify-Codes.

  • verify_message ist ein Satz für Menschen.

Code, der die Verbindung herstellt, fängt die Ausnahme ab und liest sie, wie jedes Attribut einer Ausnahme:

try:
    secure = context.wrap_socket(sock, server_hostname="reading.example")
except ssl.SSLCertVerificationError as error:
    print(error.verify_code, error.verify_message)

Entscheide anhand des Codes und nie anhand der Meldung. Die Meldung formuliert OpenSSL, und die Formulierung ändert sich zwischen Versionen. Die Zahlen stammen aus OpenSSLs eigener Fehlerliste.

Python hat keine Namen für diese Zahlen, also liefert tls_contexts.py sie als drei Sets, mit einem Kommentar zu jeder Zahl: ISSUER_CODES, NAME_CODES und DATE_CODES. Die Sets enthalten mehr Codes, als der Lauf gezeigt hat. Code 18 zum Beispiel ist ein Zertifikat, das sein eigener Inhaber signiert hat. Dem begegnest du auf einem Entwicklungsrechner, wo Leute sich solche Zertifikate selbst erstellen. Code 9 ist ein Zertifikat, dessen Zeitraum noch nicht begonnen hat. Es fällt bei derselben Prüfung durch wie ein abgelaufenes, und deshalb heißt diese Prüfung hier Datumsprüfung und nicht „abgelaufen“.

unknown-issuer.pem nennt reading.example, und seine Daten sind in Ordnung. Warum wird es abgelehnt?

Der Server legt certs/wrong-name.pem vor. Was gibt tls_failure() für den Fehler zurück?

Deine Aufgabe

Schreib tls_failure(error) in tls_contexts.py. Die Funktion liest error.verify_code und gibt einen von vier zurück:

  • "issuer", wenn der Code in ISSUER_CODES steht;

  • "name", wenn er in NAME_CODES steht;

  • "date", wenn er in DATE_CODES steht;

  • "other" für jeden anderen Code.

Dann führ das Szenario noch einmal aus:

python pl_sec.py tls

Jede Ablehnung hat jetzt eine Zeile mehr darunter, und die letzte Zeile des ersten Laufs ist verschwunden:

refused: verify_code 20: unable to get local issuer certificate
tls_failure() says: issuer

Die anderen beiden sagen tls_failure() says: name und tls_failure() says: date.

Was haben diese vier Handshakes über eine echte Website gezeigt?

Dann drück Check my work. Die Prüfung ruft deine Funktion mit Fehlerobjekten auf, die sie selbst erstellt, für die Codes, die du gesehen hast, und für Codes, die du nicht gesehen hast, also braucht sie keinen Handshake. Die Verbindung hat jetzt ein verifiziertes anderes Ende. Die nächste Lektion wendet sich der Seite zu, die darüber ankommt.

Aufgabe

Schreib tls_failure(error) in tls_contexts.py.

  • error ist ein ssl.SSLCertVerificationError. Lies seinen verify_code.

  • Gib "issuer" zurück, wenn der Code in ISSUER_CODES steht, "name", wenn er in NAME_CODES steht, "date", wenn er in DATE_CODES steht, und "other" für jeden anderen Code.

  • Entscheide anhand von verify_code. verify_message ist ein Satz für Menschen: Seine Formulierung kann sich zwischen OpenSSL-Versionen ändern, und er kann leer sein.

Führ python pl_sec.py tls im aus: Ein Handshake gelingt, drei werden abgelehnt, und unter jeder Ablehnung sagt eine Zeile tls_failure() says: issuer, name oder date.

Dann drück Check my work. Die Prüfung ruft tls_failure mit Fehlerobjekten auf, die sie selbst erstellt: den drei Codes, die der Lauf zeigt, sechs, die er nicht zeigt, und einem, dessen Meldung leer ist. Sie macht keinen Handshake und öffnet keine Verbindung. Nichts aus deinem Terminal wird bewertet.