Rolling my own Certificate Authority

I'm about to deploy a small sideproject I've been coding the past 2 weeks and I want to make sure that the site is served over https. Since I'll likely put it on a subdomain to my own company and I don't want to go get another signature from a public authority I thought I would try to roll my own mini-CA chain which I can use for these types of situations.

My basic idea is to set it up using a root certificate, an intermediate certificate and a project specific certificate. The root certificate signs the intermediate certificate which will sign the project certificate. So we have a very simple chain that way. I will store the root certificate offline on a usb device and have a accessible certificate revocation list hosted in a static directory on my company's website.

Note that it's important to keep the signed certificates somewhere safe too. If we don't have access to the cert then we can't revoke it!

Creating the certificates and keys

First I will generate the root and intermediate certificates. I'll put a password on the root cert using the -des3 flag. The root cert will also be self-signed.

openssl genrsa -des3 -out blacknode.root.pem 4096
openssl genrsa -out blacknode.dev.pem 4096
openssl req -new -x509 -days 3650 -key blacknode.root.pem -out blacknode.root.crt

Since I want to use the openssl ca tool I need to set up the directory a certain way.

mkdir ca
mkdir ca/private
mkdir ca/newcerts
touch ca/index.txt
echo "01" > ca/serial
cp blacknode.root.crt ca/
cp blacknode.root.pem ca/private

Finally create a file openssl.cnf describing the structure further. Please note the default_md and default_days values. Default_md must be a hash algorithm good enough to pass for firefox. The default MD5 which I copied from the openssl example was NOT. Having a low default_days value might cause headaches in the future when you have to renew the certs.

[ ca ]
default_ca      = CA_default            # The default ca section

[ CA_default ]

dir            = ./ca              # top dir
database       = $dir/index.txt        # index file.
new_certs_dir  = $dir/newcerts         # new certs dir

certificate    = $dir/blacknode.root.crt       # The CA cert
serial         = $dir/serial           # serial no file
private_key    = $dir/private/blacknode.root.pem# CA private key
RANDFILE       = $dir/private/.rand    # random number file

default_days   = 3650                   # how long to certify for
default_crl_days= 30                   # how long before next CRL
default_md     = sha256                   # md to use

policy         = policy_any            # default policy
email_in_dn    = no                    # Don't add the email into cert DN

name_opt       = ca_default            # Subject name display option
cert_opt       = ca_default            # Certificate display option
copy_extensions = none                 # Don't copy extensions from request

[ policy_any ]
countryName            = supplied
stateOrProvinceName    = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional

[ v3_req ]
basicConstraints = critical,CA:false
keyUsage = nonRepudiation
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always

[ v3_ca ]
subjectKeyIdentifier = hash
extendedKeyUsage = critical,serverAuth, clientAuth
basicConstraints = CA:true
keyUsage = cRLSign, keyCertSign, digitalSignature, nonRepudiation,keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign

Then sign the blacknode.dev certificate using the root cert.

openssl ca -config openssl-root.cnf -extensions v3_ca -out blacknode.dev.signed.crt -infiles blacknode.dev.csr

The output should look something like the following. Note the basicConstraints and key usage sections. These allow the signing certificate to sign new certs too.

Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Feb 24 15:03:47 2015 GMT
            Not After : Feb 24 15:03:47 2016 GMT
        Subject:
            countryName               = SE
            stateOrProvinceName       = Stockholm
            organizationName          = Blacknode
            organizationalUnitName    = Development
            commonName                = dev.blacknode.se
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                1B:E0:15:74:26:E8:9A:C6:D4:51:85:D8:73:EA:A4:5D:85:0A:C6:AD
            X509v3 Extended Key Usage: critical
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints:
                CA:TRUE
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign

Phew, halfway there.

Intermediate/Signing CA

So now I have my root ca which I put on a usb stick. I copy the needed public certs and create another ca directory in my home directory by repeating the procedure in the previous section, except this time using the blacknode.dev.pem key instead. Basically a lot of this is the same as before. We also create a similar openssl.cnf file here, but with the right paths to the dev cert instead.

Signing a project certificate.

Since I trust myself, I'll create the key for my https service here directly.

openssl genrsa -out faktura.pem 2048
openssl req -new -days 1095 -key faktura.pem -out faktura.csr
openssl ca -config openssl.cnf -in faktura.csr -out faktura.signed.crt

Finally, create a chained certificate and verify.

cat blacknode.dev.signed.crt  blacknode.root.crt > blacknode.chained.crt
[tethik@asimov dev-ca]$ openssl verify -CAfile blacknode.chained.crt faktura.signed.crt
faktura.signed.crt: OK

Now I can copy the keys and cert to the server. I intend to write a shorter post about setting up https on apache in the future.

Also left to do is setting up a certificate revocation list for each signing certificate and publishing them somewhere. Probably on my main company website.

Installing the certificates on a client machine

For a linux system, installing the certs is easy. Just copy the root public certificate (in my case blacknode.root.crt) to the /etc/ssl/certs folder. For accessing your https-enabled websites etc in your browser you may have to add the cert to the browsers trusted certificate too. In Firefox, you can do this by going to 'Preferences', then under the 'Advanced' tab open the 'View certificates' window. Then under Authorities import the root certificate.

Of course, this implies a great trust in that certificate. So be careful. I only intend to use this cert for projects which are not supposed to be used by the public.

Sources

https://www.openssl.org/docs/HOWTO/certificates.txt

https://www.openssl.org/docs/HOWTO/keys.txt

https://www.openssl.org/docs/apps/ca.html#examples

http://pages.cs.wisc.edu/~zmiller/ca-howto/

http://wiki.samat.org/CheatSheet/OpenSSL#Display_certificate_information