0%

HTTPS, Certificates, and Security Headers · practice

Watch Verification Succeed and Fail

Your two contexts have the right settings, and no connection has used them yet. In this lesson you watch them work: four handshakes on this computer, one that completes and three that the client refuses. Then you write the that says which check refused.

First read certs/README.md in your project. It describes the files in certs/. One of them, ca.pem, is a test authority: an issuer made for this course. No browser and no trusts it. Your client trusts it for one reason only, which is that you hand it the file. The other files are four with their keys. One is good, and three are each wrong in one way.

Keep one thing in mind for the whole lesson. These handshakes show how a client decides. They show nothing about any real website.

Run it

python pl_sec.py tls

The helper starts a small listener on this computer four times, each time with your server_context() and a different certificate. Each time it connects with your client_context("certs/ca.pem") and asks for the name 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

The client connects its to 127.0.0.1, and it expects reading.example. Those are two separate things. A client checks the name it asked for, not the address it connected to, and here the name is never looked up anywhere: the helper passes it to the handshake directly. A certificate can also addresses. The course’s good certificate lists 127.0.0.1 beside its names.

Look at the first block. The handshake completed, and only then did the server say anything: hello over TLS travelled inside the encrypted connection. The line also names the protocol version the two contexts agreed on. Both of yours allow 1.2 and later, and they chose the newest version both sides know.

The three refusals are the three checks of Lesson 1. unknown-issuer.pem names reading.example and its dates are fine, but its issuer is not in certs/ca.pem. wrong-name.pem comes from the right issuer, for other.example. expired.pem ended in 2020. In all three cases the client stopped during the handshake, so the server on the other end never received a request.

The client refused expired.pem. How much of an HTTP request had reached the server by then?

The error is an object

When a check fails, the handshake raises ssl.SSLCertVerificationError. The carries two attributes that say why:

  • verify_code is a number from OpenSSL, the library that does the checking. 20, 62, and 10 in the run above are verify codes.

  • verify_message is a sentence for people.

Code that makes the connection catches the exception and reads them, like any exception attribute:

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

Decide from the code and never from the message. The message is worded by OpenSSL, and the wording changes between versions. The numbers come from OpenSSL’s own list of errors.

Python has no names for these numbers, so tls_contexts.py supplies them as three sets, with a comment for each number: ISSUER_CODES, NAME_CODES, and DATE_CODES. The sets hold more codes than the run showed. Code 18, for example, is a certificate that its own holder signed. You will meet it on a development machine, where people make such certificates for themselves. Code 9 is a certificate whose period has not started yet. It fails the same check as an expired one, which is why that check is called the date check here and not “expired”.

unknown-issuer.pem names reading.example and its dates are fine. Why is it refused?

The server presents certs/wrong-name.pem. What does tls_failure() return for the error?

Your task

Write tls_failure(error) in tls_contexts.py. It reads error.verify_code and returns one of four :

  • "issuer" when the code is in ISSUER_CODES;

  • "name" when it is in NAME_CODES;

  • "date" when it is in DATE_CODES;

  • "other" for every other code.

Then run the scenario again:

python pl_sec.py tls

Each refusal now has one more line under it, and the last line of the first run is gone:

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

The other two say tls_failure() says: name and tls_failure() says: date.

What did these four handshakes show about a real website?

Then press Check my work. It calls your function with error it makes itself, for the codes you saw and for codes you did not, so it needs no handshake. The connection now has a verified other end. The next lesson turns to the page that arrives over it.

Task

Write tls_failure(error) in tls_contexts.py.

  • error is an ssl.SSLCertVerificationError. Read its verify_code.

  • Return "issuer" when the code is in ISSUER_CODES, "name" when it is in NAME_CODES, "date" when it is in DATE_CODES, and "other" for any other code.

  • Decide from verify_code. verify_message is a sentence for people: its wording can change between OpenSSL versions, and it can be empty.

Run python pl_sec.py tls in the : one handshake completes, three are refused, and under each refusal a line says tls_failure() says: issuer, name, or date.

Then press Check my work. It calls tls_failure with error it makes itself: the three codes the run shows, six it does not show, and one whose message is empty. It performs no handshake and opens no connection. Nothing from your terminal is graded.