Install Certificate Authority (CA)
Lars Jönsson 2024-06-16
These instructions are based on a guide available at https://roll.urown.net/ca/index.html
Table of Contents
- Preparations
- Create the Root CA
- Create the Intermediate CA
- Lock up safe storage
- Usage of the Issuer CA
- Configuration files
Preparations
Throughout the document, the commands uses the environment variable
DOMAIN
to point the domain, ROOT_VER
to the version of the Root CA
and IM_VER
to the version of the Intermediate CA.
Set up the variables. Replace example.net
with your own domain, and
set ROOT_VER
and IM_VER
to the version of the corresponding CA.
DOMAIN=example.net
ROOT_VER=v1
IM_VER=v1.1
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
Ensure that applicable steps in Preparations are done.
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-${ROOT_VER}/{certreqs,certs,crl,newcerts,private}
cd ${DOMAIN}.ca/root-ca-${ROOT_VER}/
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
Either download the file and modify it according to the following diff or better download the already modfied file root-ca.cnf:
74c74
< commonName = example.net Root Certification Authority
---
> commonName = example.net Root Certification Authority v1
81c81
< nameConstraints = critical, @name_constraints
---
> #nameConstraints = critical, @name_constraints
124c124
< caIssuers;URI = http://ca.example.net/certs/example.net_Root_Certification_Authority.cert.pem
---
> caIssuers;URI = http://ca.example.net/certs/example.net_Root_Certification_Authority_v1.cert.pem
129c129
< fullname = URI:http://ca.example.net/crl/example.net_Root_Certification_Authority.crl
---
> fullname = URI:http://ca.example.net/crl/example.net_Root_Certification_Authority_v1.crl
Replace example.net
with your own domain and v1
to the wanted
version, in the same file.
sed -i s/example.net/${DOMAIN}/g root-ca.cnf
sed -i s/v1/${ROOT_VER}/g root-ca.cnf
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, and point it out as current.
mkdir -p ~/CA/${DOMAIN}.ca/certs/
rsync -a root-ca.cert.pem ~/CA/${DOMAIN}.ca/certs/root-ca-${ROOT_VER}.cert.pem
rm -f ~/CA/${DOMAIN}.ca/certs/root-ca.cert.pem
ln -rs ~/CA/${DOMAIN}.ca/certs/root-ca-${ROOT_VER}.cert.pem ~/CA/${DOMAIN}.ca/certs/root-ca.cert.pem
Create the Intermediate CA
Ensure that applicable steps in Preparations are done.
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-${IM_VER}/{certreqs,certs,crl,newcerts,private}
cd ${DOMAIN}.ca/intermed-ca-${IM_VER}/
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
Either download the file and modify it according to the following diff or better download the already modfied file intermed-ca.cnf:
NOTE Setting of
xmppAddr
anddnsSRV
shall probably be removed fromintermed-ca.cnf
as it seems like newer versions of OpenSSL has native support for XMPP address. They are currently just "commented out".
9a10
> ALTNAME =
14,15c15,16
< xmppAddr = 1.3.6.1.5.5.7.8.5
< dnsSRV = 1.3.6.1.5.5.7.8.7
---
> #xmppAddr = 1.3.6.1.5.5.7.8.5
> #dnsSRV = 1.3.6.1.5.5.7.8.7
32c33
< default_days = 396 # 1 year + 31 days
---
> default_days = 730 # Two years
73c74
< default_keyfile = private/intermed-ca.key
---
> default_keyfile = private/intermed-ca.key.pem
93c94
< commonName = example.net Intermediate Certification Authority
---
> commonName = example.net Intermediate Certification Authority v1.1
106a108,119
> # Server Certificate Extensions with Subject Alternative Name
> [ server_ext_san ]
> 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
>
> #
143c156
< caIssuers;URI = http://ca.example.net/certs/example.net_Intermediate_Certification_Authority.cert.pem
---
> caIssuers;URI = http://ca.example.net/certs/example.net_Intermediate_Certification_Authority_v1.1.cert.pem
148c161
< fullname = URI:http://ca.example.net/crl/example.net_Intermediate_Certification_Authority.crl
---
> fullname = URI:http://ca.example.net/crl/example.net_Intermediate_Certification_Authority_v1.1.crl
Replace example.net
with your own domain and v1.1
to the wanted
version, in the same file.
sed -i s/example.net/${DOMAIN}/g intermed-ca.cnf
sed -i s/v1.1/${IM_VER}/g intermed-ca.cnf
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-${ROOT_VER}/certreqs/intermed-ca-${IM_VER}.req.pem
Go to the Root CA and invoke its configuration.
cd ~/CA/safe-storage/${DOMAIN}.ca/root-ca-${ROOT_VER}/
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-${IM_VER}.req.pem -out certs/intermed-ca-${IM_VER}.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-${IM_VER}.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-${IM_VER}.cert.pem
Copy the certificate to the Intermediate CA.
cp certs/intermed-ca-${IM_VER}.cert.pem ../intermed-ca-${IM_VER}/intermed-ca.cert.pem
Revocation List
Got to Intermediate CA and invoke its configuration.
cd ~/CA/safe-storage/${DOMAIN}.ca/intermed-ca-${IM_VER}/
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, and point it out as current.
mkdir -p ~/CA/${DOMAIN}.ca/certs/
rsync -a intermed-ca.cert.pem ~/CA/${DOMAIN}.ca/certs/intermed-ca-${IM_VER}.cert.pem
rm -f ~/CA/${DOMAIN}.ca/certs/intermed-ca.cert.pem
ln -rs ~/CA/${DOMAIN}.ca/certs/intermed-ca-${IM_VER}.cert.pem ~/CA/${DOMAIN}.ca/certs/intermed-ca.cert.pem
Install the Intermediate CA as Issuer CA
Copy the Intermediate CA files to the working environment and point it out as Issuer CA.
rsync -a ~/CA/safe-storage/${DOMAIN}.ca/intermed-ca-${IM_VER} ~/CA/${DOMAIN}.ca/
rm -f ~/CA/${DOMAIN}.ca/issuer-ca
ln -rs ~/CA/${DOMAIN}.ca/intermed-ca-${IM_VER} ~/CA/${DOMAIN}.ca/issuer-ca
Sync the previous Issuer CA with its Intermediate CA
The previous Issuer CA will not be used for issuing certificates anymore and now it is a good time to ensure that the content is synced with its Intermediate CA in the secure storage. This can also be done, now and then, when it is active.
Set the environment VER
to the version to sync.
VER=v1.1
rsync -a ~/CA/${DOMAIN}.ca/intermed-ca-${VER} ~/CA/safe-storage/${DOMAIN}.ca/intermed-ca-${VER}
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 Issuer CA
Ensure that the environment variable DOMAIN
is set (see
Preparations).
The Issuer CA working area is available at:
~/CA/${DOMAIN}.ca/issuer-ca
CSRs to be signed should be stored in:
~/CA/${DOMAIN}.ca/issuer-ca/certreqs
Signed certificates are stored in:
~/CA/${DOMAIN}.ca/issuer-ca/certreqs
Steps to sign a CSR:
Copy the CSR to the CA
cp <csr-file> ~/CA/${DOMAIN}.ca/issuer-ca/certreqs
Go the CA
cd ~/CA/${DOMAIN}.ca/issuer-ca
Invoke the configuration
export OPENSSL_CONF=./intermed-ca.cnf
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'
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 # 5 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 = sha384
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 = sha384
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
#
# 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 = 396 # 1 year + 31 days
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 = sha384
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 = sha384
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 ]
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 ]
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 ]
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