How-ToAbout

OpenSSL Root Certificate Authority (CA)

This post details how to setup a root OpenSSL Certificate Authority using Elliptic-curve cryptography (ECC) based on the following two resources.

Tested with:

  • OpenBSD 6.6 GENERIC
  • LibreSSL 3.0.2

Root Certificate Authority Configuration

The greatest hurdle to building out a certificate infrastructure is planning. One must understand how certificates will be used short term and long term as both will impact the overall design. Much planning is centered around how certificates are issued to users/services. Not just the type of certificate being distributed but more importantly how it gets issued from the certificate infrastructure. That is why certificate service providers will have many levels of CAs in the certificate chain. Each tier is used to enforce a policy for delegating how each type of certificate is issued. Thankfully, setting up a root CA is fairly straight forward since it will often serve one of two roles:

  1. Approve Subordinate CA - used for multi-tier certificate infrastructures.
  2. Distribute Certificates - common in small networks where the root CA also distributes certificates.

This post will cover a root CA used to approve one or more subordinate CAs. This is often referred to as an offline root CA though not entirely offline as it must periodically publish a Certificate Revocation List (CRL). The following table shows the desired properties of the root CA.

Desired Root CA Certificate Properties
Algorithm:secp521r1
CRL Published Interval:90 days
Signature Hash Algorithm:SHA512
Specific "Valid from":January 1 00:00:00 1970 GMT
Specific "Valid to":December 31 23:59:59 9999 GMT
AIA and CRL locations:pki.domain.com
Online Certificate Status Protocol (OCSP)Not used.

Some points worth mentioning in regards to the desired properties of the Root CA.

secp521r1

Many docs and how-tos will use P384. This could be because P521 isn't listed in NSA suite B or compatibility with an existing application. Whatever the case, be sure to test your own enviornment before using any ECC algorithms.

90 day CRL

Is the maximum amount of time an invalid certificate will be trusted on the network. Since this will be a root CA, a revoked issuing CA certificate will be trusted for a maximum of 90 days. Getting around this limitation requires manually updating all clients using this certificate infrastructure. Choose a value that works best.

Validity Dates

Choose whatever dates you want. This post should be considered a proof-of-concept rather than a guide for a production quality CA. For production quality, there would have been much discussion about protecting the private key, Hardware Security Module (HSM) or similar device, backups, auditing, etc. Thus the seemingly nonsensical range.

AIA and CRL

This should be a website dedicated to hosting certificates and CRLs supporting the certificate infrastructure. Ideally there should be a web server hosting this site on your LAN and another on the public internet. This way your PKI is still available to internal hosts if connectivity is severed while external clients/services have the necessary files when outside the LAN. Note that this will require some form of split-horizon DNS.

No OCSP

OCSP was designed for certificate infrastructures with large CRLs. Instead of clients downloading these large files, they issue an HTTP request to the OCSP service specifying the certificate's serial number. The OCSP service checks the serial number against the same CRL the client would have downloaded. Making OCSP only as current as the CRL. The response is created and signed, using an OCSP signing certificate, before being sent back to the client. As you can see, this is a great solution for large service providers since each OCSP request/response is very small.

Hosting the OCSP service has administrative overhead. In windows it runs as an .ASP script using a .NET clr while other platforms use a CGI based solution. Both require an OCSP signing certificate and a copy of the latest CRL.

This deployment will not have large CRLs and thus doesn't justify the amount of administration required to support it.

If this feature is of interest, update the necssary .cnf files with the OCSP service locations and then setup the OCSP websites.

Create Folder Structure

The following creates the folder structure and necessary files to get started.

Note that the password to protect the private key is documented during this step. If a specific password is required be sure to update private/passphrase as it will be used later for automating CRL publishing.

CANAME="RootCA" BASEPATH="/var/CA/" mkdir $BASEPATH mkdir $BASEPATH$CANAME mkdir $BASEPATH$CANAME/certs mkdir $BASEPATH$CANAME/db mkdir $BASEPATH$CANAME/private openssl rand -hex 25 > $BASEPATH$CANAME/private/passphrase touch $BASEPATH$CANAME/db/index openssl rand -hex 16 > $BASEPATH$CANAME/db/serial echo 1001 > $BASEPATH/$CANAME/db/crlnumber chmod -R 700 $BASEPATH$CANAME chmod 400 $BASEPATH$CANAME/private/passphrase

Create Root CA OpenSSL Configuration File

OpenSSL needs a configuration file for the subsequent commands. The following is a product of the previously mentioned resources.

Update the necessary settings within this file to reflect your environment then save it to: /var/CA/root-ca.cnf

[ default ] name = RootCA domain_suffix = domain.com aia_url = http://pki.domain.com/$name.crt crl_url = http://pki.domain.com/$name.crl default_ca = ca_default name_opt = utf8,esc_ctrl,multiline,lname,align [ ca_dn ] organizationName = "domain.com" commonName = $name [ ca_default ] home = /var/CA/$name database = $home/db/index serial = $home/db/serial crlnumber = $home/db/crlnumber certificate = $home/certs/$name.crt private_key = $home/private/$name.key RANDFILE = $home/private/.RAND new_certs_dir = $home/certs unique_subject = no copy_extensions = none default_days = 7300 default_crl_days = 90 default_md = sha512 policy = policy_c_o_match name = $name [ policy_c_o_match ] countryName = optional stateOrProvinceName = optional organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] encrypt_key = yes default_md = sha512 utf8 = yes string_mask = utf8only prompt = no distinguished_name = ca_dn req_extensions = ca_ext [ ca_ext ] basicConstraints = critical,CA:true keyUsage = critical,keyCertSign,cRLSign subjectKeyIdentifier = hash [ sub_ca_ext ] authorityInfoAccess = @issuer_info authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:true,pathlen:0 crlDistributionPoints = @crl_info extendedKeyUsage = clientAuth,serverAuth keyUsage = critical,keyCertSign,cRLSign subjectKeyIdentifier = hash [ crl_info ] URI.0 = http://pki.domain.com/$name.crl [ issuer_info ] caIssuers;URI.0 = http://pki.domain.com/$name.crt [ name_constraints ] permitted;DNS.0=domain.com permitted;DNS.0=*.domain.com excluded;IP.0=0.0.0.0/0.0.0.0

Create Private Key

The following command will create a private key and encrypt it.

When prompted, use the password stored in /var/CA/RootCA/private/passphrase

openssl ecparam -genkey -name secp521r1 | openssl ec -aes256 -out RootCA/private/RootCA.key

Create Certificate Signing Request (CSR)

Create a CSR using settings from the configuration file and newly created private key.

openssl req -config /var/CA/root-ca.cnf \ -new -extensions ca_ext \ -key /var/CA/RootCA/private/RootCA.key \ -out /var/CA/RootCA/RootCA.csr

Approve CSR

Using the following command, approve the CSR. Values specified in the cli override values in the configuration file. If successful, the new certificate will be displayed followed by a "y/n" to confirm approval. Answer in the affirmative to continue on.

openssl ca -config /var/CA/root-ca.cnf -selfsign \ -keyfile /var/CA/RootCA/private/RootCA.key \ -startdate 19700101000000Z \ -enddate 99991231235959Z \ -out /var/CA/RootCA/certs/RootCA.crt \ -in /var/CA/RootCA/RootCA.csr \ -extensions ca_ext

Generate CRL

With the CA successfully created, generate a CRL using the following command.

Note the -passin parameter used to specify the password source.

openssl ca -gencrl -config /var/CA/root-ca.cnf \ -out /var/CA/RootCA/RootCA.crl \ -passin file:/var/CA/RootCA/private/passphrase

Publish Certificate

At this point, the website pki.domain.com should be available and able to serve content. Copy /var/CA/RootCA/certs/RootCA.crt to the website content directory so it can be accessed via: http://pki.domain.com/RootCA.crt

This website does not need HTTPS. In fact it should only be used with HTTP over TCP port 80. This is because the certificate has been digitally signed which protects it against any tampering.

Publish CRL

Publishing a CRL is tricky since cron doesn't offer a way to run a task every N days. To get around this, the following script is used. First it schedules the next launch then performs CRL related tasks.

Update paths in the script to reflect your environment. Specifically the scp command which copies the CRL to the pki.domain.com content directory.

Save the script to: /var/CA/RootCA-publish-crl.sh

#!/bin/sh # save crontab to file removing the entry of the currently running script /usr/bin/crontab -l | grep -v "/var/CA/RootCA-publish-crl.sh" > /var/CA/tmpCron # add new entry just short of 90 days /bin/echo $(date -j -r `expr $(date +%s) + 7775938` "+%M %H %d %m")" * /bin/sh /var/CA/RootCA-publish-crl.sh 2>&1" >> /var/CA/tmpCron # load updated crontab /usr/bin/crontab /var/CA/tmpCron # remove temporary cron file /bin/rm /var/CA/tmpCron # generate new CRL /usr/bin/openssl ca -gencrl -config /var/CA/root-ca.cnf -out /var/CA/RootCA/RootCA.crl -passin file:/var/CA/RootCA/private/passphrase # copy crl to website content directory /usr/bin/scp -r -p -4 -i /pki.domain.com.key /var/CA/RootCA/RootCA.crl some-user@pki.domain.com:/some/folder/RootCA.crl

To manually launch this script:

/bin/sh /var/CA/RootCA-publish-crl.sh

Test

It's important to test that RootCA.crt and RootCA.crl are available.

Download Files

Verify they're accessible via HTTP over TCP port 80.

ftp -o RootCA.crl http://pki.domain.com/RootCA.crl ftp -o RootCA.crt http://pki.domain.com/RootCA.crt

View Files

Review the certificate and CRL.

openssl x509 -text -noout -in RootCA.crt openssl crl -text -noout -in RootCA.crl

Test cron Script

Consider lowering the seconds value (e.g. 7775938) to 120 seconds for testing. This is a great way to ensure a new CRL is properly generated and copied to the pki.domain.com content directory.