HTTPS, Certificates, and Security Headers · practice
A Context That Verifies
In Python, the three checks of the last lesson live in an ssl tls_contexts.py.
The client’s context
One call builds a client context with safe settings:
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 the client’s certs/ca.pem holds one test issuer, made for this course. The file certs/README.md describes it.
Three settings on the context match what you learned in Lesson 1:
verify_modeisssl.CERT_REQUIRED. The server must present a certificate, and the context checks its issuer and its dates.check_hostnameisTrue. The context compares the certificate’s names with the name the client asked for.minimum_versionsays which TLS versions the client accepts. Versions before TLS 1.2 have known weaknesses, so yoursets ssl.TLSVersion.TLSv1_2and does not rely on the default.
A context checks nothing by itself. The checks happen in the handshake, when a client wraps a connected
secure = context.wrap_socket(sock, server_hostname="reading.example")
The name in server_hostname is the one the name check compares with the certificate. If any of the three checks fails, this call raises an error and the client never gets a connection to send a request on. You will see that happen in the next lesson. In this one you only build the contexts, and you build them so that all three checks are on.
Three ways to switch it off
Verification gets in the way when a certificate is wrong, and a certificate is often wrong on a test server. So people switch verification off, usually with a deadline close and a plan to switch it back on. Then the line stays. You should be able to find it in a review. It has three forms.
The first is context.check_hostname = False. The issuer is still checked, and the name is not. The second is context.verify_mode = ssl.CERT_NONE, which stops checking the certificate at all. The third is a context built with a bare ssl.SSLContext(), which starts with both checks off.
Libraries have the same switch under another name. In requests and httpx it is verify=False.
With verification off, TLS still encrypts. But the client no longer knows who holds the other end, so it encrypts its password for whoever answered. That is the whole attack: someone on the path answers in place of the server, and the client does not notice.
Four fragments each build a context. Which one still checks both the issuer and the name?
# 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")
A context has check_hostname off and still requires a certificate. Who can answer as reading.example?
What happens at the second line?
context = ssl.create_default_context()
context.verify_mode = ssl.CERT_NONE
The server’s context
A server needs a context too, for the other half of the handshake. It presents a certificate and proves that it holds the key:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile, keyfile)
ssl.PROTOCOL_TLS_SERVER makes a context for the server side. load_cert_chain loads the certificate and its private key. A server context does not verify anyone. A browser usually has no certificate to show, and certificates for clients are outside this course.
The key file is the server’s secret, like the signing key of Chapter 5. A real key never sits in a project folder. The keys in certs/ are test keys that anyone can read, which is why every one of them has TEST-ONLY in its name.
Your task
Open tls_contexts.py. It holds three functions that are not written yet. Write two of them now:
client_context(cafile)returnsssl.create_default_context(cafile=cafile)withminimum_versionset tossl.TLSVersion.TLSv1_2. It trusts only the file it was given.server_context(certfile, keyfile)returns a context made withssl.PROTOCOL_TLS_SERVER, with the same minimum version, that has loaded exactly that certificate and key.
Leave tls_failure for the next lesson. Then run the file:
python tls_contexts.py
It builds your client context from certs/ca.pem and prints four of its settings:
verify_mode: CERT_REQUIRED
check_hostname: True
minimum_version: TLSv1_2
authorities: 1
Then press Check my work. It reads the settings of your contexts and never connects anywhere. Settings that are right on paper are not yet a handshake. In the next lesson you watch your two contexts meet.
Task
Write the two context tls_contexts.py.
client_context(cafile)returns a client context that trusts only the authorities in the filecafile. Build it withssl.create_default_context(cafile=cafile)and setminimum_versiontossl.TLSVersion.TLSv1_2. Leaveverify_modeandcheck_hostnameas that call sets them. Do not load the system’sof authorities. server_context(certfile, keyfile)returns a server context:ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)withminimum_versionset tossl.TLSVersion.TLSv1_2, afterload_cert_chain(certfile, keyfile)loaded exactly the pair it was given.
Leave tls_failure for the next lesson.
Run python tls_contexts.py in the verify_mode: CERT_REQUIRED, check_hostname: True, minimum_version: TLSv1_2, and authorities: 1.
Then press Check my work. It calls client_context with an authority file you have not seen and reads the context’s settings and the authorities it trusts. It calls server_context with another certs/ and checks which pair was loaded. It performs no handshake and opens no connection. Nothing from your terminal is graded.