Summary
Summary of the commands used to create a root CA, an intermediate CA, and a leaf certificate:
openssl genrsa -out root.key 2048
openssl req -new -key root.key -out root.csr -config root_req.config
openssl ca -in root.csr -out root.pem -config root.config -selfsign -extfile ca.ext -days 1095
openssl genrsa -out intermediate.key 2048
openssl req -new -key intermediate.key -out intermediate.csr -config intermediate_req.config
openssl ca -in intermediate.csr -out intermediate.pem -config root.config -extfile ca.ext -days 730
openssl genrsa -out leaf.key 2048
openssl req -new -key leaf.key -out leaf.csr -config leaf_req.config
openssl ca -in leaf.csr -out leaf.pem -config intermediate.config -days 365
openssl verify -x509_strict -CAfile root.pem -untrusted intermediate.pem leaf.pem
These commands rely on some setup which I will describe below. They are a bit of an overkill if you just want a few certs in a chain, which can be done with just the x509 command. These commands will also track your certs in a text database and auto-increment a serial number. I would recommend reading the warnings and bugs section of the openssl ca
man page before or after reading this answer.
Directory Structure
We will need the following directory structure before starting.
ca.ext # the extensions required for a CA certificate for signing certs
intermediate.config # configuration for the intermediate CA
root.config # configuration for the root CA
leaf_req.config # configuration for the leaf cert's csr
intermediate_req.config # configuration for the intermediate CA's csr
root_req.config # configuration for the root CA's csr
intermediate_ca/ # state files specific to the intermediate CA
index # a text database of issued certificates
serial # an auto-incrementing serial number for issued certificates
root_ca/ # state files specific to the root CA
index # a text database of issued certificates
serial # an auto-incrementing serial number for issued certificates
If this is a more permanent CA, the following changes are probably a good idea:
- Moving each CA's configuration file, private key (generated later), and certificate file (generated later) to the CA's directory. This will require changes to the configuration file.
- Creating a subdirectory in the CA's directory for issued certificates. This requires changes to the configuration file
- Encrypting the private key
- Setting a default number of days for issued certificates in the CA configuration files
Starting Directory Structure File Contents
The contents of each of the files in the directory structure are as follows:
ca.ext
[ default ]
basicConstraints = critical,CA:true # recommended to be marked critical. required for a ca
keyUsage = critical,keyCertSign # required to be marked critical. required for signing certs
intermediate.config
[ ca ]
default_ca = CA_default
[ CA_default]
dir = ./intermediate_ca # helper variable pointing to ca specific files
database = $dir/index # database of certs generated by the ca
new_certs_dir = ./ # one dir up to make the demo easier
certificate = ./intermediate.pem # one dir up to make the demo easier
serial = $dir/serial # file with incrementing hex serial number for certs
private_key = ./intermediate.key
policy = policy_any
email_in_dn = no # recommended
unique_subject = no # recommended for easier certificate rollover
copy_extensions = none # don't honor the extensions in the csr
default_md = sha256
[ policy_any ]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
root.config
[ ca ]
default_ca = CA_default
[ CA_default]
dir = ./root_ca # helper variable pointing to ca specific files
database = $dir/index # database of certs generated by the ca
new_certs_dir = ./ # one dir up to make the demo easier
certificate = ./root.pem # one dir up to make the demo easier
serial = $dir/serial # file with incrementing hex serial number for certs
private_key = ./root.key
policy = policy_any
email_in_dn = no # recommended
unique_subject = no # recommended for easier certificate rollover
copy_extensions = none # don't honor the extensions in the csr
default_md = sha256
[ policy_any ]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
leaf_req.config
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
countryName = US
commonName = Test Leaf
intermediate_req.config
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
countryName = US
commonName = Test Intermediate CA
root_req.config
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
countryName = US
commonName = Test Root CA
intermediate_ca/index (empty file). Database of issued certs. Updates automatically
[empty]
intermediate_ca/serial (a single 0 does not work). This file auto-increments
00
root_ca/index (empty file). Database of issued certs. Updates automatically
[empty]
root_ca/serial (a single 0 does not work). This file auto-increments
00
Detailed commands
Now we can run the commands from the start of this answer:
# create the private key for the root CA
openssl genrsa
-out root.key # output file
2048 # bitcount
# create the csr for the root CA
openssl req
-new
-key root.key # private key associated with the csr
-out root.csr # output file
-config root_req.config # contains config for generating the csr such as the distinguished name
# create the root CA cert
openssl ca
-in root.csr # csr file
-out root.pem # output certificate file
-config root.config # CA configuration file
-selfsign # create a self-signed certificate
-extfile ca.ext # extensions that must be present for CAs that sign certificates
-days 1095 # 3 years
# create the private key for the intermediate CA
openssl genrsa
-out intermediate.key # output file
2048 # bitcount
# create the csr for the intermediate CA
openssl req
-new
-key intermediate.key # private key associated with the csr
-out intermediate.csr # output file
-config intermediate_req.config # contains config for generating the csr such as the distinguished name
# create the intermediate CA cert
openssl ca
-in intermediate.csr # csr file
-out intermediate.pem # output certificate file
-config root.config # CA configuration file (note: root is still issuing)
-extfile ca.ext # extensions that must be present for CAs that sign certificates
-days 730 # 2 years
# create the private key for the leaf certificate
openssl genrsa
-out leaf.key # output file
2048 # bitcount
# create the csr for the leaf certificate
openssl req
-new
-key leaf.key # private key associated with the csr
-out leaf.csr # output file
-config leaf_req.config # contains config for generating the csr such as the distinguished name
# create the leaf certificate (note: no ca.ext. this certificate is not a CA)
openssl ca
-in leaf.csr # csr file
-out leaf.pem # output certificate file
-config intermediate.config # CA configuration file (note: intermediate is issuing)
-days 365 # 1 year
# verify the certificate chain
openssl verify
-x509_strict # strict adherence to rules
-CAfile root.pem # root certificate
-untrusted intermediate.pem # file with all intermediates
leaf.pem # leaf certificate to verify
Final thoughts
If you're looking to use a CA in production, please read the warnings and bugs sections of the openssl ca
man page (or just the whole man page).