Revocation checking is broken and has been for some time. Whilst some vendors have sort of worked around this with proprietary solutions, there is little that the smaller sites can do. OCSP must-staple to the rescue!
In the early days of the web we had Certificate Revocation Lists, or CRLs. These were lists of all certificates that a CA had revoked and could be downloaded by a client to check if the certificate they were served had been revoked. These lists didn't scale and eventually downloading these large files became a problem, thus the Online Certificate Status Protocol, or OCSP, was born. Instead of the client downloading a list of all revoked certificates, they would submit a request to the CA to check the status of the specific certificate they had received. Sadly OCSP was riddled with problems like poor CA infrastructure being unavailable and the privacy concern of clients leaking the site they were visiting to the CA. To get around this problem OCSP Stapling was created. Instead of the client making the OCSP request to the CA, the host website would make the request and 'staple' the response to the certificate when they served it. Because the OCSP response is short lived and digitally signed by the CA, the client can trust the stapled OCSP response. The final problem was that the client had no idea that the site in question supports OCSP and whether or not it should expect them to staple an OCSP response. Thus, we finally arrived at OCSP Must-Staple.
Setting up OCSP Must-Staple is fairly easy as it's simply a flag that needs to be set by your CA in the certificate they generate for you. This flag instructs the browser that the certificate must be served with a valid OCSP response or the browser should hard fail on the connection. How you obtain your certificates will depend on how you set the OCSP must-staple flag but if you followed my previous guide on Getting started with Let's Encrypt then it's really easy. First of all you need to edit the OpenSSL config file used to generate your CSR.
Next, find the section where you set your
req_extensions, for me I call it
req_extensions = v3_req
This section will contain other details including your SAN.
[ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names
To setup OCSP Must-Staple all you need to do is add the following line.
[ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names 184.108.40.206.220.127.116.11.24 = DER:30:03:02:01:05
Don't worry too much about the details here, but if you do want to know then 18.104.22.168.22.214.171.124 is the object identifier for SMI Security for PKIX Certificate Extension and 24 is the id assigned to RFC 7633. After the = is the ASN.1 DER encoding of the Features structure, or the value
5 to you and I. If you're using OpenSSL 1.1.0 or higher then you can specify this extension in a much prettier way, check the update at the bottom of the article. With that aside, you're ready to regenerate your CSR.
openssl req -new -key private.key -out scotthelme.csr -config openssl.cnf -sha256
With this you can now call your renewal script or call acme_tiny (or which ever Let's Encrypt client you're using) to get a new certificate that will contain the OCSP must-staple extension.
Checking for OCSP must-staple
The easiest way to check if you're new certificates are properly flagged as OCSP Must-Staple is with the awesome SSL Labs test built by Ivan Ristic. A quick scan will tell you exactly what you need, just look in the Authentication section of the report.
You can also do this from the command line before you try to use the certificates by checking both the CSR and the signed certificate you obtain. To check your certificate use the following command.
openssl x509 -in signed.crt -noout -text
In the output you're looking for the x509v3 extensions section and specifically 126.96.36.199.188.8.131.52.24 which is what we created earlier.
X509v3 extensions: X509v3 Subject Alternative Name: DNS:scotthelme.co.uk,DNS:www.scotthelme.co.uk 184.108.40.206.220.127.116.11.24: 0....
It's the same approach for the CSR with a change on the command to use
req instead of
x509 and the appropriate CSR file.
openssl req -in scott.csr -noout -text
In the output from the CSR you're looking for the exact same thing as above in the certificate.
Revocation checking that's reliable
The big problem that we had with revocation checking was that we couldn't rely on it. CRLs were bad, OCSP endpoints were unreliable and stapling helped but we didn't know if the site supported it. Now we do. In the event of a compromise or any other scenario where you find yourself needing to revoke your certificate you can be confident that when the client receives your certificate in a connection it will be forced to check for a stapled OCSP response. This offers a huge level of protection and reduces the potential time an attacker can abuse a compromised certificate from the maximum life of the certificate, which could be up to 39 months, down to the maximum life of the last valid OCSP response, which could be a few hours.
It's not perfect but OCSP must-staple presents the first opportunity for us to rely on revocation actually working. There are some concerns about depending on your CA to deliver you a valid OCSP response to serve and the implementation of OCSP stapling in web servers could do with a little work too. I'm publishing another blog this week about a mechanism similar to CSP/HPKP reporting called OCSP Expect-Staple where the browser will report back if it doesn't receive a valid OCSP staple. Check back in a few days.
Update 14th Feb 2017
If you're using OpenSSL 1.1.0 or higher then there is a slightly nicer way of specifying the Must-Staple flag as pointed out by Rob in the comments below. Check your version of OpenSSL before using this:
openssl version OpenSSL 1.1.0c 10 Nov 2016
If your version is 1.1.0 or higher you can place the following in your
openssl.cnf file instead.
[ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names tlsfeature = status_request