Setting up your own certificate authority

Note: this page was written in 2008. It is still current, but there might subtle changes in tool invocation. The default key size has been increased to 2048 bits (was: 1024) and the default signing algorithm is now sha-256 (was: md5)

Sometimes I need to play around with some digital certificates and I do not feel like shelling out a lot of money each time to buy real ones. Here’s how to set up your own CA (certificate authority) in a quick-and-dirty way.

The scenario in which I am interested is to set up a single root-CA, which signs the certificates of two sub-authorities. The sub-authorities are the entities that actually sign the end-user certificates.

Create some directories and set up some initial files:

$ mkdir -p ca/root-ca/{private,certs} ca/person-ca/{private,certs} ca/site-ca/{private,certs}
$ touch ca/{root,site,person}-ca/certindex.txt
$ for i in ca/{root,site,person}-ca; do echo '100001' > $i/serial;done Generate the root certificate:
$ cd ca/root-ca

Create a new file called openssl.cnf with the following content:

#
# OpenSSL configuration file.
#

# Establish working directory.

dir                                     = .

[ ca ]
default_ca                              = CA_default

[ CA_default ]
serial                                  = $dir/serial
database                                = $dir/certindex.txt
new_certs_dir                           = $dir/certs
certificate                             = $dir/cacert.pem
private_key                             = $dir/private/cakey.pem
default_days                            = 365
default_md                              = md5
preserve                                = no
email_in_dn                             = no
nameopt                                 = default_ca
certopt                                 = default_ca
policy                                  = policy_match

[ policy_match ]
countryName                     = match
stateOrProvinceName             = match
organizationName                = match
organizationalUnitName          = optional
commonName                      = supplied
emailAddress                    = optional

[ req ]
default_bits                    = 2048                  # Size of keys
default_keyfile                 = key.pem               # name of generated keys
default_md                      = sha256                # message digest algorithm
string_mask                     = nombstr               # permitted characters
distinguished_name              = req_distinguished_name
req_extensions                  = v3_req

[ req_distinguished_name ]
# Variable name                         Prompt string
#-------------------------        ----------------------------------
0.organizationName              = Organization Name (company)
organizationalUnitName  = Organizational Unit Name (department, division)
emailAddress                    = Email Address
emailAddress_max                = 40
localityName                    = Locality Name (city, district)
stateOrProvinceName             = State or Province Name (full name)
countryName                     = Country Name (2 letter code)
countryName_min                 = 2
countryName_max                 = 2
commonName                      = Common Name (hostname, IP, or your name)
commonName_max                  = 64

# Default values for the above, for consistency and less typing.
# Variable name                  Value
#------------------------         ------------------------------
0.organizationName_default      = Your Corp
localityName_default            = Your Town
stateOrProvinceName_default     = Your State
countryName_default             = US

[ v3_ca ]
basicConstraints                 = CA:TRUE
subjectKeyIdentifier             = hash
authorityKeyIdentifier           = keyid:always,issuer:always

[ v3_req ]
basicConstraints                = CA:FALSE
subjectKeyIdentifier            = hash

Generate a new root CA certificate:

$ openssl req -new -x509 -out root-ca-crt.pem -newkey rsa:2048 \
-keyout private/root-ca-key.pem -days 365 -extensions v3_ca \
-config openssl.cnf

The first sub-authority is a little more work.

$ cd ../site-ca
$ openssl req -new -config ../root-ca/openssl.cnf -newkey rsa:2048 \
-keyout private/site-ca-key.pem -days 365 -extensions v3_ca \
-out site-ca-req.pem

Sign the new certificate with the root cert:

$ cd ../root-ca
$ openssl ca -config openssl.cnf -days 365 -extensions v3_ca \
-keyfile private/root-ca-key.pem \
-cert root-ca-crt.pem \
-out ../site-ca/site-ca-crt.pem -in ../site-ca/site-ca-req.pem

Repeat for the person CA:

$ cd ../person-ca
$ openssl req -new -config ../root-ca/openssl.cnf -newkey rsa:2048 \ 
   -keyout private/person-ca-key.pem -days 365 -extensions v3_ca \
   -out person-ca-req.pem

Sign the new certificate with the root cert:

$ cd ../root-ca
$ openssl ca -config openssl.cnf -days 365 -extensions v3_ca \
   -keyfile private/root-ca-key.pem \
   -cert root-ca-crt.pem \
   -out ../person-ca/person-ca-crt.pem -in ../person-ca/person-ca-req.pem

Generate a new certificate signing request for a person certificate:

$ cd ../person-ca
$ mkdir csr
$ openssl req -config ../root-ca/openssl.cnf -newkey rsa:2048 \
   -keyout private/kees.leune-key.pem -out csr/kees.leune-req.pem \
   -days 365 -nodes

This generates a signing request without passphrase protection. To be prompted for a phrase, omit the -nodes switch.

Sign the request with the person-ca key:

$ openssl ca -config ../root-ca/openssl.cnf -days 365 \ 
-keyfile private/person-ca-key.pem \
-cert person-ca-crt.pem \
-in csr/kees.leune-req.pem \
-out csr/kees.leune-crt.pem

Repeat for a site and generate a new certificate signing request for a person certificate:

$ cd ../site-ca
$ mkdir csr
$ openssl req -config ../root-ca/openssl.cnf -newkey rsa:2048 \    -keyout private/site-key.pem -out csr/site-req.pem \    -days 365 -nodes

This generates a signing request without passphrase protection. To be prompted for a phrase, omit the -nodes switch.

Sign the request with the site-ca key:

$ openssl ca -config ../root-ca/openssl.cnf -days 365 \     -keyfile private/site-ca-key.pem \    -cert site-ca-crt.pem \    -in csr/site-req.pem \    -out csr/site-crt.pem

Copy root-ca-crt.pem, site-ca-crt.pem, and person-ca-crt.pem to a directory and run c_rehash . in that directory. In an Apache mod_ssl configuration, point the SSLCACertificatePath to the directory with the certificates. Copy the files site-crt.pem and site-key.pem to a place where the web server can read them and point the SSLCertificateFile and SSLCertificateKeyFile to them. This will lead to an Apache Virtual Host definition like:

SSLEngine On
SSLCertificateFile /etc/apache2/ssl/airt-dev-vm.crt
SSLCertificateKeyFile /etc/apache2/ssl/airt-dev-vm.key
SSLCACertificatePath /etc/apache2/ssl
ServerAdmin webmaster@localhost
ServerName your-host-name
SSLVerifyDepth 3
SSLVerifyClient require
SSLOptions +ExportCertData

Make sure you install the same certificates that you installed in the Apache CA directory in your browser.

Lastly, generate a p12 certificate request. Make sure you are in the person CA directory:

$ openssl pkcs12 -export -in csr/kees.leune-crt.pem -inkey private/kees.leune-key.pem \    -certfile ../person-ca/person-ca-crt.pem \    -name "Kees Leune" \    -out csr/kees.leune-crt.p12