Sunday, 14 July 2019

Private CA Part 1: Building your own root and intermediate certificate authority

Getting an SSL certificate these days has become much easier than it was in the past, with the availability of free Certificate Authorities (CAs) like Let's Encrypt. But even so, there are scenarios when you need a certificate that couldn't be issued by them: longer term certificates, complex wildcards, local addresses within your environment, and even routers that are accessed by IP instead of a dns name. Some of these could be issued by a paid CA, others aren't even an option. Code signing certificates are also great, but not cheap, while encryption and authentication certs are generally only issued in enterprise environments.

Getting a self-signed certificate is pretty easy - most routers will generate their own certificates, and it's pretty straightforward to create your own certificate using openssl or similar tools. The problem with self-signed certificates is that they won't be trusted by default. You still get the benefit of your connection being encrypted, but there won't be a guarantee that nobody intercepted your data, altered it and signed it with their own untrusted cert, unless you check the certificate every time. You could always add your certificate to your local trust store, but you'd have to do that for every single certificate you create, on every device you access them, which will quickly become cumbersome.

The solution is simple - you can create your own private CA and add it to your trust store. Any certificates created by that CA would be trusted as well, which makes managing this considerably easier! You wouldn't use these certs on your public website, but they'd be perfect for internal services or your home lab.

Taking one step further, you could also create intermediary CAs, creating a trust chain - the end device certificates would be created by your intermediary CA. If your intermediary CA keys get compromised, you could just revoke them and create a new intermediary, and won't need to update the trust store on your machines.

In these articles I'll put down what I learned while creating my own CA. I've decided to break this down into several parts, to make it easier to digest and manage:

The main requirements for setting up and managing your CA would be an installation of OpenSSL, and having the correct time and date set on your machine. Once ready, create a folder structure for your CAs. In my case, I created a separate folder for each CA - inside you have everything you need for that CA - configuration, private keys, certificates etc.

I also recommend initialising a local git repo in each CA path, especially while experimenting - it will help you keep track of your changes, and revert any steps you made if you made any mistakes! You don't have to do it, but I found it useful for myself. If you do, make sure you don't push your repo to a public server and expose your private keys. I just kept it as a local repository to keep track of the file/CA changes.

Root CA

First we set up a folder structure for our root CA, add the folders and initial files we'll be using later:

mkdir /root/ca
cd /root/ca
mkdir newcerts certs crl private requests
touch index.txt
touch index.txt.attr

The folders created will be used for:

  • newcerts - will store ALL the historically generated certificate files
  • certs - will be used to store the certificates, and I also store my .pfx files here
  • crl - will store the Certificate Revocation List (CRL) files
  • private will store the private keys for our certificates
  • requests will be used to store the certificate request files (.csr)

The index.txt is the "database" used by OpenSSL to manage the CA

Once we have the basic structure, we can configure our CA. You can use my root openssl.conf as a baseline, copy it to your CA folder, and at the very least customise entries marked with ### CHANGEME

Once everything is configured - we can create our private key and root certificate! (don't forget to adjust the file names) In my case, I created the root CA with an expiration of 25 years (9125 days)

openssl genrsa -aes256 -out private\root.key
Generating RSA private key, 2048 bit long modulus (2 primes)
e is 65537 (0x010001)
Enter pass phrase for private\root.key:
Verifying - Enter pass phrase for private\root.key:

openssl req -config openssl.conf -new -x509 -extensions v3_ca -key private\root.key -out root.crt -days 9125
Enter pass phrase for private\root.key:

Intermediate CA

Creating the intermediate CA is a similar affair. Initialise the folder structure and the files you'll need:

mkdir /root/interm
cd /root/interm
mkdir newcerts certs crl private requests
touch index.txt
touch index.txt.attr

In a similar fashion, you need a configuration file - note that the intermediary CA doesn't have to be the same as the root CA. In my case, they are a number of differences - you can use my intermediate openssl.conf as your baseline, and adjust it accordingly.

Once configured - it's time to create your private key, and intermediate CA request:

openssl genrsa -aes256 -out private\interm.key
Generating RSA private key, 2048 bit long modulus (2 primes)
e is 65537 (0x010001)
Enter pass phrase for private\interm.key:
Verifying - Enter pass phrase for private\interm.key:

openssl req -config openssl.conf -new -key private\interm.key -out ..\ca\requests\interm.csr
Enter pass phrase for private\interm.key:

With your CSR created, we're going back to our CA and will sign this request. One thing to note - each certificate you create will have a unique serial number. In my case, I wanted them to be unique and nondeterministic - for this reason before generating a certificate, I write a new hash into a file called serial. If you instead want the certificate serial numbers to be ordinal, you could just create a serial file with just one line for your initial serial value. Each time a certificate is created, the value in the serial file will be auto-incremented.

The output below is from my intermediate certificate:

cd /root/ca
openssl rand -out serial -hex 20
openssl ca -config openssl.conf -extensions v3_intermediate_ca -notext -in requests\interm.csr -out certs\interm.crt
Using configuration from openssl.conf
Enter pass phrase for /root/ca/private/interm.key:
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number:
            Not Before: May 12 12:31:29 2019 GMT
            Not After : May 10 12:31:29 2024 GMT
            countryName               = GB
            localityName              = London
            organizationName          = FlexLabs Ltd
            commonName                = FlexLabs Ltd Test Web CA
            emailAddress              = [email protected]
        X509v3 extensions:
            X509v3 Subject Key Identifier:
            X509v3 Authority Key Identifier:

            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
            X509v3 CRL Distribution Points:

                Full Name:

            Authority Information Access:
                CA Issuers - URI:
                OCSP - URI:

Certificate is to be certified until May 10 12:31:29 2024 GMT (1825 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Once a CA signs a certificate, it is also copied to the newcerts folder and is named after it's serial number. The index.txt database is also updated, and get a new row for the generated certificate, containing the status, time of issue, time of revocation (if revoked), serial and the subject:

V	240510123129Z		AACB1CD1F08AEFD28A7C33F22953180F1FF453A9	unknown	/C=GB/L=London/O=FlexLabs Ltd/CN=FlexLabs Ltd Test Web CA/[email protected]

You can copy the generated certificate to the root of your intermediary CA. I also create a chain file, containing both the intermediary CA and it's parent:

copy certs\interm.crt ..\interm\interm.crt
copy certs\interm.crt ..\interm\interm.chain.crt
echo root.crt >> ..\interm\interm.chain.crt

Installing the root certificate

Finally, don't forget to install your root certificate into your trusted certificate store, to make sure your certificates are recognised as trusted!

On a Windows machine, you can double click your root certificate file, and click on "Install Certificate" which will start the "Certificate Import Wizard". Make sure you pick "Local Machine" in the "Store Location" field. When asked for the "Certificate Store", pick the "Place all certificates in the following store" and select the "Trusted Root Certification Authorities" store.

In the second part I'll discuss creating certificates that can be used on web servers, e-mail encryption, code signing and so on.