This content is dumped, almost directly, from my old wiki and includes notes about 3 different iterations of orchestrating my own CA. From newest to oldest. It is a sister page to my notes on using an OpenSSL Certificate Authority.

See:

OCSP:

Create top-level directory structure

We will start with the following directory structure:

  • ca - this is where all the CA (and sub-CA) related data shall live.
  • certs - this is where certs will live.
    • ca - where the ca certs live
  • conf - this is where the openssl configuration file will exist
  • crl - certificate revocation lists
  • csr - this is where the client CSRs live.

Under ca and certs we will create a sub-directory per-ca, eventually.

mkdir ca certs certs/ca conf crl csr

Create initial configuration file

Create a minimal configuration file in conf/openssl.cnf:

dir = $ENV::PWD
 
[ ca ]
 
[ req ]
distinguished_name = req_distinguished_name
string_mask        = utf8only
 
[ req_distinguished_name ]

Create the root (self-signed) CA

Export the CA details in environment variables for ease

Put a short name (used for filenames and section names in openssl.cnf) in CANAME and the subject in CASUBJ:

CANAME="MyDomainRootCA"
CASUBJ="/countryName=GB/localityName=Birmingham/stateOrProvinceName=West Midlands/organizationName=Entek/commonName=MyDomain Root Certificate Authority"

Create the root CA directory structure and files

We need to create directories for the ca data and generated sub-certificates:

  • ca/<ca name> - Data relating to this CA
    • data - index and serial files
    • new_certs - new certificate files
    • private - private data (private key) N.B. Permissions should enforce privacy of this data
mkdir ca/$CANAME ca/$CANAME/data ca/$CANAME/new_certs ca/$CANAME/private
chmod 700 ca/$CANAME/private

Then we need to seed the data files:

  • ca/<ca name>/data/index - this just needs creating
  • ca/<ca name>/data/serial - initialise this with an integer
touch ca/$CANAME/data/index
echo 1000 > ca/$CANAME/data/serial

Configure openssl.cnf

Add a policy

We want a fairly strict policy for the root CA to apply, so add one:

[ policy_strict ]
countryName            = match
stateOrProvinceName    = match
organizationName       = match
organizationalUnitName = optional
commonName             = supplied

N.B. According to RFC3850 section 3 emailAddress should not be included in the distinguished name (“The email address SHOULD be in the subjectAltName extension, and SHOULD NOT be in the subject distinguished name.””). According to the openssl ca manual page: “Any fields not mentioned in the policy section are silently deleted” so leaving emailAddress out here is deliberate.

Add root CA extensions

The root CA needs to be a CA, so let’s create a section to do that:

[ ext_root_ca ]
basicConstraints       = critical,CA:TRUE
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
keyUsage               = critical, keyCertSign, cRLSign

Add the CA section

Finally we can add the root CA section (N.B. leave <ca name> as literally those characters, will be substituted from the environment in a second):

[ ca_root_<ca name> ]
serial          = $dir/ca/<ca name>/data/serial
database        = $dir/ca/<ca name>/data/index
certificate     = $dir/certs/ca/<ca name>.pem
private_key     = $dir/ca/<ca name>/private/<ca name>.key
new_certs_dir   = $dir/ca/<ca name>/new_certs
# CAs (which will be signed by this root ca) need a longish existence.  10 years should be enough.
default_days    = 3650
default_md      = sha512
policy          = policy_strict
copy_extensions = copy

Populate with the correct CA name

Check the substitutions to be made:

grep --colour -C4 '<ca name>' conf/openssl.cnf

And if all seems fine, make the substitutions:

sed -i "s/<ca name>/$CANAME/g" conf/openssl.cnf

Finally, check the substitutions:

grep --colour -C4 '<ca name>' conf/openssl.cnf
grep --colour -C4 "$CANAME" conf/openssl.cnf

Create the certificate

Once all this is setup, it’s a simple case of issuing a one-line command (days will give the root certificate 100 years validity):

openssl req -new -x509 -config "conf/openssl.cnf" -days 36500 -newkey rsa:4096 -sha512 -extensions ext_root_ca -keyout "ca/$CANAME/private/$CANAME.key" -out "certs/ca/$CANAME.pem" -subj "$CASUBJ"

And securing the key:

chmod 600 "ca/$CANAME/private/$CANAME.key"

Note:

  • A large key should be generated, 4096 is probably the minimum to use nowadays.
  • Don’t forget to give it a strong passphrase.

Create intermediary certificates

We should NOT be signing end certificates with the root. If we need to regenerate an intermediary then all the clients who trust the root will trust the new intermediary (and it’s issued certificates). This will not be the case if we have to re-issue the root CA.

Export the CA details in environment variables for ease

Put a short name (used for filenames and section names in openssl.cnf) in CANAME and the subject in CASUBJ.

E.g.:

  • For server certificates:
    CANAME="SUBCA1"
    CASUBJ="/countryName=GB/localityName=Birmingham/stateOrProvinceName=West Midlands/organizationName=Entek/commonName=MyDomain Server Intermediate Certificate Authority 1"
    
  • For OpenVPN certificates:
    CANAME="OPENVPNCA1"
    CASUBJ="/countryName=GB/localityName=Birmingham/stateOrProvinceName=West Midlands/organizationName=Entek/commonName=MyDomain OpenVPN Intermediate Certificate Authority 1"
    

Create the intermediary CA directory structure and files

We need to create directories for the ca data and generated sub-certificates:

  • ca/<ca name> - Data relating to this CA
    • data - index and serial files
    • new_certs - new certificate files
    • private - private data (private key) N.B. Permissions should enforce privacy of this data
  • certs/<ca name> - certificates for clients
mkdir ca/$CANAME ca/$CANAME/data ca/$CANAME/new_certs ca/$CANAME/private certs/$CANAME
chmod 700 ca/$CANAME/private

Then we need to seed the data files:

  • ca/<ca name>/data/index - this just needs creating
  • ca/<ca name>/data/serial - initialise this with an integer
  • ca/<ca name>/data/crlnumber - this needs the same integer as the serial file
touch ca/$CANAME/data/index
echo 1000 > ca/$CANAME/data/serial
echo 1000 > ca/$CANAME/data/crlnumber

Configure openssl.cnf

Add a policy

When signing end certificates we won’t want a strict policy, so let’s add a loose one:

[ policy_loose ]
countryName            = optional
stateOrProvinceName    = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied

Add intermediate CA extensions

The intermediate CA needs to be a CA, but also not allowed to have further sub CAs, so let’s create a section to do that:

[ ext_intermediate_ca ]
basicConstraints       = critical, CA:TRUE, pathlen:0
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
keyUsage               = critical, keyCertSign, cRLSign

We also need extensions for signing certificates, which may be slightly different depending on the purpose of the resultant certificate, so add a section for that too:

N.B. Because we can only have 1 CA per CRL url, we have to have per-CA extension sections.

  • Generic SSL certificate (e.g. for SSL/TLS etc.):
    [ ext_<ca name>_cert ]
    basicConstraints       = critical, CA:FALSE
    subjectKeyIdentifier   = hash
    authorityKeyIdentifier = keyid, issuer:always
    keyUsage               = critical, digitalSignature, keyEncipherment
    extendedKeyUsage       = serverAuth
    authorityInfoAccess    = OCSP;URI:http://ocsp.entek.org.uk/
    crlDistributionPoints  = URI:http://crl.entek.org.uk/<ca name>.crl.pem
    
  • OpenVPN client certificate:
    [ ext_<ca name>_cert ]
    basicConstraints       = critical, CA:FALSE
    subjectKeyIdentifier   = hash
    authorityKeyIdentifier = keyid, issuer
    keyUsage               = critical, digitalSignature, keyEncipherment
    extendedKeyUsage       = clientAuth
    authorityInfoAccess    = OCSP;URI:http://ocsp.entek.org.uk/
    crlDistributionPoints  = URI:http://crl.entek.org.uk/<ca name>.crl.pem
    

Add the CA section

Finally we can add the intermediate CA section (N.B. again, leave <ca name> as literally those characters, will be substituted from the environment in a second):

[ ca_intermediate_<ca name> ]
serial           = $dir/ca/<ca name>/data/serial
database         = $dir/ca/<ca name>/data/index
certificate      = $dir/certs/ca/<ca name>.pem
private_key      = $dir/ca/<ca name>/private/<ca name>.key
new_certs_dir    = $dir/ca/<ca name>/new_certs
crl              = $dir/crl/<ca name>.crl.pem
# 5 years for certificates signed by this CA by default
default_days     = 1825
default_md       = sha512
default_crl_days = 10
policy           = policy_loose
copy_extensions  = copy
x509_extensions  = ext_<ca name>_cert

Populate with the correct CA name

Check the substitutions to be made:

grep --colour -C4 '<ca name>' conf/openssl.cnf

And if all seems fine, make the substitutions:

sed -i "s/<ca name>/$CANAME/g" conf/openssl.cnf

Finally, check the substitutions:

grep --colour -C4 '<ca name>' conf/openssl.cnf
grep --colour -C4 "$CANAME" conf/openssl.cnf

Create the certificate

This is a little more complex than for the root as we have to generate a request then sign it with the root.

  1. Create the CSR:
    openssl req -new -out "csr/$CANAME.csr" -newkey rsa:4096 -sha512 -keyout "ca/$CANAME/private/$CANAME.key" -config "conf/openssl.cnf" -subj "$CASUBJ"
    
  2. Secure the key:
    chmod 600 "ca/$CANAME/private/$CANAME.key"
    
  3. Sign the request with the root ca:
    openssl ca -name ca_root_MyDomainRootCA -extensions ext_intermediate_ca -notext -out "certs/ca/$CANAME.pem" -config "conf/openssl.cnf" -infiles "csr/$CANAME.csr"
    

Note:

  • A large key should be generated, 4096 is probably the minimum to use nowadays.
  • Don’t forget to give it a strong passphrase

Old stuff follows!

This section is purely for posterity. In essence, you have reached the end of the useful information.

Much of this I have scripted and put in a git repository called ‘camagic’. I have detailed the steps that each script goes through here to allow the process to be understood.

Create directory structure

Command: initdirs

We will start with the following directory structure:

  • ca - this is where all the CA (and sub-CA) related configurations and data shall live.
    • private - this is where the CA keys live.
    • conf - this is where the CA configurations live.
    • data - this is where the index, serial and other tracking files live.
    • csr - this is where CSRs for intermediate CAs go
  • csr - this is where the client CSRs live.
  • certs - this is where certs will live.
    • ca - where the ca certs live.

Create root CA

Create config

Command: create_ca_conf <name of ca>

e.g.

create_ca_conf MyDomainRootCA

This will create a template configuration file and seed some files for a new ca:

  • ca/conf/<name of ca>.conf - configuration for the CA
  • ca/private/<name of ca>/serial - serial number index
  • ca/private/<name of ca>/index - index of certificates

Self-sign CA

Command: ca_self_sign <name of ca>

e.g.

ca_self_sign MyDomainRootCA

This will create a self signed root certificate in certs/ca with it’s key in ca/private.

It will prompt for the following information:

  • Passphrase for the key (set one on all signing certificates!)
  • Organisation name (I accepted default in config I had set of ‘Entek’)
  • Organisation unit (I left blank)
  • Email Address (I left blank)
  • Locality Name (I accepted default of ‘Birmingham’)
  • State or province (I accepted default of ‘West Midlands’)
  • Country (I accepted default of ‘GB’)
  • Common name (I set to <my domain> Root Certificate Authority)

Create intermediate CA

This will be used to sign all end certificates. It allows the generation of a new intermediate if the current one expires or is compromised whilst maintaining trust for clients that trust the root certificate.

Create config

Command: create_ca_conf <name of intermediate ca>

e.g.

bin/create_ca_conf MyDomainSubCA1

Sign sub-CA

Command: sub_ca_sign <ca> <name of intermediate ca>

e.g.

bin/sub_ca_sign MyDomainRootCA MyDomainSubCA1

This creates a csr for the intermediate and signs it with the specified ca.

For this one I left all the parameters as default except for the common name, which I set to: <my domain> Intermediate Certificate Authority 1.

REALLY Old stuff follows!

This section is purely for posterity. These notes predate the “old stuff” above. If you are reading this, you ignored my last note that nothing useful follows and so I presume this one will also be ignored…

Create root CA

Create a suitable directory layout and create some initial files:

mkdir certs new_certs private reqs configs
echo 1000 > serial_default
touch index_default.txt

Create configs/CA.cnf:

dir = $ENV::PWD

[ ca ]
default_ca = CA_default

[ CA_default ]
serial = $dir/serial_default
database = $dir/index_default.txt
new_certs_dir = $dir/new_certs
certificate = $dir/certs/CA.pem
private_key = $dir/private/CA.key
default_days = 365
default_crl_days = 30
default_md = sha512
preserve = no
email_in_dn = no
nameopt = default_ca
certopt = default_ca
policy = policy_match
copy_extensions = copy

[ CA_openvpn ]
serial = $dir/serial_openvpn
database = $dir/index_openvpn.txt
new_certs_dir = $dir/new_certs
certificate = $dir/certs/openvpn_intermediary_ca.pem
private_key = $dir/private/openvpn_intermediary_ca.key
default_days = 365
default_crl_days = 30
default_md = sha512
preserve = no
email_in_dn = no
nameopt = default_ca
certopt = default_ca
policy = policy_match
copy_extensions = copy

[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ req ]
default_bits = 4096
default_keyfile = key.pem
default_md = sha512
string_mask = nombstr
distinguished_name = req_distinguished_name
req_extensions = v3_req

[ req_distinguished_name ]
# Variable name	= Prompt string
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64

# Default values for the above, for consistency and less typing.
0.organizationName_default = Entek
localityName_default = Birmingham
stateOrProvinceName_default = West Midlands
countryName_default = GB
 
[ v3_ca ]
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always

[ v3_openvpn_intermediary ]
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
nsCaRevocationUrl = https://ca.my.domain.tld/CA.crl

[ v3_openvpn_server ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
nsCaRevocationUrl = https://ca.my.domain.tld/CA.crl
nsCertType = server

[ v3_req ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
nsCaRevocationUrl = https://ca.my.domain.tld/CA.crl

Create the CA certificate (I gave it a cn of <my domain> Root Certificate Authority and an email address of caroot@my.domain.tld and left everything else per the above config file):

openssl req -new -x509 -days 3650 -extensions v3_ca -keyout private/CA.key -out certs/CA.pem -config configs/CA.cnf