This is an old revision of the document!
Table of Contents
Creating an intermediate and root certificate authority with OpenSSL
Generally speaking, [https://letsencrypt.org/|Let's Encrypt] is a better solution that a self-hosted Certificate Authority in 2020. It's stable, easy to configure and trusted in all major browsers. However its primary drawback is that it can be very awkward to use with domains that are not on the public Internet. Therefore I run a small certificate authority to sign x509 certificates for use internally as HSTS is implemented for the quay.net domain. This upshot of this configuration on the public domain means that in order to access HTTP resources on my internal subdomain I require trusted TLS certificates.
This page is a brief overview of how to configure a self-signed CA that implements an intermediate CA in order to allow us to take the root CA offline.
mkca.sh helper script
Stick this somewhere to help set up the directory structures for the two CAs you are about to create.
#!/bin/bash workdir=${1} mkdir -p ${workdir}/{certs,crl,csr,newcerts,private} chmod 700 ${workdir}/private touch ${workdir}/certindex echo 1000 > ${workdir}/serialfile
Root CA configuration
We'll set some environment variables for the root CA in order to simplify changing the name.
export CA=quayrootCA export workdir=${CA} export OPENSSL_CONF=${workdir}/${CA}.conf export privkey=${workdir}/private/privkey.pem export cacert=${workdir}/certs/${CA}.crt mkca.sh $CA
Root CA OpenSSL configuration file
Place this file in $OPENSSL_CONF
location you set above, this will govern OpenSSL usage for the root CA and can be customized as you see fit. The settings here are relatively sane.
RANDFILE = /dev/arandom [ ca ] default_ca = quayrootCA [ crl_ext ] # issuerAltName=issuer:copy #this would copy the issuer name to altname issuerAltName = URI:https://quay.net/pub/ca/quayrootCA.crt authorityKeyIdentifier = keyid:always,issuer [ quayrootCA ] new_certs_dir = /home/ca/quayrootCA/certs unique_subject = no certificate = /home/ca/quayrootCA/certs/quayrootCA.crt database = /home/ca/quayrootCA/certindex private_key = /home/ca/quayrootCA/private/privkey.pem serial = /home/ca/quayrootCA/serialfile default_days = 1096 default_md = sha256 policy = quayrootCA_policy x509_extensions = quayrootCA_extensions [ quayrootCA_policy ] commonName = supplied stateOrProvinceName = supplied countryName = supplied emailAddress = optional organizationName = supplied organizationalUnitName = optional [ quayrootCA_extensions ] basicConstraints = CA:false subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = digitalSignature,keyEncipherment extendedKeyUsage = serverAuth crlDistributionPoints = URI:https://quay.net/pub/ca/quayrootCA.crl [ v3_ca ] subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer basicConstraints = CA:true keyUsage = cRLSign, keyCertSign [ req ] default_bits = 2048 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CA countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Ontario localityName = Locality Name (eg, city) localityName_default = Toronto 0.organizationName = Organization Name (eg, company) 0.organizationName_default = The Quay organizationalUnitName = Organizational Unit Name (eg, section) commonName = Common Name (eg, fully qualified host name) commonName_max = 64 emailAddress = Email Address emailAddress_default = gabriel@quay.net emailAddress_max = 64 [ req_attributes ] #challengePassword = A challenge password #challengePassword_min = 0 #challengePassword_max = 20
Generate root CA
# generate a key for the CA (you can strip the password by omitting -aes256) openssl genrsa -aes256 -out $privkey 4096 chmod 600 $privkey # generate the CA root certificate (you can tweak the keysize and validity duration as you see fit) openssl req -newkey rsa:4096 -x509 \ -days 3650 \ -key $privkey \ -sha256 \ -extensions v3_ca \ -out $cacert
Intermediate CA configuration
We're going to craete an intermediate CA (this allows us to keep the root CA offline, just in case).
Set the environment for the Intermediate CA.
export CA=quayintCA export workdir=~/${CA} export OPENSSL_CONF=${workdir}/${CA}.conf export privkey=${workdir}/private/privkey.pem export cacert=${workdir}/certs/${CA}.crt
Intermediate CA OpenSSL configuration file
Place this file in $OPENSSL_CONF
location you set in the previous section, this will govern OpenSSL usage for the intermediate CA and can be customized as you see fit. The settings here are relatively sane. These will be the settings you use normally to manage server certificates.
RANDFILE = /dev/arandom [ ca ] default_ca = quayintCA [ crl_ext ] # issuerAltName=issuer:copy #this would copy the issuer name to altname issuerAltName = URI:https://quay.net/pub/ca/quayintCA.crt authorityKeyIdentifier = keyid:always,issuer [ quayintCA ] new_certs_dir = /home/ca/quayintCA/certs unique_subject = no certificate = /home/ca/quayintCA/certs/quayintCA.crt database = /home/ca/quayintCA/certindex private_key = /home/ca/quayintCA/private/privkey.pem serial = /home/ca/quayintCA/serialfile default_days = 1096 default_md = sha256 policy = quayintCA_policy x509_extensions = quayintCA_extensions [ quayintCA_policy ] commonName = supplied stateOrProvinceName = supplied countryName = supplied emailAddress = optional organizationName = supplied organizationalUnitName = optional [ quayintCA_extensions ] basicConstraints = CA:false subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = digitalSignature,keyEncipherment extendedKeyUsage = serverAuth crlDistributionPoints = URI:https://quay.net/pub/ca/quayintCA.crl [ v3_ca ] subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer basicConstraints = CA:true keyUsage = cRLSign, keyCertSign [ req ] default_bits = 2048 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CA countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Ontario localityName = Locality Name (eg, city) localityName_default = Toronto 0.organizationName = Organization Name (eg, company) 0.organizationName_default = The Quay organizationalUnitName = Organizational Unit Name (eg, section) commonName = Common Name (eg, fully qualified host name) commonName_max = 64 emailAddress = Email Address emailAddress_default = gabriel@quay.net emailAddress_max = 64 [ req_attributes ] #challengePassword = A challenge password #challengePassword_min = 0 #challengePassword_max = 20
Generate a private key for the Intermediate CA
openssl genrsa -aes256 -out $privkey 4096 chmod 600 $privkey
Generate the intermediate CA certificate signing request
openssl req -config $OPENSSL_CONF -sha256 -new -key $privkey -out $workdir/csr/$CA.csr
Sign the intermediate CA
We need to switch our environment back to use the root CA.
export CA=quayrootCA export workdir=${CA} export OPENSSL_CONF=${workdir}/${CA}.conf export privkey=${workdir}/private/privkey.pem export cacert=${workdir}/certs/${CA}.crt openssl ca -keyfile $privkey \ -cert $cacert \ -extensions v3_ca \ -notext -md sha256 \ -in ${workdir}/csr/quayintCA.csr \ -out ~/quayintCA/certs/quayintCA.crt \ -days 4018
Create the intermediate CA certificate chain
We'll need a certificate chain to use with web browsers.
cat ~/quayintCA/certs/quayintCA.crt \ ~/quayrootCA/certs/quayrootCA.crt > \ ~/quayintCA/certs/quaycerts.crt
Creating server certificates
export CA=quayintCA export workdir=${CA} export OPENSSL_CONF=${workdir}/${CA}.conf export privkey=${workdir}/private/privkey.pem export cacert=${workdir}/certs/${CA}.crt # create a certificate request and private key for my router export certname=router.in.quay.net openssl req -newkey rsa:2048 -nodes -out ${certname}.csr -keyout ${certname}.key # now sign the certificate request openssl ca -keyfile $privkey \ -cert $cacert \ -notext -md sha256 \ -in ${certname}.csr -out ${certname}.crt
OpenSSL tips
Decode an x509 certificate file to plain text:
openssl x509 -in certifcate_file.crt -text
Verify a certificate is signed by a CA:
# verify intermediate certificate openssl verify -CAfile ~/quayrootCA/quayrootCA.crt ~/quayintCA/quayintCA.crt # verify server cert using cert chain openssl verify -CAfile ~/quayintCA/quayCAcertchain.crt router.in.quay.net.crt
Notes
I publish the CA root certificate here in the off chance that somebody outside my network might require it: https://quay.net/pub/ca/quayrootCA.crt
Here is the CRL: https://quay.net/pub/ca/quayrootCA.crl