Install Certificate Authority (CA)

Lars Jönsson 2024-02-29

NOTE Setting of xmppAddr and dnsSRV shall probably be removed from intermed-ca.cnf as it seems like newer versions of OpenSSL has native support for XMPP address

These instructions are based on a guide available at https://roll.urown.net/ca/index.html

Table of Contents

Preparations

Throughout the document, the commands uses the environment variable DOMAIN to point the domain. Set up the variable. Replace example.net with your own domain.

DOMAIN=example.net

Create base of the CA at ~/CA

cd ~
mkdir CA

Create a safe storage for storage of root and intermediate CA

cd ~/CA
dd if=/dev/zero bs=1M count=100 of=secret-device.img
sudo cryptsetup luksFormat secret-device.img 
sudo cryptsetup open --type luks secret-device.img secret-device
sudo mkfs.ext4 /dev/mapper/secret-device
mkdir safe-storage
sudo mount /dev/mapper/secret-device safe-storage
sudo chown $USER:`id -gn` safe-storage

Create the Root CA

Directories and files

Create the directory structure in the safe storage and populate it with basic files.

cd ~/CA/safe-storage/
mkdir -p ${DOMAIN}.ca/root-ca/{certreqs,certs,crl,newcerts,private}
cd ${DOMAIN}.ca/root-ca/
chmod 700 private
touch root-ca.index
echo 00 > root-ca.crlnum

Set a random serial number for next openssl action.

openssl rand -hex 16 > root-ca.serial

Configuration file

A good starting point is found later in this document (root-ca.cnf) and available for download at:

wget https://roll.urown.net/_downloads/1daede3e61516d4ccdba1eaf26bdb86c/root-ca.cnf

Download the file and modify it according to the following diff:

81c81
< nameConstraints         = critical, @name_constraints
---
> #nameConstraints         = critical, @name_constraints

Replace example.net with your own domain, in the same file:

organizationName        = example.net
commonName              = example.net Root Certification Authority

URI                     = http://ca.example.net/
email                   = certmaster@example.net

caIssuers;URI           = http://ca.example.net/certs/example.net_Root_Certification_Authority.cert.pem
fullname                = URI:http://ca.example.net/crl/example.net_Root_Certification_Authority.crl

Activate the configuration

export OPENSSL_CONF=./root-ca.cnf

Generate key and CSR

Create the private key and generate a certificate signing request at the same time.

openssl req -new -out root-ca.req.pem

Protect the private key

chmod 400 private/root-ca.key.pem

Show the CSR

Optionally take peek at the CSR.

openssl req -verify -in root-ca.req.pem -noout -text -reqopt no_version,no_pubkey,no_sigdump -nameopt multiline

Self-Signing of the Root Certificate

Self-sign the request.

openssl rand -hex 16 > root-ca.serial

openssl ca -selfsign -in root-ca.req.pem -out root-ca.cert.pem \
-extensions root-ca_ext \
-startdate `date +%y%m%d000000Z -u -d -1day` \
-enddate `date +%y%m%d000000Z -u -d +10years+1day`

Optionally take peek at the certificate

openssl x509 -in ./root-ca.cert.pem -noout -text \
-certopt no_version,no_pubkey,no_sigdump -nameopt multiline

Verify that the certificate is valid

openssl verify -verbose -CAfile root-ca.cert.pem root-ca.cert.pem

Revocation List (CRL)

Create an empty certificate revication list.

openssl ca -gencrl -out crl/root-ca.crl

Install the Root Certificate

Save the root certificate for later installation and use.

mkdir -p ~/CA/${DOMAIN}.ca/certs/
cp root-ca.cert.pem ~/CA/${DOMAIN}.ca/certs/

Create the Intermediate CA

Directories and files

Create the directory structure in the safe storage and populate it with basic files.

cd ~/CA/safe-storage/
mkdir -p ${DOMAIN}.ca/intermed-ca/{certreqs,certs,crl,newcerts,private}
cd ${DOMAIN}.ca/intermed-ca/
chmod 700 private
touch intermed-ca.index
echo 00 > intermed-ca.crlnum

Set a random serial number for next openssl action.

openssl rand -hex 16 > intermed-ca.serial

Configuration file

A good starting point is found later in this document (intermed-ca.cnf) and available for download at:

wget https://roll.urown.net/_downloads/7514c4fc42236f15a393223f7d7c98d9/intermed-ca.cnf

Download the file and modify it according to the following diff:

9a10
> ALTNAME                 = 
73c74
< default_keyfile         = private/intermed-ca.key
---
> default_keyfile         = private/intermed-ca.key.pem
107    a109,121
> # Server Certificate Extensions with Subject Alternative Name
> [ server_ext_san ]
> basicConstraints        = CA:FALSE
> keyUsage                = critical, digitalSignature, keyEncipherment
> extendedKeyUsage        = critical, serverAuth, clientAuth
> subjectKeyIdentifier    = hash
> authorityKeyIdentifier  = keyid:always
> issuerAltName           = issuer:copy
> authorityInfoAccess     = @auth_info_access
> crlDistributionPoints   = crl_dist
> subjectAltName          = $ENV::ALTNAME
> 
> #

Replace example.net with your own domain, in the same file:

organizationName        = example.net
commonName              = example.net Intermediate Certification Authority

URI                     = http://ca.example.net/
email                   = certmaster@example.net

caIssuers;URI           = http://ca.example.net/certs/example.net_Intermediate_Certification_Authority.cert.pem

fullname                = URI:http://ca.example.net/crl/example.net_Intermediate_Certification_Authority.crl

Activate the configuration

export OPENSSL_CONF=./intermed-ca.cnf

Generate key and CSR

Create the private key and generate a certificate signing request at the same time.

openssl req -new -out intermed-ca.req.pem

Protect the private key

chmod 400 private/intermed-ca.key.pem

Show the CSR

Optionally take peek at the CSR.

openssl req -verify -in intermed-ca.req.pem -noout -text -reqopt no_version,no_pubkey,no_sigdump -nameopt multiline

Sign the Intermediate CA with the Root CA

Copy the CSR to the Root CA

cp intermed-ca.req.pem ../root-ca/certreqs/

Go to the Root CA and invoke its configuration.

cd ~/CA/safe-storage/${DOMAIN}.ca/root-ca/
export OPENSSL_CONF=./root-ca.cnf

Sign the intermediate CSR with the root key with the intermediate extensions

openssl rand -hex 16 > root-ca.serial

openssl ca -in certreqs/intermed-ca.req.pem -out certs/intermed-ca.cert.pem \
-extensions intermed-ca_ext \
-startdate `date +%y%m%d000000Z -u -d -1day` \
-enddate `date +%y%m%d000000Z -u -d +5years+1day`

Optionally take peek at the certificate.

openssl x509 -in certs/intermed-ca.cert.pem -noout -text \
-certopt no_version,no_pubkey,no_sigdump -nameopt multiline

Verify the certificate

openssl verify -verbose -CAfile root-ca.cert.pem certs/intermed-ca.cert.pem

Copy the certificate to the Intermediate CA.

cp certs/intermed-ca.cert.pem ../intermed-ca/

Revocation List

Got to Intermediate CA and invoke its configuration.

cd ~/CA/safe-storage/${DOMAIN}.ca/intermed-ca/
export OPENSSL_CONF=./intermed-ca.cnf

Create an empty certificate revication list.

openssl ca -gencrl -out crl/intermed-ca.crl

Install the Intermediate Certificate

Save the intermediate certificate for later installation and use.

mkdir -p ~/CA/${DOMAIN}.ca/certs/
cp intermed-ca.cert.pem ~/CA/${DOMAIN}.ca/certs/

Install and use the Intermediate CA

Copy the Intermediate CA files to the working environment.

cp -R ~/CA/safe-storage/${DOMAIN}.ca/intermed-ca/ ~/CA/${DOMAIN}.ca/

Lock up safe storage

A copy of Intemediate CA is available in the working environment and and the Root and Intermediate CAs in the safe storage can be locked up (and stored in a safe place).

cd ~/CA
sudo umount safe-storage
sudo cryptsetup close secret-device

Usage of the Intermediate CA

Ensure that the environment variable DOMAIN is set (see Preparations).

The Intermediate CA working area is available at:

~/CA/${DOMAIN}.ca/intermed-ca

CSRs to be signed should be stored in:

~/CA/${DOMAIN}.ca/intermed-ca/certreqs

Signed certificates are stored in:

~/CA/${DOMAIN}.ca/intermed-ca/certreqs

Steps to sign a CSR:

  1. Copy the CSR to the CA

    cp <csr-file> ~/CA/${DOMAIN}.ca/intermed-ca/certreqs
  2. Go the CA

    cd ~/CA/${DOMAIN}.ca/intermed-ca
  1. Invoke the configuration

    export OPENSSL_CONF=./intermed-ca.cnf
  2. Sign the certificate

    • When SAN (Subject Alternative Name) is set in the CSR

      openssl rand -hex 16 > intermed-ca.serial
      
      openssl ca -notext \
      -in certreqs/<function>.csr \
      -out certs/<function>.crt \
      -extensions server_ext
    • When SAN is missing in the CSR

      openssl rand -hex 16 > intermed-ca.serial
      
      ALTNAME=DNS:<hostname-1>,DNS:<hostname-2>,IP:<ip-adress> \
      openssl ca -notext \
      -in certreqs/<function>.csr \
      -out certs/<function>.crt \
      -extensions server_ext_san

      The Common Name (CN) of the CSR is usually used as the SAN and can be retrieved with the following command

      openssl req -noout -subject -in certreqs/<function>.csr | \
      sed -n '/^subject/s/^.*CN\s*=\s*//p'
  3. Copy the certificate to the user

    cp ~/CA/${DOMAIN}.ca/intermed-ca/certs/<cert-file> <user-location>

Configuration files

root-ca.cnf

#
# OpenSSL configuration for the Root Certification Authority.
#

#
# This definition doesn't work if HOME isn't defined.
CA_HOME                 = .
RANDFILE                = $ENV::CA_HOME/private/.rnd

#
# Default Certification Authority
[ ca ]
default_ca              = root_ca

#
# Root Certification Authority
[ root_ca ]
dir                     = $ENV::CA_HOME
certs                   = $dir/certs
serial                  = $dir/root-ca.serial
database                = $dir/root-ca.index
new_certs_dir           = $dir/newcerts
certificate             = $dir/root-ca.cert.pem
private_key             = $dir/private/root-ca.key.pem
default_days            = 1826 # Five years
crl                     = $dir/root-ca.crl
crl_dir                 = $dir/crl
crlnumber               = $dir/root-ca.crlnum
name_opt                = multiline, align
cert_opt                = no_pubkey
copy_extensions         = copy
crl_extensions          = crl_ext
default_crl_days        = 180
default_md              = sha256
preserve                = no
email_in_dn             = no
policy                  = policy
unique_subject          = no

#
# Distinguished Name Policy for CAs
[ policy ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = supplied
organizationalUnitName  = optional
commonName              = supplied

#
# Root CA Request Options
[ req ]
default_bits            = 4096
default_keyfile         = private/root-ca.key.pem
encrypt_key             = yes
default_md              = sha256
string_mask             = utf8only
utf8                    = yes
prompt                  = no
req_extensions          = root-ca_req_ext
distinguished_name      = distinguished_name
subjectAltName          = @subject_alt_name

#
# Root CA Request Extensions
[ root-ca_req_ext ]
subjectKeyIdentifier    = hash
subjectAltName          = @subject_alt_name

#
# Distinguished Name (DN)
[ distinguished_name ]
organizationName        = example.net
commonName              = example.net Root Certification Authority

#
# Root CA Certificate Extensions
[ root-ca_ext ]
basicConstraints        = critical, CA:true
keyUsage                = critical, keyCertSign, cRLSign
nameConstraints         = critical, @name_constraints
subjectKeyIdentifier    = hash
subjectAltName          = @subject_alt_name
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy
authorityInfoAccess     = @auth_info_access
crlDistributionPoints   = crl_dist

#
# Intermediate CA Certificate Extensions
[ intermed-ca_ext ]
basicConstraints        = critical, CA:true, pathlen:0
keyUsage                = critical, keyCertSign, cRLSign
subjectKeyIdentifier    = hash
subjectAltName          = @subject_alt_name
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy
authorityInfoAccess     = @auth_info_access
crlDistributionPoints   = crl_dist

#
# CRL Certificate Extensions
[ crl_ext ]
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy

#
# Certificate Authorities Alternative Names
[ subject_alt_name ]
URI                     = http://ca.example.net/
email                   = certmaster@example.net

#
# Name Constraints
[ name_constraints ]
permitted;DNS.1         = example.net
permitted;DNS.2         = example.org
permitted;DNS.3         = lan
permitted;DNS.4         = onion
permitted;email.1       = example.net
permitted;email.2       = example.org

#
# Certificate download addresses for the root CA
[ auth_info_access ]
caIssuers;URI           = http://ca.example.net/certs/example.net_Root_Certification_Authority.cert.pem

#
# CRL Download address for the root CA
[ crl_dist ]
fullname                = URI:http://ca.example.net/crl/example.net_Root_Certification_Authority.crl

# EOF

intermed-ca.cnf

#
# OpenSSL configuration for the Intermediate Certification Authority.
#

#
# This definition doesn't work if HOME isn't defined.
CA_HOME                 = .
RANDFILE                = $ENV::CA_HOME/private/.rnd
oid_section             = new_oids

#
# XMPP address Support
[ new_oids ]
xmppAddr          = 1.3.6.1.5.5.7.8.5
dnsSRV            = 1.3.6.1.5.5.7.8.7

#
# Default Certification Authority
[ ca ]
default_ca              = intermed_ca

#
# Intermediate Certification Authority
[ intermed_ca ]
dir                     = $ENV::CA_HOME
certs                   = $dir/certs
serial                  = $dir/intermed-ca.serial
database                = $dir/intermed-ca.index
new_certs_dir           = $dir/newcerts
certificate             = $dir/intermed-ca.cert.pem
private_key             = $dir/private/intermed-ca.key.pem
default_days            = 730 # Two years
crl                     = $dir/crl/intermed-ca.crl
crl_dir                 = $dir/crl
crlnumber               = $dir/intermed-ca.crlnum
name_opt                = multiline, align
cert_opt                = no_pubkey
copy_extensions         = copy
crl_extensions          = crl_ext
default_crl_days        = 30
default_md              = sha256
preserve                = no
email_in_dn             = no
policy                  = policy
unique_subject          = no

#
# Distinguished Name Policy
[ policy ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied

#
# Distinguished Name Policy for Personal Certificates
[ user_policy ]
countryName             = supplied
stateOrProvinceName     = optional
localityName            = supplied
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = supplied
#xmppAddr               = optional # Added to SubjAltName by req

#
# Intermediate CA request options
[ req ]
default_bits            = 3072
default_keyfile         = private/intermed-ca.key
encrypt_key             = yes
default_md              = sha256
string_mask             = utf8only
utf8                    = yes
prompt                  = no
req_extensions          = req_ext
distinguished_name      = distinguished_name
subjectAltName          = subject_alt_name

#
# Intermediate CA Request Extensions
[ req_ext ]
subjectKeyIdentifier    = hash
subjectAltName          = @subject_alt_name

#
# Distinguished Name (DN)
[ distinguished_name ]
organizationName        = example.net
commonName              = example.net Intermediate Certification Authority

#
# Server Certificate Extensions
[ server_ext ]
basicConstraints        = CA:FALSE
keyUsage                = critical, digitalSignature, keyEncipherment
extendedKeyUsage        = critical, serverAuth, clientAuth
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy
authorityInfoAccess     = @auth_info_access
crlDistributionPoints   = crl_dist

#
# Client Certificate Extensions
[ client_ext ]
basicConstraints        = CA:FALSE
keyUsage                = critical, digitalSignature
extendedKeyUsage        = critical, clientAuth
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy
authorityInfoAccess     = @auth_info_access
crlDistributionPoints   = crl_dist

#
# User Certificate Extensions
[ user_ext ]
basicConstraints        = CA:FALSE
keyUsage                = critical, digitalSignature
extendedKeyUsage        = critical, clientAuth, emailProtection
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy
authorityInfoAccess     = @auth_info_access
crlDistributionPoints   = crl_dist

#
# CRL Certificate Extensions
[ crl_ext ]
authorityKeyIdentifier  = keyid:always
issuerAltName           = issuer:copy

#
# Certificate Authorities Alternative Names
[ subject_alt_name ]
URI                     = http://ca.example.net/
email                   = certmaster@example.net

#
# Certificate download addresses for the intermediate CA
[ auth_info_access ]
caIssuers;URI           = http://ca.example.net/certs/example.net_Intermediate_Certification_Authority.cert.pem

#
# CRL Download address for the intermediate CA
[ crl_dist ]
fullname                = URI:http://ca.example.net/crl/example.net_Intermediate_Certification_Authority.crl

# EOF