User Tools

Site Tools


crypto:x509

This is an old revision of the document!


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

crypto/x509.1589932666.txt.gz · Last modified: 2020-05-19 19:57 by gabriel