HTTPS, Zertifikate und Sicherheits-Header · Abschlussprojekt
Abschlussprojekt: Projekt: Die Leseliste über HTTPS
Zeile F13 von SECURITY.md ist die letzte offene Zeile, die dieser Kurs schließt. Sie hat zwei Hälften. Ein Client, der das client_context() geschlossen. Die andere Hälfte ist die Seite: Keine Antwort hat einem Browser irgendetwas gesagt, und seit der letzten Lektion läuft jede Antwort, die dein Code erzeugt, durch harden(). Ein Zweig von harden() ist allerdings noch nie gelaufen. Dein Server spricht einfaches state["https"] False, und keine Antwort hat HSTS getragen. Dieses Projekt bringt
Den lauschenden Socket umhüllen
make_server baut einen HTTPServer, und dieser Server besitzt einen lauschenden server.socket. Um TLS zu sprechen, umhüllst du diesen Socket mit einem Server-Kontext, bevor die erste Verbindung ankommt:
server.socket = context.wrap_socket(server.socket, server_side=True, do_handshake_on_connect=False)
Jede Verbindung, die der Server von da an annimmt, ist eine TLS-Verbindung. server_side=True sagt, welche Hälfte des Handshakes dieses Ende spielt.
Das dritte accept, wo kein Timeout gilt. Ein Client, der sich verbindet und dann nichts sagt, würde den Server dort festhalten, und das Limit für Verbindungen würde nie erreicht. Mit do_handshake_on_connect=False passiert der Handshake stattdessen beim ersten Lesen, und http_adapter.py lässt jedes Lesen auf einer Verbindung höchstens zwei Sekunden warten. Das Limit gilt für jedes einzelne Lesen, nicht für den Handshake als Ganzes: Ein Client, der immer nur ein paar Bytes sendet, kann eine Verbindung länger halten.
Python 3.14 hat außerdem http.server.HTTPSServer, der den Socket für dich umhüllt und seinen eigenen Kontext erstellt. Hier umhüllst du selbst, damit die Einstellung oben deine Wahl ist.
Warum übergibt make_server do_handshake_on_connect=False?
Sag dem Dienst, dass er hinter HTTPS steht
harden() kann die Verbindung nicht sehen. Es wird über state["https"] informiert. Also muss die Stelle, die entscheidet, ob der Server TLS spricht, auch das Flag setzen, und diese Stelle ist main(). Gib ihr zwei Optionen, --certfile und --keyfile. Wenn beide angegeben sind, baut main() mit tls_contexts.server_context() einen Kontext, setzt state["https"] = True und übergibt den Kontext an make_server. Wenn keine angegeben ist, ändert sich nichts. Die eine ohne die andere ist ein Fehler im Befehl, und parser.error() meldet ihn.
Der Server spricht TLS, und state["https"] wurde auf False gelassen. Was fehlt in seinen Antworten?
Deine Aufgabe
Füg in
make_servereinen Keyword-only-context=Nonehinzu. Wenn ein Kontext übergeben wird, umhülleserver.socketwie oben gezeigt und speichere das Ergebnis zurück inserver.socket.Füg in
main()--certfileund--keyfilewie beschrieben hinzu.Schreib drei weitere Tests in
tests/test_attacks.py:test_attack_page_refuses_inline_scriptmeldet sich mitsteps.login_cookie()an, fragt mit demGET /an und sichert zu, dass diecontent-security-policyder Antwort eindefault-srchat und'unsafe-inline'nicht enthält.test_attack_hsts_only_over_httpssichert zu, dassGET /aboutkeinstrict-transport-securityträgt, solangestate["https"]Falseist, und es trägt, nachdem der Teststate["https"] = Truegesetzt hat.test_attack_client_context_verifiesrufttls_contexts.client_context("certs/ca.pem")auf und sichert zu, dassverify_modessl.CERT_REQUIREDist undcheck_hostnameTrue. Die Datei importiertsslundtls_contextsbereits.
Führ den Dienst über TLS aus:
python pl_sec.py https
Das Hilfsprogramm baut einen State, setzt state["https"] selbst und bedient ihn über dein make_server mit einem Kontext aus deinem server_context():
pl_sec: https, your server on 127.0.0.1 (a port the system chose), speaking TLS with certs/reading.pem
pl_sec set state["https"] to True, as your main() must when it is given a certificate. The name reading.example exists only in the model.
--- GET /about over TLS, expecting the name reading.example ---
HTTP/1.1 200 OK
content-type: application/json
content-length: 41
etag: "a595067de2a7c59a4be181e441d808ef93b456aca1f6d76b8583cd58d30abe1b"
cache-control: max-age=60
vary: Origin
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
strict-transport-security: max-age=31536000
--- the same client, expecting the name other.example ---
refused before any request was sent: name
--- a client that speaks plain HTTP to the same port ---
the server closed the connection without an HTTP answer
--- result ---
the service answered over TLS, and its response carries strict-transport-security
serve() returned after 3 connections; the server is closed
pl_sec: every thread has stopped
Der erste Block ist dein ganzer Dienst, der innerhalb von TLS antwortet, mit all seinen Headern. Der zweite ist dein eigener Client, der deinen eigenen Server ablehnt, weil er nach einem Namen gefragt hat, den das Zertifikat nicht auflistet. Der dritte ist ein Client, der einfaches HTTP an einen
Führ
python -m pytest -qaus. Dann füll Zeile F13 vonSECURITY.mdaus: Nenn die vier Tests dieses Kapitels in der Spalte Test und schreibclosed in 10.6in die Spalte Status.
Ein Client sendet ein einfaches GET /about HTTP/1.1 an den TLS-Port. Was bekommt er?
Was das nicht getan hat
Der Lauf zeigt deinen Server, wie er auf diesem Computer mit Testzertifikaten TLS spricht. Über das Deployment zeigt er nichts. Dieses Kapitel hat kein Zertifikat ausgestellt, erneuert oder zurückgezogen, und es hat den Dienst nicht hinter einem vorgeschalteten Server betrieben.
Zwei weitere Grenzen. Wenn ein Client das Zertifikat ablehnt oder einfaches HTTP spricht, meldet http.server den fehlgeschlagenen Handshake, indem es etwa dreißig Zeilen
Und „jede Antwort läuft durch harden()“ stimmt für jede Antwort, die dein Code erzeugt. Manche Fehlerseiten schreibt http.server selbst, bevor dein Code die Anfrage sieht: seine 501 für eine PATCH, seine 400 für eine Anfragezeile, die es nicht lesen kann, und seine Antworten auf eine Anfragezeile oder einen Header, der zu lang ist. Das sind HTML-Seiten ohne einen dieser Header. Und die 411, die closing_error() erzeugt, trägt über TLS kein HSTS: Diese Funktion hat keinen State, den sie fragen könnte. Das ist ein Grund, warum ein echter Dienst solche Header in seinem vorgeschalteten Server oder seinem Framework setzt, wo keine Antwort an ihnen vorbeikommt.
Drück Check my work. In SECURITY.md sind jetzt F1 bis F13 geschlossen und F14 absichtlich offen. Im nächsten Kapitel sagt dir niemand, wo du suchen sollst.
Aufgabe
Bring
In reading_server.py:
make_server(handle, state, *, timeout=1.0, context=None): WenncontextnichtNoneist, setzserver.socket = context.wrap_socket(server.socket, server_side=True, do_handshake_on_connect=False). Ohne Kontext ist der Server wie zuvor.main()nimmt--certfileund--keyfile. Mit beiden ruft estls_contexts.server_context(args.certfile, args.keyfile)auf, setztstate["https"] = Trueund übergibtcontext=...anmake_server. Mit keiner von beiden bleibtstate["https"]False, und es wird kein Kontext erstellt. Mit nur einer davon ruft esparser.error(...)auf.Alles, was Lektion 5 verlangt hat, gilt weiterhin.
Schreib in tests/test_attacks.py, neben dem Test aus Lektion 5:
test_attack_page_refuses_inline_script: Die Seite einer angemeldetenträgt eine content-security-policymitdefault-srcund ohne'unsafe-inline'.test_attack_hsts_only_over_https:GET /aboutträgt keinstrict-transport-security, solangestate["https"]Falseist, und trägt es, wenn das FlagTrueist.test_attack_client_context_verifies:tls_contexts.client_context("certs/ca.pem")hatverify_modegleichssl.CERT_REQUIREDundcheck_hostnamegleichTrue.
Füll in SECURITY.md Zeile F13 aus: die Namen der Tests, die sie beweisen, und closed in 10.6.
Führ python pl_sec.py https und python -m pytest -q im
Dann drück Check my work. Die Prüfung wiederholt die Prüfungen aus Lektion 5. Sie ruft make_server mit Platzhaltern für die Serverklasse und den Kontext auf, sodass nichts lauscht und kein Handshake stattfindet, und sieht sich an, wie der main() mit Platzhaltern für make_server, serve und server_context aus, mit und ohne die beiden Optionen. Sie prüft die Caching-POST /tokens, GET /me und einem Lesezugriff mit Token. Sie führt deine vier Tests gegen einen korrekten Server und Kontext aus, wo sie bestehen müssen, und jeden gegen den Fehler, den er bemerken muss, wo er fehlschlagen muss. Sie liest Zeile F13. Keine Prüfung öffnet eine Verbindung. Nichts aus deinem Terminal wird bewertet.