Setting up a private Certification Authority with OpenSSL

Configuring your own CA is a task that can be done with OpenSSL. In this post I'll show how to create a self signed master root certificate and the steps to create signing requests and sign them.

Creating the CA environment directory

The certificate environment is not more than a directory to work on. Organizing files that way makes the CA management easier for us. We also create a .conf file to save time on issuing OpenSSL commands.

So, create your working directory for the CA and then, within this directory, we need two create two more: certs and private. The certs directory is for placing all the certs that will be created by our CA, and private will be used to store the copy of our CA key. The private directory permissions should be 0700.

Besides those directories, we will create four files that our CA infrastructure will need:

  1. One to track the last serial number that was used for cert generation. It's called serial and we initialize it to contain the value 01. OpenSSL expects this value to be in hex, and it must contain at least 2 digits, so we must pad it with a 0 in the left.
  2. The second is kind of a database to keep track of the certs that have been issued by this CA. OpenSSL requires that this file exists, but as we haven't created any cert, the file will be empty. Its name is index.txt.
  3. The third file is the database attributes file, which in the beginning should have the value of unique_subject = no. The default of this property is yes, but OpenSSL recommends to change it to no, as explained here.
  4. The configuration file. We'll be working on this one in the next sections of this post. For OpenSSL to read that file, before starting executing commands we need to setup an environment variable called OPENSSL_CONF with the absolute path of the config file as its value: export OPENSSL_CONF=/path/to/environment/openssl.cnf.

An example of how the directory should look like, assuming our environment directory is foobar:

[drwxrwxr-x]  foobar
├── [drwxrwxr-x]  certs
├── [-rw-rw-r--]  index.txt
├── [-rw-rw-r--]  index.txt.attr
├── [-rw-rw-r--]  openssl.cnf
├── [drwx------]  private
└── [-rw-rw-r--]  serial
Issuing our self signed root certificate

So here we start with the configuration file. In this first section we'll set the variables needed to create out root certificate:

  • default_bits specifies the length of the key. A value of 2048 is considered safe here.
  • default_keyfile is the path where the key will be created.
  • default_md is the message algorithm that will be used to sign certs and CRLs.
  • prompt will define if OpenSSL will ask for information to fill the distinguished name fields.
  • distinguished_name makes reference to he section that contains the information for the distinguished name fields. A reference of the available fields can be found here.
  • x509_extensions makes reference to the section that lists the extensions for the certificate. These extensions determine how certificates are going to be used. A reference of the standard extensions can be found here. The only "contraint" we're using here, refers to the certificate being used as a certificate authority.
  • encrypt_key is self explaining. The generated key will be encrypted depending on the value of this attribute. If encryption is enabled, you'll need to provide the passphrase each time a client certificate is signed.

Now that this configuration is in place, and assuming the OPENSSL_CONF environment variable has been configured, the root key pair and certificate can be created:

openssl req -newkey rsa -x509 -days 365 -out ca.crt -outform PEM

The -newkey rsa parameter tells OpenSSL to create a new certificate request along with a new private key (RSA), but -x509 changes the certificate request for a self signed certificate, with a validity of -days 365. The size of the private key and the location of it are specified in the openssl.cnf file we created. The remaining -out and -outform parameters are self explaining.

There should not be duplicated cert serial numbers issued by the same CA (the serial file keeps track of the last used serial number).

Configuring the Certificate Authority

To configure the behavior of our CA, some other options should be specified in the openssl.cnf file. These settings apply to the certificates signing process.

  • default_ca specifies the section that will contain the settings for our certificate authority.
  • dir is the directory where our CA environment directory is located.
  • The certificate, database, new_certs_dir, private_key and serial attributes are just references to the files we initially created to setup our environment directory.
  • default_crl_days defines how often CLRs are going to be issued.
  • default_days tells OpenSSL for how many days the certificates are going to be valid.
  • default_md, just as in the req section, specifies with message digest algorithm to use.
  • policy specifies the section that will be used to configure the certificates distinguished name fields. The 3 available options are match, supply and optional. Depending on their value, the fields on the certificate request should match with the ones of the root certificate, be supplied or make them optional.
  • x509_extensions, just like in the req section, specifies the extensions that the signed certificates will contain, in this case, no signed certificate from this CA can be used as a CA itself.
Creating CSRs for signing

To sign a certificate, a certificate signing request (CSR) should be sent to the Certificate Authority. These are created by the clients, so the OPENSSL_CONF variable has nothing to do here. So, the clients should use the openssl req command but without the -x509 option. Execute this command in any other location, since the generated files are not part of the CA environment, but hypothetically generated by a client user.

openssl req -newkey rsa -keyout client.key -keyform PEM -out client.csr -outform PEM -nodes

The generated .crt file is the one that should be sent to out CA to issue a certificate for the client. The command will ask for some data fields that correspond to the distinguished name of the root certificate. Depending on the CA configuration this CSR is being sent to, the value of the fields could be optional or strictly match with it. Remove the -nodes option if you want the generated private key to be encrypted.

The previous command generates both a new private key and the CSR to sign. If the client already has a private key, the command for creating a new CSR is slightly different and simpler:

openssl req -new -key client.key -out client.csr
Actually signing the CSRs

Now, going back to the CA environment. As our CA configuration is already in place in the openssl.cnf file (and the OPENSSL_CONF environment variable set), singing certificates will be easy:

openssl ca -in /path/to/client.csr

OpenSSL will ask for confirmation, sign the cert, and update its database. That's it! Take a look at the certs/ directory to retrieve the sign cert, and inspect the serial and index.txt fields to confirm that the database was actually updated.

Revoking certificates

This is also an easy process, just execute this command:

openssl ca -revoke /path/to/client.crt

The path to the client certificate could be in the same environment, since issued certificates are in the certs/ folder. You can also grab a copy elsewhere and execute the command with it. The only change it does is to update the database and mark the certificate as revoked. It's our responsibility to create a publish the revocation lists, and that could be done publicly using a some HTTP server. To generate a revocation list, issue the following command:

openssl ca -gencrl -out /path/to/ca.crl

The only entity that currently knows that the cert is revoked is the CA itself. That's why the CRL should be published to let users known that their certs are revoked. It's worth noting that if you use the certs in your application to grant some kind of access to it, you should also have a mechanism to block those revoked certs.

An example of a working CA configuration can be found here.