OpenSSL CA setup
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:
- http://stackoverflow.com/questions/18233835/creating-an-x509-v3-user-certificate-by-signing-csr
- http://wiki.cacert.org/FAQ/subjectAltName
- https://github.com/midasplatform/infrastructure/wiki/Become-your-own-SSL-Certificate-Authority
- http://stackoverflow.com/questions/21488845/how-can-i-generate-a-self-signed-certificate-with-subjectaltname-using-openssl
- https://jamielinux.com/docs/openssl-certificate-authority/create-the-root-pair.html
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 CAdata
- index and serial filesnew_certs
- new certificate filesprivate
- 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 creatingca/<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 CAdata
- index and serial filesnew_certs
- new certificate filesprivate
- 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 creatingca/<ca name>/data/serial
- initialise this with an integerca/<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.
- 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"
- Secure the key:
chmod 600 "ca/$CANAME/private/$CANAME.key"
- 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 CAca/private/<name of ca>/serial
- serial number indexca/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