Self-Signing Certificate Authorities

Introduction

If you run a website which receives or displays personal information, passwords or other secrets, you need to encrypt your connections using SSL or TLS. This is what puts the "S" into HTTPS, FTPS, IMAPS, POPS etc, and requires private keys and public certificates. Your browser (or other SSL/TLS client) trusts certain CAs (certificate authorities), and they in turn are willing to trust you by issuing you a certificate, if you throw money at them.

This is necessary for public-facing production deployments, and these days the cheapest certificates don't cost the earth - for example, Namecheap's start at £6/$9, and Let's Encrypt should also be launching this summer, providing free domain-validated certificates. However, cheap certificates are domain validated, where the CA checks that you own the domain using automated tests; these can be slow and laborious, so it's often not practical or possible to get these for internal services or development environments.

The alternative is to set yourself up as a self-signing CA. You won't be trusted by the public, but it's a good option if you're in the position where you're not public-facing, or where you can tell your users to install your root CA before they use your service. You'll be able to issue as many certificates as you want, and your connection should be just as secure as with a "proper" certificate.

The easy way

I've written caman which will do everything for you - it's a bash script with all the commands I'm about to describe in excruciating detail, so if all you want to do is jump to getting your certificates, go over to the project page and start following its instructions.

Read on if you want to know how to do it yourself.

What we're aiming for

We're going to create a directory which contains your CA, your host keys, and the certificates you generate. The structure will be:

ca/                 Your CA information
  caconfig.cnf      CA configuration (modify this)
  ca.key.pem        CA private key (keep this safe)
  ca.crt.pem        CA public certificate (distribute this)
  ca.crl.pem        List of revoked keys (publish this)
  serial            Next serial number
  index.txt         Registered certificates
  crlnumber         Next CRL number
  newcerts/         A copy of each certificate signed
store/              Your certificates
  host.domain.tld/  One folder per host
    config.cnf      Config for the given host
    YYYY-MM-DD/     Build date for cert set
      host.domain.tld.key.pem       Key
      host.domain.tld.csr           Signing request
      host.domain.tld.crt.pem       Certificate
      host.domain.tld.keycrt.pem    Key + cert combo

Prepare your CA

Before we start, you'll need openssl to be installed on your system. In Ubuntu, that would be:

sudo apt-get install openssl

Now we'll create the CA dir, and start the serial number at 1:

cd my_ca
mkdir ca store
mkdir ca/certs ca/private
echo '01' > ca/serial
touch ca/index.txt

You will now need to create your ca/caconfig.cnf file. You can use the caman template:

curl https://raw.githubusercontent.com/radiac/caman/master/ca/caconfig.cnf.default > ca/caconfig.cnf

You will need to change some lines - look for the comments starting # >>.

  • Change the 6 values under [ req_distinguished_name ]:

    • countryName: your two-character country code
    • stateOrProvinceName: your state or province
    • organizationName: the name of your organisation
    • organizationUnitName: your department in the organisation
    • commonName: the name of your organisation
    • emailAddress: your e-mail address
  • Change the CRL distribution points URL under [ usr_cert ] and [ v3_ca ]:

    • crlDistributionPoints: URL where you will publish your ca.crl.pem
    • The CRL is a list of revoked certificates which you'll need to update each time you revoke a host certificate; if you don't want to be bothered with this, you can just comment these lines out, as well as crl_extensions and crlnumber under [ CA_default ].
  • You can ignore the value for default_days here - caman uses it, but OpenSSL won't; we'll be passing it directly on the command line.

Create the root certificate

Your CA is identified by its root certificate. First we need to create the private CA key:

openssl genrsa -aes256 -out ca/ca.key.pem 4096

The openssl genrsa command generates an RSA key, and the other options tell it to encrypt it with AES 256 (-aes256), to use 4096 bits, and to write it to the file ca/ca.key.pem.

When you run the command, it will ask you for a password - keep it safe, you'll need it every time you want to generate and sign a new certificate.

This will generate the private CA key, ca/ca.key.pem. This is important:

  • Do not lose this key. Without it, certificates can't be signed or renewed
  • Do not disclose this key to anyone. If it is compromised, others will be able to impersonate the CA.

Now we can use the key to generate the public CA certificate:

openssl req -x509 -new -key ca/ca.key.pem -days 36500 -out ca/ca.crt.pem -config ca/caconfig.cnf

The openssl req command with -x509 tells OpenSSL to generate a self-signed certificate; the other options tell it to generate a new certificate (-new) and how long the certificate should be valid for - in this case, -days 36500 means about 100 years. It then uses the caconfig.cnf which we configured earlier, and puts the certificate in the file ca/ca.crt.pem.

Publishing your CA Certificate and CRL

To get clients to trust certificates signed by the new self-signing authority, they must install the root certificate, ca.crt.pem.

The easiest way to do this is to put it on your web server as a normal file for download, called ca.crt. Most browsers will know what to do with it from there.

If you're using Apache to serve your page, it may need to know the MIME type - add this to your configuration or .htaccess:

AddType application/x-x509-ca-cert .crt

You can then generate your CRL with the following command:

openssl ca -gencrl -out ca/ca.crl.pem -config ca/caconfig.cnf

To publish it, just put it at the URL you defined in your caconfig.cnf above. Remember to update it each time you renew or revoke a certificate.

Preparing for a new host

Before we start creating hosts, lets set up a directory to store them.

In this article we'll be creating a certificate for my.example.com:

mkdir store/my.example.com

To make a wildcard certificate to cover multiple hosts, just use an asterisk - for example, *.example.com would catch any subdomain of example.com.

You will now need to create a configuration for this host. Again, you can use the caman template:

curl https://raw.githubusercontent.com/radiac/caman/master/ca/host.cnf.default > store/my.example.com/config.cnf

You will need to change some lines - look for the comments starting # >>.

  • Change 4 of the values under [ host_distinguished_name ]:

    • countryName: the two-character country code for this host
    • stateOrProvinceName: the state or province for this host
    • organizationName: the name of the organisation for this host
    • emailAddress: the e-mail address for the admin for this host
    • Do not change commonName or organizationUnitName - these are placeholders which will be set by caman
  • You can ignore the value for default_days here - caman uses it, but OpenSSL won't; we'll be passing it directly on the command line.

You're now ready to make the certificate itself.

Creating a host certificate

First create a new sub-directory to store this set of files - there will be a key, a CSR (certificate signing request) and a certificate. I like to use the date I'm generating the certificate:

mkdir store/my.example.com/2015-05-18

Now we'll create a new private key and CSR with this command:

openssl req -sha256 \
    -newkey rsa:2048 -nodes \
    -keyout store/my.example.com/2015-05-18/my.example.com.key.pem \
    -new -out store/my.example.com/2015-05-18/my.example.com.csr \
    -config store/my.example.com/config.cnf

This again calls openssl req, but without -x509 this time because we don't want it to be self-signed - we want to sign it using our own CA. We then tell it to use SHA256 (-sha256), and generate a new 2048 bit RSA private key (-newkey rsa:2048) which is not encrypted (-nodes - that's "no DES", not "nodes") in the file ending .key.pem. It will then use this to create a new CSR (-new) in the file ending .csr. It uses the host config we created earlier to provide any additional settings.

Next we need to sign the CSR to generate the certificate:

openssl ca \
    -in store/my.example.com/2015-05-18/my.example.com.csr \
    -out store/my.example.com/2015-05-18/my.example.com.crt.pem \
    -days 3650 -config ca/caconfig.cnf

This calls openssl ca to use the certificate authority to sign the CSR (-in) and generate the certificate (-out). Here it will be valid for 3650 days (-days), or approximately 10 years. We give it the CA config this time, so it knows where to find all the settings for our CA.

You will now have a private key ending .key.pem, and a public certificate ending .crt.pem. In most cases you'll use these separately, but some services may want them merged together in one file (such as Apache wth the SSLCertificateFile directive), so we'll concatenate them just in case it's needed:

cat store/my.example.com/2015-05-18/my.example.com.key.pem \
    store/my.example.com/2015-05-18/my.example.com.crt.pem \
    > store/my.example.com/2015-05-18/my.example.com.keycrt.pem

You can now copy those three files to the server (you won't need the .csr) and configure your services to use them. That's a bit beyond the scope of this article, but may be something I'll follow up with in a later one.

Revoking and renewing certificates

If a host's certificate has expired, is no longer needed, or has been compromised, you must revoke the current certificate for that host, and generate and publish a new CRL.

First check ca/index.txt for the index corresponding to the host you're revoking:

more index.txt | grep my.example.com

You will see something like this:

R       250513123511Z   150516124528Z   01      unknown /C=CN/ST=State/O=MyOrg/OU=my.example.com/CN=my.example.com/emailAddress=email@example.com
R       250513124545Z   150516124604Z   02      unknown /C=CN/ST=State/O=MyOrg/OU=my.example.com/CN=my.example.com/emailAddress=email@example.com
V       250513124606Z                   03      unknown /C=CN/ST=State/O=MyOrg/OU=my.example.com/CN=my.example.com/emailAddress=email@example.com

If a line starts with an R it means that certificate has been revoked; look for the one starting V with CN= which matches your host; it will almost certainly be the last one. Then take the short number in the fourth column; in the example above, the index is 03.

Now you have the index, you can revoke that certificate:

openssl ca -revoke ca/newcerts/03.pem" -config ca/caconfig.cnf

replacing 03 with the index for that certificate.

If you are replacing the certificate, you can now create a new host certificate exactly as you did in the Creating a host certificate section above.

Don't forget to update and publish a new CRL, as described under Publishing your CA Certificate and CRL.

Comments

Exactly what I needed. Awesome! Thank you.

I don't really like the CA model, and believe anyone should be able to setup its own CA, just like GPG keys. Peers should be able to trust the CA's they want easily.

Thanks for the comment and contributions!

I agree, the current model isn't that great - I understand the need for a widely-accepted set of root CAs for the modern web to work, but the ability to add new CAs for trusted peers is essential. Unfortunately we're starting to see places where unofficial CAs aren't as trusted - eg if you install your CA in an Android device, you'll get a "Network may be monitored" warning every time you turn it on.

Leave a comment