OpenSSL-based PKI (part 1)
Sources:
https://openssl-ca.readthedocs.io/en/latest/index.html
https://www.golinuxcloud.com/tutorial-pki-certificates-authority-ocsp/
https://social.technet.microsoft.com/wiki/contents/articles/2900.offline-root-certification-authority-ca.aspx
https://pki-tutorial.readthedocs.io/en/latest/index.html
An airgapped Root CA
The system that contains the RootCA is supposed to be airgapped, to be under controlled access, and to be used only for the emission of new CAs and revocation of compromised CAs.
In this example, we can put everything on the same server, but one should try to separate the RootCA from the other CAs as much as possible.
The first step is to create a Root Certification Authority (RootCA), which will only create other CAs.
Switch to the root
user:
|
|
We create a random complex password:
|
|
ayUiXnIjZFRh
We create the key and provide the complex password:
|
|
Generating RSA private key, 8192 bit long modulus (2 primes) ......................................................+++ ..+++ e is 65537 (0x010001)
Then we create (and self-sign) the RootCA certificate:
|
|
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [PT]: State or Province Name [Lisboa]: Locality Name [Lisboa]: Organization Name [TiagoJoaoSilva]: Organizational Unit Name [TJS]: Common Name []:ca-tjs-1 Email Address [bofh@tjs.lan]:
|
|
We can check the certificate’s contents:
|
|
Certificate: Data: Version: 3 (0x2) Serial Number: 25:92:4d:14:da:6f:9f:8b:4b:49:55:fc:68:be:9e:c7:57:24:cd:70 Signature Algorithm: sha512WithRSAEncryption Issuer: C = PT, ST = Lisboa, L = Lisboa, O = TiagoJoaoSilva, OU = TJS, CN = ca-tjs-1, emailAddress = bofh@tjs.lan Validity Not Before: Aug 12 19:38:30 2021 GMT Not After : Aug 7 19:38:30 2041 GMT Subject: C = PT, ST = Lisboa, L = Lisboa, O = TiagoJoaoSilva, OU = TJS, CN = ca-tjs-1, emailAddress = bofh@tjs.lan Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (8192 bit) Modulus: [...] Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0 X509v3 Authority Key Identifier: keyid:01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0 X509v3 Basic Constraints: critical CA:TRUE X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign Signature Algorithm: sha512WithRSAEncryption [...]
Certificate Revocation
Before we go any further, I think we have to talk right now about the revocation of certificates, unlike all other Internet tutorials which leave it until the end, if at all; if we do not configure Certificate Revocation now, it will be too late to be included in any certificate created by our RootCA.
How to revoke a certificate
|
|
-crl_reason
can also have the values keyCompromise, CACompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold, or (for Delta-CRLs only) removeFromCRL
-crl_hold
automatically places the reason as certificateHold but that’s outside the scope of this document
-crl_compromise AAAAMMDDHHMMSSZ
sets the reason as keyCompromise on the suppied date.
-crl_CA_compromise AAAAMMDDHHMMSSZ
does the same, but the reason is CACompromise
We need to set up a mechanism for the RootCA to disseminate the information of the revoked certificates, and this needs to be done before creating any subsequent certificate, by adding information to the RootCA’s openssl.cnf
before de creating any certificate (up to and including other CAs).
CRL distribution
The classic method is the direct distribution of the CRL lists, which can be done by various means, such as an HTTP server, an LDAP or AD directory, etc.; there may still be an additional complication because to avoid the constant transmission of large files you can use Delta-CRLs containing only the changes to the CRL, but while these can be implemented in Windows or other PKI packages, they are not available on OpenSSL.
These lists are usually updated (because they expire and all CA certificates are no longer accepted) at a maximum of every 30 days, but the RootCA’s CRL list should not expire (or expire only annually when it is time to update the system that hosts the RootCA).
[CA_default]
default_crl_days = 385
At that time, the CRL is refreshed and (in the case of an airgapped RootCA system) extracted on paper, QR Code, CD-R or other safe medium, then taken to the system from where it will be distributed.
The distribution format of these lists is DER (binary coding), but the file must have a .CRL extension (RFC 2585).
The other method, more recommended these days, is using an OCSP Responder to answer queries about the validity of a certificate interactively, which is a quick operation that does not require much bandwidth, certifies the revocation by signing it with a key from the originator CA, and that answer can be cached on the clients (through OCSP Stapling).
As the RootCA’s CRL will contain very few certificates (the RootCA will only issue other CAs, called IntermediateCAs, SubordinateCAs ou
SigningCAs, which won’t be many), let’s set up a CRLDistribution package for the RootCA.
We begin by putting the information from where the CRL will be served on the [ v3_intermediate_ca ]
section of /root/ca/openssl.cnf
, to be
Included in any certificates issued by the RootCA.
We will use an HTTP server on port 7788:
[ v3_intermediate_ca ]
crlDistributionPoints = URI:http://server.tjs.lan:7788/root-ca.crl
We create the CRL (https://blog.didierstevens.com/2013/05/08/howto-make-your-own-cert-and-revocation-list-with-openssl/)
|
|
We verify the contents of the CRL:
|
|
Certificate Revocation List (CRL): Version 2 (0x1) Signature Algorithm: sha512WithRSAEncryption Issuer: C = PT, ST = Lisboa, L = Lisboa, O = TiagoJoaoSilva, OU = TJS, CN = ca-tjs-1, emailAddress = bofh@tjs.lan Last Update: Aug 13 23:46:07 2021 GMT Next Update: Sep 2 23:46:07 2022 GMT CRL extensions: X509v3 Authority Key Identifier: keyid:01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0 X509v3 CRL Number: 4097 Revoked Certificates: Serial Number: 1001 Revocation Date: Aug 13 22:11:25 2021 GMT CRL entry extensions: X509v3 CRL Reason Code: Unspecified Serial Number: 1002 Revocation Date: Aug 13 23:41:51 2021 GMT CRL entry extensions: X509v3 CRL Reason Code: Superseded Signature Algorithm: sha512WithRSAEncryption [...]
And we need to convert the PEM (Base64) into DER format:
|
|
Network-connected system
Since the RootCA should be airgapped and offline, it is not on the RootCA’s system that we should configure the CRL’s distribution because that system must be accessible on the network.
We set up a new Debian 11 system, and recreate the folder that contains the RootCA’s CRL:
|
|
We bring the root-ca.crl
file to it using a safe mechanism (burn a CD-R, printing and scanning with OCR, etc.) e place it in /root/ca/crl
Install and configure the CRLDistribution webserver
Let’s use a very basic webserver, lighttpd
|
|
According to Debian’s wiki about Lighttpd, the default installation configures the service, grabs the 80/TCP
port, runs with the wser www-data
, set the webroot to be /var/www/html
, and the default file to be index.php
or index.html
, although it comes pre-configured to not run .php
files.
Let’s set up a different port (7788), another webroot
to not interfere with the defaults from other webservers, and we will disable the automatic redirection to index.html
In addition, and this can be important, the MIME type settings have been pre-configured with a Perl script, which we may have to edit and re-do because the CRL files have to be served by HTTP with a specific MIME type, application/pkix-crl
(https://pki-tutorial.readthedocs.io/en/latest/mime.html)
|
|
Change the lines:
1 server.modules = (
2 # "mod_indexfile",
3 # "mod_access",
4 # "mod_alias",
5 # "mod_redirect",
6 )
8 server.document-root = "/var/www/crl"
10 server.errorlog = "/var/log/lighttpd/crl-error.log"
14 server.port = 7788
41 #index-file.names = ( "index.php", "index.html" )
Create a new webroot
:
|
|
Move the original index.html
installed by the lighttpd
package to that place and deactivate it:
|
|
Place the DER-format CRL inside webroot
:
|
|
To configure the MIME types, the lighttpd.conf
package called a script that creates a MIME configuration from the types defined in /etc/mime.types
; did the latter already have a definition for the CRL’s MIME type?
|
|
application/pkix-crl crl
Therefore, line 47 of
lighttpd.conf
will create a list of MIME types that includes our CRL type each time the lighttpd
service is started.
47 include_shell "/usr/share/lighttpd/create-mime.conf.pl"
We check lighttpd.conf
’s syntax for errors:
|
|
2021-08-14 02:26:38: configfile.c.1142) WARNING: unknown config-key: url.access-deny (ignored)
This error isn’t important, so:
|
|
● lighttpd.service - Lighttpd Daemon Loaded: loaded (/lib/systemd/system/lighttpd.service; enabled; vendor preset: enabled) Active: active (running)
|
|
Synchronizing state of lighttpd.service with SysV service script with /lib/systemd/systemd-sysv-install. Executing: /lib/systemd/systemd-sysv-install enable lighttpd
We open port 7788 in the firewall:
|
|
And test access to the file:
|
|
Resolving localhost (localhost)... ::1, 127.0.0.1 Connecting to localhost (localhost)|::1|:7788... connected. HTTP request sent, awaiting response... 200 OK Length: 1359 (1.3K) [application/pkix-crl] Saving to: ‘root-ca.crl’ root-ca.crl 100%[=================================================>] 1.33K --.-KB/s in 0s 2021-08-14 02:33:18 (168 MB/s) - ‘root-ca.crl’ saved [1359/1359]
Connection was established and the file is served with the correct MIME type.
But unfortunately, you should only use CRLs when we are sure that our clients support this format (and in this regard, none is better than Microsoft). For example, it is impossible to revoke webserver certificates by CRLDistribution because only Internet Explorer has the code to download and analyze a CRL (Firefox and Chrome don’t even care) https://news.netcraft.com/archives/2013/05/13/how-certificate-revocation-doesnt-work-in-practice.html .
That’s why it’s safer to set up the alternative mechanism, which is an OCSP Responder, also called AIA due to the directive that configures it ( AuthorityInfoAccess
).
Setting up the OCSP Responder on the RootCA’s CNF
We start by placing on the RootCA’s openssl.cnf
the following, on the [ intermediate_ca ]
section:
authorityInfoAccess = OCSP;URI:http://ocsp.tjs.lan:7899
We can use any TCP port for the OCSP Responder, and it’s usually listening on a seldom-used HTTP port (like 8008). I chose port 7899 because after analyzing IANA’s port list, TCP ports 7888-7899 are not assigned, and may be used for this purpose. If we weren’t using CRLD, Lighttpd could also be switched off and port 7788 reused.
The protocol is HTTP and not HTTPS because how would any OCSP’s SSL Certificate be checked? (https://social.technet.microsoft.com/Forums/office/en-US/c65e1784-39be-4732-a135-bfba7446ad05/should-the-ocsp-responder-service-be-running-http-80-or-https-443-)
The configuration of an OCSP Responder will be documented in the next part, which will about configuring an IntermediateCA. But we left the information in the RootCA’s .CNF in case we want to implement an OCSP Responder later.
Note: if this information is not configured now, it can only be introduced on the PKI (and passed to any downstream certificates issued by the RootCA) by revoking and re-issuing all certificates already issued; therefore the structure of a PKI should be very well-planned before being implemented.
Now we can start the IntermediateCA’s configuration.