How To: Create An Offline Root CA

How To: Create An Offline Root CA

Cameron Banowsky

Many people wake up one morning and realize they desperately need their very own offline root certificate authority.  I imagine that is the case.  This guide was created in conjunction with someone I respect greatly and whose preference is to remain anonymous.  I cannot take credit for the entirety of this guide – total transparency.

The cleanest way to do this is on a machine you can in a safe for eternity after offloading the certificate generated to portable media that can be physically secured and accessed only for disaster recovery purposes.


  1. A computer running Red Hat Enterprise Linux 8 and with OpenSSL compiled with FIPS_MODE enabled.
  2. A secure physical location to store this computer when finished.  NOTE: This machine's sole purpose in life is to create your root certificate authority, do not expect or attempt to use this machine for any other purpose.  

Create The CA

In a shell, begin creating the files and directories you will need to place your keys and certs.

mkdir .rootca

cd .rootca/

mkdir certs crl csr private newcerts

chmod 700 private

touch index.txt

echo 1000 > serial

touch config

vi config

The config file can be modified but should at a minimum contain something like this:

# OpenSSL Root Certificate Authority Configuration File.
[ openssl_conf_section ]
# Configuration module list
alg_section = evp_sect
[ evp_sect ]
# Set to “yes” to enter FIPS mode if supported
fips_mode = yes
[ ca ]
default_ca = CA_default
[ CA_default ]
# Directory and file locations.
dir               = /home/<your user>/.rootca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand
# The root key and root certificate.
private_key = $dir/private/root.enc.key           
certificate = $dir/certs/root.crt
# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/root.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 90
# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha512
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 3650
preserve          = no
policy            = policy_loose
[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = optional
organizationName        = match
organizationalUnitName  = match
commonName              = supplied
emailAddress            = supplied
[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional
[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only
# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha512
# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca
[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address
# Optionally, specify some defaults.
countryName_default             = US
stateOrProvinceName_default     = California
localityName_default            = Los Angeles
0.organizationName_default      = SHE BASH
organizationalUnitName_default  = Engineering
emailAddress_default            =
[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
#authorityInfoAccess = caIssuers;URI:
#crlDistributionPoints = URI:
[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:2
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
#authorityInfoAccess = caIssuers;URI:
crlDistributionPoints = URI:
[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:TRUE
#nsCertType = client, email
#nsComment = “OpenSSL Generated Client Certificate”
#subjectKeyIdentifier = hash
#authorityKeyIdentifier = keyid,issuer
#keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
#extendedKeyUsage = clientAuth, emailProtection
[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:TRUE
#nsCertType = server
#nsComment = “OpenSSL Generated Server Certificate”
#subjectKeyIdentifier = hash
#authorityKeyIdentifier = keyid,issuer:always
#keyUsage = critical, digitalSignature, keyEncipherment
#extendedKeyUsage = serverAuth
[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:TRUE
#subjectKeyIdentifier = hash
#authorityKeyIdentifier = keyid,issuer
#keyUsage = critical, digitalSignature
#extendedKeyUsage = critical, OCSPSigning

I highly recommend reading the manual page for a comprehensive explanation of all these parameters.

Generate Root CA Private Key

openssl genrsa 4096 > private/root.key

Encrypt The Private Key

openssl pkcs8 -in private/root.key -topk8 -out private/root.enc.key -v1 PBE-SHA1-3DES
# Always store the key encrypted and don't forget your password
rm private/root.key

Self Sign Private Key / Generate Public Cert for RootCA

openssl req -config config -key private/root.enc.key -new -x509 -days 3650 -sha512 -extensions v3_ca -out certs/root.crt

# accept all but the following default vaules (defaults are assigned in the config file)
# The CN can be modified to your needs

CN = 

Sign the CSR For Your Intermediate Certificate Authority

Given that the root will be offline, this will be a useless exercise or at least highly impractical if you do not stand up an intermediate certificate authority to handle the issuance of certificates to the various clients that need them.  You will have to generate a private key and certificate signing request (CSR).

You will want to store this private key in a secret store or someplace safe; it is a private key.

Procedurally, however, run the following on the machine you will run the intermediate certificate CA:

# you'll need to find a means to bring the server.csr contents onto the root CA machine.
openssl req -new -newkey rsa:4096 -nodes -keyout server.key -out server.csr
cat server.csr

Take the entire contents of your CSR and on the Root CA machine (in the same directory context) run:

# paste the contents of your csr into a file within the csr directory
vi csr/int_ca.csr

openssl ca -config config -extensions v3_intermediate_ca -days 3650 -notext -md sha512 -in csr/int_ca.csr -out certs/int_ca.crt

Verify The Chain of Trust

openssl verify -CAfile certs/root.crt certs/int_ca.crt

Assuming the output is, then your int_ca.crt along with the public certificate of your Root CA can be bundled and serve as the intermediate CA fully trusted internally.  While the process is 100% known to you as trusted, this will not grant you public trust.  CA Browser Forum gives public trust. However, for internal PKI, an offline Root CA is the most secure way to handle PKI for an organization to mitigate the compromise of the root CA.