Establish multi-level Public Key Infrastructure using OpenSSL
Objectives
In this short how-to guide we will establish a Public Key Infrastructure based on OpenSSL. To keep things simple and straight forward we only define two levels (although many more are possible):
- A Root Certification Authority which is only used to sign certificates of subordinate intermediary Certification Authorities
- An Intermediary Certificate Authority which is used to issue certificates for both servers and users
It should however be easy to modify the instructions below to your target implementation.
Creating a dedicated user for pki operations
For security reasons it is preferable to create a dedicated user for use with your PKI and to ensure that only this user (or group of users) has access to this location. In our setup we will create a user called pki and use the homedirectory for this account as root for the PKI.
sudo adduser pki
chmod -R 0700 /home/pki/pki-root
Make sure you repeat the last command when the PKI setup is completed to ensure that no files or directories remain world readable.
A word about structure
The following directory structure is recommended as a skeleton for setting up a new PKI. Each Certificate Authority in your Public Key Infrastructure should have a directory structure similar to the one explained below.
PKI directory: Root directory for the OpenSSL PKI
CA directory : Root directory for the Certificate Authority
CA.db.tmp: Temporary directory for openssl operations. This directory will mainly be used to store intermediary .pem files.
CA.db.certs: Directory used to store generated certificates in .pem format. It is advised to create additional subdirectories corresponding to the common name of the generated certificate to hold certificates, private keys and other formats such as p12 containers for future reference.
CA.db.crl: Directory used to store certificate revocation lists generated by openssl.
Additionally the following files will need to be created for the CA to function properly.
CA.db.serial: File indicating serial to be used for the next generated certificate. The initial value should be 01.
CA.db.crlserial: File indicating serial to be used for the next CRL. The initial value should be 01.
CA.db.index: Text based database file used by OpenSSL to store details of all generated certificates. Initially this file should be empty.
CA.db.rand: File containing a random string which ensures the randomness of the CA. If security is of high importance it is recommended to use a hardware random key generator for this.
To simplify our setup we will consistently use the following naming and extension convention throughout our PKI:
.crt: File containing a certificate
.csr: File containing a Certificate Signing Request
.key: File containing a private key
.p12: Binary container with certificate, private key and ca chain
.pwd: File containing the password used to open the p12 container
The commands to put the above mentioned structure in place are mentioned below. Make sure you modify ca in the example to reflect the name of the CA you are putting in place and make sure you execute the commands within the proper location on the filesystem (in this example /home/pki/pki-root).
mkdir ca
mkdir ca/ca.db.tmp
mkdir ca/ca.db.certs
mkdir ca/ca.db.crl
echo 01 > ca/ca.db.serial
echo 01 > ca/ca.db.crlserial
touch ca/ca.db.index
openssl rand -out ca/ca.db.rand 8192
Configure OpenSSL through openssl.cnf
The OpenSSL configuration file allows us to (pre-)define many of the settings related to our Certificate authority. Although many of these options can equally be set using command line parameters we can simplify our command lines and standardise the creation of certificates by using the configuration file.
We will define the following aspects of our Certification Authority through the configuration file:
- The Certification Authorities making up our Public Key Infrastructure and their related settings such as base directories and files.
- The policies to be applied to certificate requests, indicating which fields are optional, mandatory and/or should match the Certification Authorities values.
- The standard certificate request parameters to be used.
- The extensions to be used when generating specific types of certificates.
Let us create the OpenSSL file and place it in our root PKI directory (/home/pki/). The OpenSSL configuration file should start with a entry indicating which Certification Authority should be used by default is none is specified during certificate generation.
[ ca ]
default_ca = CA # The default ca section
As a next step we need to introduce a CA configuration section for each of the CA’s which make up our Public Key Infrastructure.
Note: if your PKI only consists of one CA you should only create one section.
Note: Make sure to replace CA with the name of the Certification Authority you are implementing, in our example RootCA and IntermCA.
[ CA ]
dir = ./CA
# Where everything is kept
certs = $dir/ca.db.certs
# Where the issued certs are kept
crl_dir = $dir/ca.db.crl
# Where the issued crl are kept
database = $dir/ca.db.index
# database index file.
unique_subject = no
# Set to 'no' to allow creation of several ctificates with same subject.
new_certs_dir = $dir/ca.db.certs
# default place for new certs.
certificate = $dir/ca.crt
# The CA certificate
serial = $dir/ca.db.serial
# The current serial number
crlnumber = $dir/ca.db.crlserial
# the current crl number must be commented out to leave a V1 CRL
private_key = $dir/ca.key
# The private key
RANDFILE = $dir/ca.db.rand
# private random number file
name_opt = ca_default
# Subject Name options
cert_opt = ca_default
# Certificate field options
default_days = 365
# how long to certify for
default_crl_days = 60
# how long before next CRL
default_md = md5
# use public key default MD
preserve = no
# keep passed DN ordering
policy = policy_match
The options, policy match and unique subject are most commonly used for root certification authorities since you will want the attributes of the intermediary CA’s to match the root CA and to ensure that you have a unique subject. These two options should potentially be modified for intermediary CA’s.
Make sure to modify the relevant values to reflect the configuration of the CA’s you want to put in place. Additionally it is advisable to modify the default_ca parameter to contain the CA you want to use for signing certificates by default.
Once we defined the CA’s we have the option of creating a policy which will be applied to certificate signing requests. By default the OpenSSL configuration file contains two policies called policy_match and policy_anything.
The first requires the request to match most of the values present in the CA certificate such as country name, state or province etc.
The latter imposes no restrictions on the content of the certificate signing request.
[ policy_match ]
countryName = match
stateOrProvinceName = match
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
Let’s go ahead and define our own policy which is to be used when signing user certificates. Feel free to modify the template below to match whatever requirements you want to put on specific types of certificate signing requests.
[ policy_usr ]
countryName = supplied
stateOrProvinceName = supplied
localityName = supplied
organizationName = supplied
organizationalUnitName = optional
commonName = supplied
emailAddress = supplied
With our CA’s and policies in place it’s time to define the ‘default’ request parameters. Modify the parameters below to reflect your organisation
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca # The extentions to add to the self signed cert
string_mask = nombstr
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = BE
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Vlaams-Brabant
localityName = Locality Name (eg, city)
localityName_default = Leuven
0.organizationName = Organization Name (eg, company)
0.organizationName_default = AUSY
1.organizationName = Second Organization Name (eg, company)
1.organizationName_default = not used
organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = Demo CA project
commonName = Common Name (eg, YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
[ req_attributes ]
# challengePassword = A challenge password
# challengePassword_min = 4
# challengePassword_max = 20
# unstructuredName = An optional company name
As a final step we have the option of defining specific extensions, contstraints and comments for specific certificate types. Below you find an example of server and user certificate types and their specifics. Make sure to modify the crlDistributionPoints and/or comment to reflect your environment.
[ srv_cert ]
basicConstraints=CA:FALSE
nsCertType = server
crlDistributionPoints = URI:http://example.com/crl.pem
nsComment = "OpenSSL Generated Server Certificate"
[ usr_cert ]
basicConstraints=CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer:always
The v3req, v3ca and crlext definitions are required for OpenSSL to function properly. These sections define the basic configuration for a common certificate request and the required extensions to generate a CA certificate. The crlext section defines how CRL’s are generated.
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
# Extensions for a typical CA
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always
basicConstraints = CA:true
[ crl_ext ]
# CRL extensions.
authorityKeyIdentifier=keyid:always,issuer:always
## Establishing our CA’s
### Setup Root CA
Create Root CA public / private keypair
openssl genrsa -des3 -out ./RootCA/RootCA.key 2048
Generate Root CA certificate, which is by definition a self signed certificate. Don’t forget to modify the validity to an appropriate value.
openssl req -new -x509 -days 3650 -key ./RootCA/RootCA.key -out ./RootCA/RootCA.crt -config openssl.cnf
Setup intermediary CA(’s)
Create Intermediate CA public / private keypair
mkdir ./RootCA/RootCA.db.certs/IntermCA
openssl genrsa -des3 -out ./RootCA/RootCA.db.certs/IntermCA/IntermCA.key 2048
Generate Certificate Siging Request for intermediary CA. Don’t forget to modify the validity to an appropriate value.
openssl req -new -days 1095 -key ./RootCA/RootCA.db.certs/IntermCA/IntermCA.key -out ./RootCA/RootCA.db.certs/IntermCA/IntermCA.csr -config openssl.cnf
Sign the previously generated CSR using the RootCA while adding the CA extension to ensure that the resulting certificate can be used to sign other certificates.
openssl ca -config openssl.cnf -name RootCA -extensions v3_ca -out ./RootCA/RootCA.db.certs/IntermCA/IntermCA.crt -infiles ./RootCA/RootCA.db.certs/IntermCA/IntermCA.csr
Now copy over the relevant certificate and key to the IntermCA root.
cp ./RootCA/RootCA.db.certs/IntermCA/IntermCA.crt ./RootCA/RootCA.db.certs/IntermCA/IntermCA.key ./IntermCA/
It’s recommended to create a certificate chain file for future use. This file can be imported by a client and will indicate the full chain of Certification Authorities linked to the provided certificate.
Common operations
That’s it, all relevant bits and pieces should be in place to start using our OpenSSL PKI. In this section you will find some common operations and the related openssl commands.
Generate server certificate
openssl req -newkey rsa:2048 -keyout ./IntermCA/IntermCA.db.certs/server.domain/server.domain.key -nodes -config openssl.cnf -out ./IntermCA/IntermCA.db.certs/server.domain/server.domain.csr
openssl ca -config openssl.cnf -name IntermCA -out ./IntermCA/IntermCA.db.certs/server.domain/server.domain.crt -infiles ./IntermCA/IntermCA.db.certs/server.domain/server.domain.csr
Generate user certificate
openssl req -newkey rsa:2048 -keyout ./IntermCA/IntermCA.db.certs/username/username.key -nodes -config openssl.cnf -out ./IntermCA/IntermCA.db.certs/username/username.csr
'' openssl ca -config openssl.cnf -extensions usr_cert -name IntermCA -out ./IntermCA/IntermCA.db.certs/username/username.crt -infiles ./IntermCA/IntermCA.db.certs/username/username.csr
Generate p12
openssl pkcs12 -export -clcerts -in ./IntermCA/IntermCA.db.certs/server.domain/server.domain.crt -inkey ./IntermCA/IntermCA.db.certs/server.domain/server.domain.key -certfile ./pki.crt -out ./IntermCA/IntermCA.db.certs/server.domain/server.domain.p12
Revoke certificate
openssl ca -config openssl.cnf -name IntermCA -revoke ./IntermCA/IntermCA.db.certs/02.pem
Generate CRL
openssl ca -config openssl.cnf -name IntermCA -gencrl -out ./IntermCA/IntermCA.crl/crl-27.02.2014.pem
Generate CA chain
cat ./RootCA/RootCA.crt ./IntermCA/IntermCA.crt > pki.crt
... Regularly reinventing a slightly different wheel ...