HTTPS, certificaten en beveiligingsheaders · oefening
Een context die verifieert
In Python leven de drie controles uit de vorige les in een ssl, een context. Een context is een bundel tls_contexts.py.
De context van de client
Eén aanroep bouwt een clientcontext met veilige instellingen:
import ssl
context = ssl.create_default_context(cafile="certs/ca.pem")
print(context.verify_mode)
print(context.check_hostname)
print(context.minimum_version)
cafile is de certs/ca.pem bevat één testuitgever, gemaakt voor deze cursus. Het bestand certs/README.md beschrijft hem.
Drie instellingen op de context passen bij wat je in les 1 hebt geleerd:
verify_modeisssl.CERT_REQUIRED. De server moet een certificaat laten zien, en de context controleert de uitgever en de datums ervan.check_hostnameisTrue. De context vergelijkt de namen in het certificaat met de naam waar de client om vroeg.minimum_versionzegt welke TLS-versies de client accepteert. Versies vóór TLS 1.2 hebben bekende zwakheden, dus jezet ssl.TLSVersion.TLSv1_2en vertrouwt niet op de standaardwaarde.
Een context controleert zelf niets. De controles gebeuren in de handshake, wanneer een client een verbonden
secure = context.wrap_socket(sock, server_hostname="reading.example")
De naam in server_hostname is de naam die de naamcontrole met het certificaat vergelijkt. Als een van de drie controles faalt, werpt deze aanroep een fout op en krijgt de client nooit een verbinding om een verzoek over te sturen. Dat zie je in de volgende les gebeuren. In deze les bouw je alleen de contexts, en je bouwt ze zo dat alle drie de controles aan staan.
Drie manieren om het uit te zetten
Verificatie zit in de weg als een certificaat niet klopt, en op een testserver klopt een certificaat vaak niet. Dus zetten mensen verificatie uit, meestal met een deadline dichtbij en een plan om het weer aan te zetten. Daarna blijft de regel staan. Je moet hem bij een review kunnen vinden. Hij heeft drie vormen.
De eerste is context.check_hostname = False. De uitgever wordt nog gecontroleerd, de naam niet. De tweede is context.verify_mode = ssl.CERT_NONE, die helemaal stopt met het controleren van het certificaat. De derde is een context gebouwd met een kale ssl.SSLContext(), die begint met beide controles uit.
Bibliotheken hebben dezelfde schakelaar onder een andere naam. In requests en httpx is het verify=False.
Met verificatie uit versleutelt TLS nog steeds. Maar de client weet niet meer wie het andere eind vasthoudt, dus versleutelt hij zijn wachtwoord voor wie er ook maar antwoordde. Dat is de hele aanval: iemand op het pad antwoordt in plaats van de server, en de client merkt het niet.
Vier fragmenten bouwen elk een context. Welke controleert nog zowel de uitgever als de naam?
# 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")
Een context heeft check_hostname uit en eist nog steeds een certificaat. Wie kan antwoorden als reading.example?
Wat gebeurt er bij de tweede regel?
context = ssl.create_default_context()
context.verify_mode = ssl.CERT_NONE
De context van de server
Een server heeft ook een context nodig, voor de andere helft van de handshake. Hij laat een certificaat zien en bewijst dat hij de sleutel heeft:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile, keyfile)
ssl.PROTOCOL_TLS_SERVER maakt een context voor de serverkant. load_cert_chain laadt het certificaat en de privésleutel ervan. Een servercontext verifieert niemand. Een browser heeft meestal geen certificaat om te laten zien, en certificaten voor clients vallen buiten deze cursus.
Het sleutelbestand is het geheim van de server, net als de ondertekeningssleutel uit hoofdstuk 5. Een echte sleutel staat nooit in een projectmap. De sleutels in certs/ zijn testsleutels die iedereen kan lezen; daarom heeft elk ervan TEST-ONLY in de naam.
Je opdracht
Open tls_contexts.py. Het bevat drie functies die nog niet geschreven zijn. Schrijf er nu twee:
client_context(cafile)geeftssl.create_default_context(cafile=cafile)terug metminimum_versionopssl.TLSVersion.TLSv1_2. Hij vertrouwt alleen het bestand dat hij kreeg.server_context(certfile, keyfile)geeft een context terug, gemaakt metssl.PROTOCOL_TLS_SERVER, met dezelfde minimumversie, die precies dat certificaat en die sleutel heeft geladen.
Laat tls_failure liggen voor de volgende les. Voer daarna het bestand uit:
python tls_contexts.py
Het bouwt je clientcontext uit certs/ca.pem en drukt vier van zijn instellingen af:
verify_mode: CERT_REQUIRED
check_hostname: True
minimum_version: TLSv1_2
authorities: 1
Klik daarna op Check my work. Die leest de instellingen van je contexts en maakt nergens verbinding. Instellingen die op papier kloppen, zijn nog geen handshake. In de volgende les zie je je twee contexts elkaar ontmoeten.
Opdracht
Schrijf de twee contextfuncties in tls_contexts.py.
client_context(cafile)geeft een clientcontext terug die alleen de autoriteiten in het bestandcafilevertrouwt. Bouw hem metssl.create_default_context(cafile=cafile)en zetminimum_versionopssl.TLSVersion.TLSv1_2. Laatverify_modeencheck_hostnamezoals die aanroep ze zet. Laad niet demet autoriteiten van het systeem. server_context(certfile, keyfile)geeft een servercontext terug:ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)metminimum_versionopssl.TLSVersion.TLSv1_2, nadatload_cert_chain(certfile, keyfile)precies het paar heeft geladen dat hij kreeg.
Laat tls_failure liggen voor de volgende les.
Voer python tls_contexts.py uit in de verify_mode: CERT_REQUIRED, check_hostname: True, minimum_version: TLSv1_2 en authorities: 1 af.
Klik daarna op Check my work. Die roept client_context aan met een autoriteitenbestand dat je niet hebt gezien en leest de instellingen van de context en de autoriteiten die hij vertrouwt. Hij roept server_context aan met een ander certs/ en controleert welk paar geladen is. Hij doet geen handshake en opent geen verbinding. Niets uit je terminal wordt beoordeeld.