OpenSSL is a full featured tool capable not only to generate keys and certificates, but also to provide every facility a PKI must have, such as indirect CRL and OCSP responders: these features, along with certificate's best practices such as the Certification Practice Statement (CPS), publishing CRL Distribution Points URL, OCSP Responders URL, CA Issuers URL, are the topics of the OpenSSL CA tutorial - A full-featured openssl PKI. 

Right after setting up the Lab PKI, the post shows you also how to mark the issued certificates as Extended Validated, Organization Validated, Individual Validated and Domain Validated.

This post is the hands-on lab of my previous post "X509 Certificates HowTo - A Public Key Infrastructure Tutorial": I strongly suggest you to read it before going further: I won't provide any theoretical notions in this post - it would be pointless, since it's every already explained there.

Mind that OpenSSL CLI and tools have not been developed with performance in mind: if you need something more performing, please consider using OpenXPKI or Cloudflare's PKI and TLS Toolkit. If you are interested into quickly learn how to set up a full-featured PKI with Cloudflare's PKI and TLS Toolkit, here are the links to the book I wrote on it: ebook and of course, paperback.

The Public Key Infrastructure We Want To implement

We are going to setup the following Lab:

  • Root CA: CARC R100T
  • Intermediate CA: CARCI100T
  • both CA will have their own Validation Authority with dedicated certificate to sign OCSP Responses and Indirect CRL
  • both CA use Elliptic Curves as the key algorithm
This post shows how to set-up a Systemd Unit for the OCSP Responder: since HTTP publishing the CRL file is the same as publishing any kind of file, you can configure your favourite web server for doing this trivial task. Mind only that it is not necessary to use HTTPS: a CRL is a public document - the only important thing is that it is digitally signed by the authoritative entity.

Since we are setting up everything from scratch, and every configuration command needs administrative rights, we directly switch to the root user:

sudo su -

Prerequisites

Add a PKI Service User

The very first and obvious thing to do is creating a dedicated service user::

adduser -r -c "pki Service User" -s /sbin/nologin pki

Setup The PKI Directory Tree

As we saw, a Public Key Infrastructure is very often made of more than just one Certificate Authority: for this reason we create two dedicated directory trees:

the first, where to store each Certificate Authorities' configuration files:

mkdir -m 755 /etc/corporate-pki \
/etc/corporate-pki/ca

the second one is instead used to store data files of each Certificate Authority:

mkdir -m 755 /var/lib/corporate-pki

Automating The Certificate Authority Creation

Manually configuring Certificate Authorities with openssl is a cumbersome, time consuming and error prone task: for this reason I developed the below automation script that automatically generates the directories structures, taking care of properly setting permissions, and deriving OpenSSL configuration files from templates.

The script is intentionally missing the statements necessary to create the keys and certificates: the goal of this post is exactly to explain how to deal with openssl - automating everything is just the opposite of this purpose. If you fancy, you can complete the script yourself by adding the statements we are manually using in this post, ... it is a trivial task.

The CA-Setup Script

Create the "/usr/local/bin/corporate-pki-newca.sh" script with the following contents:

#!/bin/bash
PKI_CONF_PATH="/etc/corporate-pki/ca"
PKI_TEMPLATES_PATH="/etc/corporate-pki/templates"
PKI_LIB_PATH="/var/lib/corporate-pki"
PKI_SERVICE_USER="pki"
ADD_ISSUER=0

CA_NAME_D="CARC I100T"
CA_LABEL_D="carc_i100t"
OCSP_URI_D="http://ocsp.test.carcano.local:8889"
CAISSUERS_URI_D="http://ca.test.carcano.local/carc_r100t.cer"
CRL_URI_D="http://www.test.carcano.local/crl/carc_r100t.crl"
COUNTRY_D="CH"
ORGANIZATION_D="Carcano SA"
ENTERPRISE_OID_D="1234"
CPS_URL_D="http://www.carcano.local/ca/cps.html"
ROOTCA_LABEL_D="CARC I100T"

validate(){
    local missing_options=""
    if [[ -z ${CA_NAME} ]]; then
        missing_options="--ca-name"
    fi
    if [[ -z ${CA_LABEL} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--ca-label"
    fi
    if [[ -z ${ORGANIZATION} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--organization"
    fi
    if [[ -z ${ENTERPRISE_OID} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--enterprise-oid"
    fi
    if [[ -z ${COUNTRY} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--country"
    fi
    if [[ -z ${CRL_URI} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--crl-url"
    fi
    if [[ -z ${OCSP_URI} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--ocsp-url"
    fi
    if [[ -z ${CAISSUERS_URI} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--ca-issuers"
    fi
    if [[ -z ${CPS_URL} ]]; then
        [ -n "${missing_options}" ] && missing_options="${missing_options}, "
        missing_options="${missing_options}--cps-url"
    fi
    if [[ -n "${missing_options}" ]]; then
        echo "" >&2
        echo "The following mandatory options are missing:" >&2
        echo "" >&2
        echo "${missing_options}" >&2
        echo "" >&2
        echo "The command line syntax is as follows:" >&2
	usage
        exit 1
    fi
}

usage(){
    echo "" >&2
    echo "Create a new Certificate Authority" >&2
    echo "" >&2
    echo "  USAGE:" >&2
    echo "" >&2
    echo "    ${0##*/} [ OPTIONS ]" >&2
    echo "" >&2
    echo "  OPTIONS:" >&2
    echo "" >&2
    echo "    -n|--ca-name:         Name of the CA to create - for example: \"${CA_NAME_D}\"" >&2
    echo "    -l|--ca-label:        Label of the CA to create - for example: \"${CA_LABEL_D}\"" >&2
    echo "    -g|--organization:    Organization - for example: \"${ORGANIZATION_D}\"" >&2
    echo "    -i|--enterprise-oid:  Enterprise OID - for example: \"${ENTERPRISE_OID_D}\"" >&2
    echo "    -c|--country:         Country - for example: \"${COUNTRY_D}\"" >&2
    echo "    -v|--crl-url:         CRL's URL - for example: \"${CRL_URI_D}\"" >&2
    echo "    -o|--ocsp-url:        OCSP responder's URL - for example: \"${OCSP_URI_D}\"" >&2
    echo "    -u|--ca-issuers:      CA-Issuers URL - for example: \"${CAISSUERS_URI_D}\"" >&2
    echo "    -p|--cps-url:         CPS URL - for example: \"${CPS_URL_D}\"" >&2
    echo "    -r|--root-ca-label:   if creating an INTERMEDIATE level CA," >&2
    echo "                          the label of the ROOT level CA - for example \"${ROOTCA_LABEL_D}\"" >&2
    echo "    -s|--add-issuer:      add CA Issuer name and certificate serial to issued certificates" >&2
    echo "                          WARNING - not compatible with CA's cross-signing and CA's certificate" >&2
    echo "                          re-issuing using the same private key" >&2
    echo "    -h|--help:            print this help message" >&2
    echo "" >&2
}

if [[ -z "${1}" ]]; then
    usage
    exit 1
fi

echo ""
PARAMS=`getopt -o l:i:o:u:v:c:g:n:p:r:h --long ca-label:,enterprise-oid:,ocsp-url:,ca-issuers-url:,crl-url:,country:,organization:,ca-name:,cps-url:,root-ca-label:,help -n "${0##*/}" -- "$@"`
if [[ ${?} -ne 0 ]]; then
    usage
    exit 1
fi

eval set -- "${PARAMS}"
unset PARAMS

CA_LEVEL="intermediate"

while true
do
  case ${1} in
    -l|--ca-label)
      CA_LABEL=${2}
      shift 2
      ;;
    -i|--enterprise-oid)
      ENTERPRISE_OID=${2}
      shift 2
      ;;
    -o|--ocsp-url)
      OCSP_URI=${2}
      shift 2
      ;;
    -u|--ca-issuers-url)
      CAISSUERS_URI=${2}
      shift 2
      ;;
    -v|--crl-url)
      CRL_URI=${2}
      shift 2
      ;;
    -c|--country)
      COUNTRY=${2}
      shift 2
      ;;
    -g|--organization)
      ORGANIZATION=${2}
      shift 2
      ;;
    -n|--ca-name)
      CA_NAME=${2}
      shift 2
      ;;
    -p|--cps-url)
      CPS_URL=${2}
      shift 2
      ;;
    -r|--root-ca-label)
      ROOTCA_LABEL=${2}
      shift 2
      ;;
    -s|--add-issuer)
	    ADD_ISSUER=1
      shift 1
      ;;
    -h|--help)
      usage
      exit
      ;;
    --)
      shift
      break
      ;;
  esac
done

validate

if [[ -z "${ROOTCA_LABEL}" ]]; then
  CA_LEVEL="root"
fi

[ -d "${PKI_CONF_PATH}/${CA_LABEL}" ] || mkdir -m 755 "${PKI_CONF_PATH}/${CA_LABEL}"
[ -d "${PKI_CONF_PATH}/${CA_LABEL}/conf" ] || mkdir -m 755 "${PKI_CONF_PATH}/${CA_LABEL}/conf"
[ -d "${PKI_CONF_PATH}/${CA_LABEL}/keys" ] || mkdir -m 700 "${PKI_CONF_PATH}/${CA_LABEL}/keys"
chown ${PKI_SERVICE_USER}: "${PKI_CONF_PATH}/${CA_LABEL}/keys"
[ -d "${PKI_CONF_PATH}/${CA_LABEL}/certs" ] || mkdir -m 755 "${PKI_CONF_PATH}/${CA_LABEL}/certs"
[ -d "${PKI_LIB_PATH}/${CA_LABEL}" ] || mkdir -m 755 "${PKI_LIB_PATH}/${CA_LABEL}"
[ -d "${PKI_LIB_PATH}/${CA_LABEL}/certs" ] || mkdir -m 755 "${PKI_LIB_PATH}/${CA_LABEL}/certs"
[ -d "${PKI_LIB_PATH}/${CA_LABEL}/db" ] || mkdir -m 755 "${PKI_LIB_PATH}/${CA_LABEL}/db"
[ -d "${PKI_LIB_PATH}/${CA_LABEL}/crl" ] || mkdir -m 755 "${PKI_LIB_PATH}/${CA_LABEL}/crl"
[ -f "${PKI_LIB_PATH}/${CA_LABEL}/db/index.txt" ] || touch "${PKI_LIB_PATH}/${CA_LABEL}/db/index.txt"
[ -f "${PKI_LIB_PATH}/${CA_LABEL}/db/serial" ] || echo "1000" > "${PKI_LIB_PATH}/${CA_LABEL}/db/serial"
cp "${PKI_TEMPLATES_PATH}/openssl-${CA_LEVEL}.conf" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s/##CA_LABEL##/${CA_LABEL}/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s+##PKI_CONF_PATH##+${PKI_CONF_PATH}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s+##PKI_LIB_PATH##+${PKI_LIB_PATH}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s/##ENTERPRISE_OID##/${ENTERPRISE_OID}/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s+##OCSP_URI##+${OCSP_URI}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s+##CAISSUERS_URI##+${CAISSUERS_URI}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s+##CRL_URI##+${CRL_URI}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s/##COUNTRY##/${COUNTRY}/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s/##ORGANIZATION##/${ORGANIZATION}/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s/##CA_NAME##/${CA_NAME}/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
sed -i "s+##CPS_URL##+${CPS_URL}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
cp "${PKI_TEMPLATES_PATH}/crl.conf" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
sed -i "s/##CA_LABEL##/${CA_LABEL}/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
sed -i "s+##PKI_CONF_PATH##+${PKI_CONF_PATH}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
sed -i "s+##PKI_LIB_PATH##+${PKI_LIB_PATH}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
sed -i "s+##CRL_URI##+${CRL_URI}+g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
if [[ ${ADD_ISSUER} -eq 1 ]]; then
  sed -i "s/##AUTHORITY_KEY_IDENTIFIER##/authorityKeyIdentifier = keyid:always,issuer:always/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
  sed -i "s/##AUTHORITY_KEY_IDENTIFIER##/authorityKeyIdentifier = keyid:always,issuer:always/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
else
  sed -i "s/##AUTHORITY_KEY_IDENTIFIER##/authorityKeyIdentifier = keyid:always/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/openssl.conf"
  sed -i "s/##AUTHORITY_KEY_IDENTIFIER##/authorityKeyIdentifier = keyid:always/g" "${PKI_CONF_PATH}/${CA_LABEL}/conf/crl.conf"
fi
[ -f "${PKI_LIB_PATH}/${CA_LABEL}/db/crl_serial" ] || echo 1000 > "${PKI_LIB_PATH}/${CA_LABEL}/db/crl_serial"

and of course, set it as executable:

chmod 755 /usr/local/bin/corporate-pki-newca.sh

OpenSSL CA configuration File Templates

The automation script generates the Certificate Authority's configuration file from the following template files:

  • openssl-root.conf
  • openssl-intermediate.conf
  • crl.conf

Let's create the directory where to store them:

mkdir -m 755 /etc/corporate-pki/templates

we are now ready to create the template files.

Root Certificate Authorities Configuration Template

Create the "/etc/corporate-pki/templates/openssl-root.conf" file with the following contents:

oid_section     = custom_oids

[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = ##PKI_LIB_PATH##/##CA_LABEL##
certs             = ##PKI_CONF_PATH##/##CA_LABEL##/certs
private_key       = ##PKI_CONF_PATH##/##CA_LABEL##/keys/ca.key
certificate       = ##PKI_CONF_PATH##/##CA_LABEL##/certs/ca.crt
new_certs_dir     = $dir/certs
database          = $dir/db/index.txt
serial            = $dir/db/serial
default_md        = sha256
unique_subject    = no
policy            = policy_short
preserveDN        = no
email_in_dn       = no

[ custom_oids ]
CPS           = 1.3.6.1.4.1.##ENTERPRISE_OID##.1.1
CA-Cert       = 1.3.6.1.4.1.##ENTERPRISE_OID##.1.2
EV-Cert       = 2.23.140.1.1

[ policy_short ]
countryName             = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied

[ req ]
distinguished_name = req_distinguished_name

[ req_distinguished_name ]

[ root_ca ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
keyUsage = critical,keyCertSign
basicConstraints = critical,CA:TRUE

[ crl_issuer ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
keyUsage = critical,cRLSign,nonRepudiation,digitalSignature,keyEncipherment
basicConstraints = critical,CA:FALSE
extendedKeyUsage = OCSPSigning

[ aia ]
OCSP;URI.1=##OCSP_URI##
caIssuers;URI.2=##CAISSUERS_URI##

[ intermediate_ca ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
basicConstraints = critical,CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
crlDistributionPoints = crl_dp
authorityInfoAccess = @aia
certificatePolicies = ia5org,@CPS,@CA_policy,@EV_policy

[ crl_dp ]
fullname = URI:##CRL_URI##
CRLissuer = dirName:crl_issuer_dn

[ crl_issuer_dn ]
C=##COUNTRY##
O=##ORGANIZATION##
CN=##CA_NAME##

[ CPS ]
policyIdentifier = CPS
CPS.1            = "##CPS_URL##"
userNotice.1     = @CPS_Notice

[ CPS_Notice ]
explicitText  = "##CA_NAME## Certification Practice Statement"

[ CA_policy ]
policyIdentifier = CA-Cert
userNotice.2     = @CA_Notice

[ CA_Notice ]
explicitText  = "CA Certificate Policy"

[ EV_policy ]
policyIdentifier = EV-Cert
userNotice.6     = @EV_Notice

[ EV_Notice ]
explicitText  = "Certificate issued in compliance with the Extended Validation Guidelines"

Specifying "issuer" as value of the authorityKeyIdentifier x509v3 extension causes the issuer's certificate Dir name and Certificate serial to be added to every issued certificate. This deeply affects the validation procedure: since the signer's certificate serial is included, when renewing, it is no more possible to re-issue the Certificate Authority's certificate using the same private key, since the resulting certificate will have a serial that differs from the one that was copied in every issued certificate. If you want to be able to renew CA's certificate by using the same private key,  you must not include "issuer" in the authorityKeyIdentifier x509v3 extension. The script allow you to work both ways: by default it does not add "issuer", but if you fancy you can configure it the strict way by supplying the --add-issuer command line switch - the ##AUTHORITY_KEY_IDENTIFIER## is automatically replaced with the proper settings.

Intermediate Certificate Authorities Configuration Template

Create the "/etc/corporate-pki/templates/openssl-intermediate.conf" file with the following contents:

oid_section     = custom_oids

[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = ##PKI_LIB_PATH##/##CA_LABEL##
certs             = ##PKI_CONF_PATH##/##CA_LABEL##/certs
private_key       = ##PKI_CONF_PATH##/##CA_LABEL##/keys/ca.key
certificate       = ##PKI_CONF_PATH##/##CA_LABEL##/certs/ca.crt
new_certs_dir     = $dir/certs
database          = $dir/db/index.txt
serial            = $dir/db/serial
default_md        = sha256
unique_subject    = no
policy            = policy_short
preserveDN        = no
email_in_dn       = no

[ custom_oids ]
CPS           = 1.3.6.1.4.1.##ENTERPRISE_OID##.1.1
CA-Cert       = 1.3.6.1.4.1.##ENTERPRISE_OID##.1.2
DV-Cert       = 2.23.140.1.2.1
OV-Cert       = 2.23.140.1.2.2
IV-Cert       = 2.23.140.1.2.3
EV-Cert       = 2.23.140.1.1

[ policy_short ]
countryName             = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied

[ req ]
distinguished_name = req_distinguished_name

[ req_distinguished_name ]

[ crl_issuer ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
keyUsage = critical,cRLSign,nonRepudiation,digitalSignature,keyEncipherment
basicConstraints = critical,CA:FALSE
extendedKeyUsage = OCSPSigning

[ aia ]
OCSP;URI.1=##OCSP_URI##
caIssuers;URI.2=##CAISSUERS_URI##

[ intermediate_ca ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
basicConstraints = critical,CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
crlDistributionPoints = crl_dp
authorityInfoAccess = @aia
certificatePolicies = ia5org,@CPS,@CA_policy,@EV_policy

[ dv ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
basicConstraints = critical,CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
crlDistributionPoints = crl_dp
authorityInfoAccess = @aia
certificatePolicies = ia5org,@CPS,@DV_policy

[ iv ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
basicConstraints = critical,CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
crlDistributionPoints = crl_dp
authorityInfoAccess = @aia
certificatePolicies = ia5org,@CPS,@IV_policy

[ ov ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
basicConstraints = critical,CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
crlDistributionPoints = crl_dp
authorityInfoAccess = @aia
certificatePolicies = ia5org,@CPS,@OV_policy

[ ev ]
subjectKeyIdentifier = hash
##AUTHORITY_KEY_IDENTIFIER##
basicConstraints = critical,CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
crlDistributionPoints = crl_dp
authorityInfoAccess = @aia
certificatePolicies = ia5org,@CPS,@EV_policy

[ crl_dp ]
fullname = URI:##CRL_URI##
CRLissuer = dirName:crl_issuer_dn

[ crl_issuer_dn ]
C=##COUNTRY##
O=##ORGANIZATION##
CN=##CA_NAME##

[ CPS ]
policyIdentifier = CPS
CPS.1            = "##CPS_URL##"
userNotice.1     = @CPS_Notice

[ CPS_Notice ]
explicitText  = "##CA_NAME## Certification Practice Statement"

[ CA_policy ]
policyIdentifier = CA-Cert
userNotice.2     = @CA_Notice

[ CA_Notice ]
explicitText  = "CA Certificate Policy"

[ DV_policy ]
policyIdentifier = DV-Cert
userNotice.3     = @DV_Notice

[ DV_Notice ]
explicitText  = "Compliant with Baseline Requirements – No entity identity asserted"

[ IV_policy ]
policyIdentifier = IV-Cert
userNotice.4     = @IV_Notice

[ IV_Notice ]
explicitText  = "Compliant with Baseline Requirements – Individual identity asserted"

[ OV_policy ]
policyIdentifier = OV-Cert
userNotice.5     = @OV_Notice

[ OV_Notice ]
explicitText  = "Compliant with Baseline Requirements – Individual identity asserted"

[ EV_policy ]
policyIdentifier = EV-Cert
userNotice.6     = @EV_Notice

[ EV_Notice ]
explicitText  = "Certificate issued in compliance with the Extended Validation Guidelines"
As you should expect, the Intermediate CAs' template differs from the Root CAs' template: the CA's have different purposes - for example the Intermediate CA must be able to issue Extended Validated (EV), Organization Validated (OV), Individual Validated (IV) and Domain Validated (DV) entity certificates.

Validation Authorities Configuration Template

The Validation Authorities need to have their own OpenSSL configuration file, so we create the "/etc/corporate-pki/templates/crl.conf" template file with the following contents:

Are you enjoying these high quality free contents on a blog without annoying banners? I like doing this for free, but I also have costs so, if you like these contents and you want to help keeping this website free as it is now, please put your tip in the cup below:

Even a small contribution is always welcome!

[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = ##PKI_LIB_PATH##/##CA_LABEL##
private_key       = ##PKI_CONF_PATH##/##CA_LABEL##/keys/va.key
certificate       = ##PKI_CONF_PATH##/##CA_LABEL##/certs/va.crt
database          = $dir/db/index.txt
default_md        = sha256
crlnumber         = $dir/db/crl_serial
default_crl_days  = 30
crl_dir           = ##PKI_LIB_PATH##/##CA_LABEL##/crl
crl               = ##PKI_LIB_PATH##/##CA_LABEL##/crl/##CA_LABEL##

[ crl_extensions ]
##AUTHORITY_KEY_IDENTIFIER##
issuingDistributionPoint = @crl_issuing_dp

[ crl_issuing_dp ]
fullname = URI:##CRL_URI##
indirectCRL=TRUE

Setup The Root CA

We finally have all the necessary automation to quickly set up Certificate Authorities: let's start from setting up the "CARC R100T" ROOT level Certificate Authority with the following settings:

  • Name: CARC R100T
  • Label: carc-r100t
  • Enterprise OID: 1234
  • Organization: Carcano SA
  • Country: CH
  • CA issuers URL: http://ca.test.carcano.local/carc_r100t.cer
  • Certificate Practice Statement URL: http://www.carcano.local/ca/cps.html
  • CRL Endpoint URL: http://www.test.carcano.local/crl/carc_r100t.crl
  • OCSP Responder URL: http://ocsp.test.carcano.local:8889
Since this is a Lab, we can use any value as Enterprise OID, but in real life you MUST ask IANA for assigning you a dedicated one. If you really need an Enterprise OID - don't use this for experiments - the form is here.

Let's create the directory tree and configuration files using the automation script we developed:

/usr/local/bin/corporate-pki-newca.sh --ca-name "CARC R100T" \
--ca-label carc_r100t --enterprise-oid 1234 --organization "Carcano SA" --country CH \
--crl-url "http://www.test.carcano.local/crl/carc_r100t.crl" \
--ocsp-url "http://ocsp.test.carcano.local:8889" \
--ca-issuers "http://ca.test.carcano.local/carc_r100t.cer" \
--cps-url "http://www.carcano.local/ca/cps.html"

If you don't know the meaning of some of the above parameters, refer to my previous post “X509 Certificates HowTo - A Public Key Infrastructure Tutorial”: since I already thoroughly explained everything, I won't go too much into details in this post, also because otherwise it would become too long.

Generate The Self-Signed ROOT CA Certificate

The very first certificate to generate is the Certificate Authority's self-signed certificate.

First, we generate the key as an empty file and immediately restrict access to it:

CA_LABEL="carc_r100t"
PKI_CONF_PATH="/etc/corporate-pki/ca/${CA_LABEL}"
touch ${PKI_CONF_PATH}/keys/ca.key
chmod 400 ${PKI_CONF_PATH}/keys/ca.key
chown pki: ${PKI_CONF_PATH}/keys/ca.key

then generate the private key overwriting the empty file:

openssl ecparam -name secp521r1 -genkey | \
openssl ec -out ${PKI_CONF_PATH}/keys/ca.key -aes256

of course we are asked for the password we want to use to encrypt the private key.

In this example we created an Elliptic Curve private key using the secp521r curve: if you need more information on this topic, please refer to "Symmetric And Asymmetric Cryptography".

Create the Certificate Signing Request (CSR) with the following data:

  • Certificate Lifetime: 3652 days (10 years)
  • Country: CH
  • Organization: Carcano SA
  • Common Name: CARC R100T
openssl req -new -key ${PKI_CONF_PATH}/keys/ca.key \
-out ${PKI_CONF_PATH}/certs/ca.csr \
-subj "/C=CH/O=Carcano SA/CN=CARC R100T"

Then, we generate the CA Certificate from the CSR, signing it with the same key used for the CSR:

openssl x509 -req -extfile ${PKI_CONF_PATH}/conf/openssl.conf \
-extensions root_ca -days 3652 \
-key ${PKI_CONF_PATH}/keys/ca.key \
-in ${PKI_CONF_PATH}/certs/ca.csr \
-out ${PKI_CONF_PATH}/certs/ca.crt

The outcome is a PEM encoded file containing the certificate data.

Let's have a look to its first two lines and last two lines:

head -n 2 ${PKI_CONF_PATH}/certs/ca.crt; echo ...; tail -n 2 ${PKI_CONF_PATH}/certs/ca.crt

the output looks like the following snippet:

-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIUOQTx2cnp7mGK4Dumzly6I5Bp7PowCgYIKoZIzj0EAwQw
...
wBKX/Z/uE5Y3ZB/gABw3
-----END CERTIFICATE-----

We can of course decode the PEM formatted certificate and print it in human-readable format.

Type the following statement:

openssl x509 -noout -text -in ${PKI_CONF_PATH}/certs/ca.crt

the output is as follows:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            70:97:97:61:e9:ce:c4:31:16:5b:63:a7:c8:2e:58:8f:bf:64:d2:7a
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = CH, O = Carcano SA, CN = CARC R100T
        Validity
            Not Before: Jun  1 15:02:58 2023 GMT
            Not After : May 31 15:02:58 2033 GMT
        Subject: C = CH, O = Carcano SA, CN = CARC R100T
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (521 bit)
                pub:
                    04:00:10:30:b5:2c:03:09:5d:9f:d2:a6:c4:43:e0:
                    89:5c:10:47:c1:ea:54:b7:08:f0:1c:ec:39:c9:8b:
                    ca:92:59:dd:14:20:01:c5:4e:74:0e:9f:14:ec:02:
                    21:b5:3a:f6:cb:34:49:5b:d7:e1:a5:9b:06:61:b2:
                    db:48:6c:03:b5:8f:50:01:23:76:ff:3d:b2:ce:ba:
                    ae:0a:d5:c9:24:74:00:27:0a:63:b1:0d:21:c1:f7:
                    4b:40:0f:78:e9:cd:88:50:fa:25:29:e8:e3:57:47:
                    42:c5:dc:24:66:7d:f0:46:9a:25:78:69:be:11:ca:
                    28:a6:c0:86:28:c4:1f:a0:c6:64:13:e3:4b
                ASN1 OID: secp521r1
                NIST CURVE: P-521
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                B5:26:F1:34:F7:C6:51:A7:F3:01:73:9F:28:16:8B:F3:A0:E8:9F:65
            X509v3 Authority Key Identifier: 
                B5:26:F1:34:F7:C6:51:A7:F3:01:73:9F:28:16:8B:F3:A0:E8:9F:65
            X509v3 Key Usage: critical
                Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:81:87:02:41:59:eb:46:77:cd:b3:0e:f1:b5:cd:19:7e:c6:
        66:b0:a1:64:65:38:24:a3:fa:a1:06:48:98:af:b5:fc:9b:56:
        26:c5:0c:cb:03:0f:94:23:ff:85:57:e3:d4:6b:27:c2:09:b9:
        55:db:1a:ef:f6:69:18:51:b4:21:45:96:c9:95:89:c1:02:42:
        01:25:a8:1c:1f:80:90:c9:c6:67:8e:a6:3b:18:ec:97:4d:28:
        7d:ff:50:66:02:4c:9a:4e:fa:1b:c1:1e:3d:e8:c3:79:a0:d2:
        b1:19:1a:1b:81:a1:65:54:74:67:7f:6f:ea:49:d4:27:a7:4f:
        a2:4f:40:f5:4f:db:5d:16:b0:22:6f:f0

In this post I really want to show you a complex scenario, so that you can derive your own setups tailored to your needs. If you look at the above output, you see the key usage is only "Certificate Sign '': since it is missing the “CRL Sign” usage, this certificate can of course be used to sign other certificates, but not to sign CRLs. This advanced setup, called "Indirect CRL'', enables you to remove the CA's private key to securely store it offline right after having signed the intermediate CA's certificate: you will use the delegated private key to sign CRLs. The downside is that not every SSL implementation is compatible with this advanced setup, so you have to make sure the technology stack in your environment supports it.

Generate the CA's bundle certificate file with the trust-chain for the CA's certificate:

cp ${PKI_CONF_PATH}/certs/ca.crt ${PKI_CONF_PATH}/certs/bundle.crt

Validation Authority

As we saw, without a Validation Authority it is not possible to verify if certificates have been revoked before their expiration date. Let's set-up the "CARC R100T" validation authority so that it provides both Indirect CRL and OCSP.

Indirect CRL Certificate

Let's generate the private key of the certificate we want to delegate for signing Certificate Revocation Lists (CRLs) - as we did before, generate and restrict access to the private key file:

touch ${PKI_CONF_PATH}/keys/va.key
chmod 400 ${PKI_CONF_PATH}/keys/va.key
chown pki: ${PKI_CONF_PATH}/keys/va.key

then generate the private key overwriting the empty file:

openssl ecparam -name secp521r1 -genkey | \
openssl ec -out ${PKI_CONF_PATH}/keys/va.key -aes256

we are ready to generate the Certificate Signing Request (CSR) to submit to the "CARC R100T" for generating the certificate: the validation rules for CRL signed by an Indirect Certificate are very strict, so, in the CSR for the Indirect Certificate, you must:

  • set the value of the DN of the delegating CA as subject DN
  • make sure that the "crlissuer" attribute in the openssl.conf file matches the same DN

In this example, the delegating CA's subject DN is:

  • Country: CH
  • Organization: Carcano SA
  • Common Name: CARC R100T

so the subject DN in the CSR must be exactly the same:

openssl req -new -key ${PKI_CONF_PATH}/keys/va.key \
-out ${PKI_CONF_PATH}/certs/va.csr \
-subj "/C=CH/O=Carcano SA/CN=CARC R100T"

We can now submit the CSR to the "CARC R100T" CA and generate the certificate:

openssl ca -batch --notext -config ${PKI_CONF_PATH}/conf/openssl.conf \
-extensions crl_issuer -days 3652 -in ${PKI_CONF_PATH}/certs/va.csr \
-out ${PKI_CONF_PATH}/certs/va.crt

it obviously asks for the password to unlock the CA's private key:

Enter pass phrase for /etc/corporate-pki/carc_r100t/keys/ca.key:

then it shows a report with the most interesting information in the issued certificate.

Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'CH'
organizationName      :ASN.1 12:'Carcano SA'
commonName            :ASN.1 12:'CARC R100T'
Certificate is to be certified until May 31 15:05:49 2033 GMT (3652 days)

Write out database with 1 new entries
Data Base Updated

let's have a look to this Indirect CRL certificate:

openssl x509 -in ${PKI_CONF_PATH}/certs/va.crt -noout -text

the output is as follows:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 4096 (0x1000)
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = CH, O = Carcano SA, CN = CARC R100T
        Validity
            Not Before: Jun  1 15:05:49 2023 GMT
            Not After : May 31 15:05:49 2033 GMT
        Subject: C = CH, O = Carcano SA, CN = CARC R100T
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (521 bit)
                pub:
                    04:01:3d:a5:be:b4:a7:fe:65:2e:b8:f3:df:38:9b:
                    84:55:1c:a8:58:24:52:cb:13:79:6c:1c:d3:b6:58:
                    5e:ba:9a:a0:36:7a:c9:f7:fc:6a:ab:70:80:65:dc:
                    9c:8e:24:a6:be:7b:4d:c5:ce:2a:b9:d7:38:b3:bd:
                    02:cb:73:82:ef:69:09:00:ec:2f:df:24:7d:42:2f:
                    20:55:6f:a3:a7:40:66:01:81:d5:9b:b5:8a:62:71:
                    4c:ef:85:ca:63:9d:ac:9c:ac:84:6a:3e:25:db:e4:
                    f9:ae:5b:bc:8c:e3:51:33:b6:9a:0e:73:27:d6:3b:
                    00:6b:72:96:53:c3:28:10:53:2a:db:36:eb
                ASN1 OID: secp521r1
                NIST CURVE: P-521
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                5D:A0:6D:8A:F5:D0:5D:6D:CF:B0:1D:30:8B:EA:4F:43:15:46:9A:FA
            X509v3 Authority Key Identifier: 
                B5:26:F1:34:F7:C6:51:A7:F3:01:73:9F:28:16:8B:F3:A0:E8:9F:65
            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment, CRL Sign
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Extended Key Usage: 
                OCSP Signing
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:81:88:02:42:00:e1:9e:cf:17:a5:93:ef:b5:76:f2:a6:03:
        29:dd:25:2a:b4:ba:65:58:d8:2a:8e:78:e6:09:d0:af:ca:c6:
        68:3b:f0:cd:6c:ab:a1:32:77:93:d1:ef:29:94:d7:22:86:ed:
        9e:d1:de:ac:3a:50:63:8b:6c:b4:00:d5:97:fb:d3:d5:a1:02:
        42:01:5d:54:33:9f:05:5f:9d:31:13:21:d8:08:f0:fc:10:74:
        17:94:f9:1c:fd:02:33:1d:32:85:b1:61:15:d2:e0:a8:d8:34:
        2b:aa:1e:ce:eb:4f:7f:c1:d9:94:64:59:5e:d9:24:0c:36:09:
        91:93:cf:7e:e2:1a:48:79:26:3e:c1:fc:ab

OCSP Responder

As we saw, OCSP is a more modern and better performing way of providing information about certificates revocations.

The OpenSSL OCSP has not been designed to handle large Certificate Authorities' workloads - The openssl-ocsp man page says "The text index file format of revocation is also inefficient for large quantities of revocation data". What is shown here can be used to set up quite small corporate Certificate Authorities to be used as Labs to experiment with certificates and PKI. If you need something more reliable, consider using OpenXPKI or Cloudflare's PKI and TLS Toolkit. If you are interested into quickly learn how to set up a full-featured PKI with Cloudflare's PKI and TLS Toolkit, here are the links to the book I wrote on it: ebook and of course, paperback.

To implement an OCSP responder for the "CARC R100T CA", proceed as follows:

Create the directory where to store the OCSP responder log files:

mkdir -m 750 /var/log/corporate-pki
chown pki: /var/log/corporate-pki

set up the "ocsp" Systemd service unit template - create the "/etc/systemd/system/ocsp@.service" wit the following contents

[Unit]
Description=%i OCSP responder
After=syslog.target network-online.target

[Service]
Type=simple
User=pki
Group=pki
EnvironmentFile=/etc/sysconfig/ocsp-%i
ExecStart=/bin/openssl ocsp -text -port ${PORT} -index ${INDEX} -CA ${BUNDLE} -rkey ${KEY} -rsigner ${CERT} -out ${LOG}

[Install]
WantedBy=multi-user.target

reload Systemd to publish the new Systemd unit template:

systemctl daemon-reload

configure the "/etc/sysconfig/ocsp-carc_r100t" containing settings specific to the "CARC R100T" CA:

PORT=8889
INDEX=/var/lib/corporate-pki/carc_r100t/db/index.txt
BUNDLE=/etc/corporate-pki/ca/carc_r100t/certs/bundle.crt
KEY=/etc/corporate-pki/ca/carc_r100t/keys/va.key
CERT=/etc/corporate-pki/ca/carc_r100t/certs/va.crt
LOG=/var/log/corporate-pki/ocsp-carc_r100t.log

we can finally enable to start at boot the "CARC R100T"'s OCSP responder, starting it immediately too:

systemctl enable --now ocsp@carc_r100t

Setup The Intermediate CA

As we saw in the previous post “X509 Certificates HowTo - A Public Key Infrastructure Tutorial”, a Root Certificate Authority, besides issuing infrastructural certificates such as the ones used to sign CRLs and OCSP responses, is supposed to issue only Intermediate CA's certificates.

In this example we are creating the "CARC I100T" Intermediate Certificate Authority as follows:

  • Name: CARC I100T
  • Label: carc-i100t
  • Enterprise OID: 1234
  • Organization: Carcano SA
  • Country: CH
  • CA issuers URL: http://ca.test.carcano.local/carc_i100t.cer
  • Certificate Practice Statement URL: http://www.carcano.local/ca/cps.html
  • CRL Endpoint URL: http://www.test.carcano.local/crl/carc_i100t.crl
  • OCSP Responder URL: http://ocsp.test.carcano.local:8890

As we did with the Root CA, let's create its directory trees structures and configuration files by using the automation script we developed:

/usr/local/bin/corporate-pki-newca.sh --ca-name "CARC I100T" \
--ca-label carc_i100t --enterprise-oid 1234 --organization "Carcano SA" --country CH \
--crl-url "http://www.test.carcano.local/crl/carc_i100t.crl" \
--ocsp-url "http://ocsp.test.carcano.local:8890" \
--ca-issuers "http://ca.test.carcano.local/carc_i100t.cer" \
--cps-url "http://www.carcano.local/ca/cps.html" \
--root-ca-label carc_r100t

note how this time we also supplied the "--root-ca-label" to have the script generating an OpenSSL configuration file suitable for an Intermediate Certificate Authority.

The value of the "--root-ca-label" parameter is not actually used in this version of the script: I made it like so to ease the improvement of the script adding the capability of generating key and certificates, ... a feature I couldn't add in this post because with a full automation there wouldn't have been statements to explain and so I wouldn't have anything to explain about the process.

Sign The Intermediate CA Certificate

As we did for the Root CA's certificate, we need to generate a private key - as we did before, create  the key file and restrict access permissions:

CA_LABEL="carc_i100t"
PKI_CONF_PATH="/etc/corporate-pki/ca/${CA_LABEL}"
touch ${PKI_CONF_PATH}/keys/ca.key
chmod 400 ${PKI_CONF_PATH}/keys/ca.key
chown pki: ${PKI_CONF_PATH}/keys/ca.key

then generate the private key, overwriting the secured key file:

openssl ecparam -name secp521r1 -genkey | \
openssl ec -out ${PKI_CONF_PATH}/keys/ca.key -aes256

of course we are asked for the password we want to use to encrypt the private key.

Conversely from the Root CA's certificate, the Intermediate CA's certificate must be signed by another Certificate Authority.

For this reason we need to generate a Certificate Signing Request to submit to the "CARC R100T" Root CA.

The subject DN for this Intermediate CA is:

  • Country: CH
  • Organization: Carcano SA
  • Common Name: CARC I100T

To generate the CSR, type the following statement:

openssl req -new -key ${PKI_CONF_PATH}/keys/ca.key \
-out ${PKI_CONF_PATH}/certs/ca.csr \
-subj "/C=CH/O=Carcano SA/CN=CARC I100T"

Now, let's submit the CSR to the "CARC R100T" Root CA:

ISSUING_CA="/etc/corporate-pki/ca/carc_r100t"
openssl ca -batch --notext -config ${ISSUING_CA}/conf/openssl.conf \
-extensions intermediate_ca -days 1826 \
-in ${PKI_CONF_PATH}/certs/ca.csr \
-out ${PKI_CONF_PATH}/certs/ca.crt

let's have a look to the issued certificate:

openssl x509 -noout -text -in ${PKI_CONF_PATH}/certs/ca.crt

the output is as follows:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 4097 (0x1001)
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = CH, O = Carcano SA, CN = CARC R100T
        Validity
            Not Before: Jun  1 15:10:46 2023 GMT
            Not After : May 31 15:10:46 2028 GMT
        Subject: C = CH, O = Carcano SA, CN = CARC I100T
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (521 bit)
                pub:
                    04:01:fb:76:05:1a:41:41:e9:90:f9:a3:d0:01:bd:
                    8f:9c:3a:02:90:7a:80:ee:99:cd:7e:26:e1:a3:e7:
                    6f:c3:9f:fc:ea:d6:bf:38:6c:c7:5a:41:0c:bd:ec:
                    32:ee:6b:93:02:d7:b4:3b:4f:5b:9c:b9:d7:38:7e:
                    b3:9e:6c:f2:4f:ef:20:00:f6:c1:f6:07:e6:9b:38:
                    30:97:e2:ce:b5:e6:9d:f4:71:75:4a:70:44:86:36:
                    fc:82:a5:8a:5d:be:61:a1:1d:66:b2:f6:94:27:98:
                    48:9c:29:05:1e:a5:a6:31:4e:e6:78:81:4d:ea:49:
                    7f:fa:77:7a:a2:be:29:18:1e:c0:b8:8d:4f
                ASN1 OID: secp521r1
                NIST CURVE: P-521
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                86:9C:25:A8:77:A2:7B:B7:18:5D:1F:A9:9E:4E:AC:E6:76:17:60:DB
            X509v3 Authority Key Identifier: 
                B5:26:F1:34:F7:C6:51:A7:F3:01:73:9F:28:16:8B:F3:A0:E8:9F:65
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 CRL Distribution Points: 
                Full Name:
                  URI:http://www.test.carcano.local/crl/carc_r100t.crl                CRL Issuer:
                  DirName:C = CH, O = Carcano SA, CN = CARC R100T
            Authority Information Access: 
                OCSP - URI:http://ocsp.test.carcano.local:8889
                CA Issuers - URI:http://ca.test.carcano.local/carc_r100t.cer
            X509v3 Certificate Policies: 
                Policy: 1.3.6.1.4.1.1234.1.1
                  CPS: http://www.carcano.local/ca/cps.html
                  User Notice:
                    Explicit Text: CARC R100T Certification Practice Statement
                Policy: 1.3.6.1.4.1.1234.1.2
                  User Notice:
                    Explicit Text: CA Certificate Policy
                Policy: 2.23.140.1.1
                  User Notice:
                    Explicit Text: Certificate issued in compliance with the Extended Validation Guidelines
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:81:88:02:42:01:d9:51:72:92:a5:c0:3f:03:5e:1c:74:2c:
        db:f2:66:6c:68:82:89:c1:e7:d3:11:e2:83:c2:a2:97:29:7c:
        05:2d:65:24:d8:aa:4e:cb:93:51:e0:f1:ce:d1:72:07:2c:8f:
        fb:fa:34:d8:6f:f7:36:22:bf:3e:63:76:ce:b2:77:b9:a7:02:
        42:00:94:cb:c5:e7:26:d9:e9:dd:8c:b9:88:4e:62:7a:0b:a5:
        a9:42:2e:2d:1f:17:52:3e:20:20:bb:ed:5b:f2:81:5e:c4:88:
        9c:71:c9:b4:76:4a:06:d3:15:e7:f8:20:2f:63:81:3e:24:bf:
        b7:1f:75:b4:95:c6:83:38:ff:dc:4e:21:31
We have just generated an intermediate CA certificate: the best practice is to securely off-line storing the Root CA private key  now and bring it back online only when it comes to sign new intermediate CA certificates or renew already issued certificates. Offline storing means copying the private key to a secure offline storage (such as a PKCS#11 hardware token) and store it in a secure remote location such as a safety deposit box in a bank. As for the private key on the file system, just deleting it is not enough: you must shred it.

Generate the CA's bundle certificate file with the trust-chain for the CA's certificate:

cp ${ISSUING_CA}/certs/ca.crt ${PKI_CONF_PATH}/certs/bundle.crt
cat ${PKI_CONF_PATH}/certs/ca.crt >> ${PKI_CONF_PATH}/certs/bundle.crt

Validation Authority

It's now time to se up the CARC I100T  Validation Authority.

Indirect CRL Certificate

Let's generate the private key of the certificate we want to delegate for signing Certificate Revocation Lists (CRLs) - as we did before, generate and restrict access to the private key file:

touch ${PKI_CONF_PATH}/keys/va.key
chmod 400 ${PKI_CONF_PATH}/keys/va.key
chown pki: ${PKI_CONF_PATH}/keys/va.key

then generate the private key overwriting the empty file:

openssl ecparam -name secp521r1 -genkey | \
openssl ec -out ${PKI_CONF_PATH}/keys/va.key -aes256

we are ready to generate the Certificate Signing Request (CSR) to submit to the "CARC I100T" for generating the certificate.

The attributes are:

  • Country: CH
  • Organization: Carcano SA
  • Common Name: CARC I100T
openssl req -new -key ${PKI_CONF_PATH}/keys/va.key \
-out ${PKI_CONF_PATH}/certs/va.csr \
-subj "/C=CH/O=Carcano SA/CN=CARC I100T"

We can now submit the CSR to the CARC I100T CA and generate the certificate:

openssl ca -batch --notext -config ${PKI_CONF_PATH}/conf/openssl.conf \
-extensions crl_issuer -days 1826 \
-in ${PKI_CONF_PATH}/certs/va.csr -out ${PKI_CONF_PATH}/certs/va.crt

it obviously asks us for the password to unlock CARC I100T's private key:

Enter pass phrase for /etc/corporate-pki/carc_i100t/keys/ca.key:

then it shows a report with the most interesting information in the issued certificate.

OCSP Responder

As we saw, OCSP is a more modern and better performing way of providing information about certificates revocations.

It is enough to configure a new instance of the "ocsp" systemd unit:

Configure the "/etc/sysconfig/ocsp-carc_i100t" containing settings specific to the "CARC I100T" CA

PORT=8890
INDEX=/var/lib/corporate-pki/carc_i100t/db/index.txt
BUNDLE=/etc/corporate-pki/ca/carc_i100t/certs/bundle.crt
KEY=/etc/corporate-pki/ca/carc_i100t/keys/va.key
CERT=/etc/corporate-pki/ca/carc_i100t/certs/va.crt
LOG=/var/log/corporate-pki/ocsp-carc_i100t.log

we can finally enable to start at boot the "CARC I100T"'s OCSP responder, starting it immediately too:

systemctl enable --now ocsp@carc_i100t

Entity Certificates

We are finally ready to request the issuing of entity certificates - we are going to request an Extended Validated Certificate

Generate the private key:

openssl ecparam -name secp384r1 -genkey \
| openssl ec -out /tmp/foo.key -aes256

then, generate the CSR - in this example we are requesting the issuing a certificate with the following attributes:

  • Country: CH
  • Organization: Carcano SA
  • Common Name: foor.org
  • Subject Alternatives Names:
    • foo.org
    • www.foo.org
    • mail.foo.org
    • 10.21.34.52
openssl req -new -key /tmp/foo.key -out /tmp/foo.csr \
-subj "/C=CH/O=Carcano SA/CN=foo.org" \
-addext "subjectAltName = DNS:foo.org, DNS:www.foo.org, DNS:mail.foo.org, IP:10.21.34.52"

submit the CSR to the CARC I100T CA to get the signed certificate - in this example it is valid for 397 days:

openssl ca -batch --notext -config ${PKI_CONF_PATH}/conf/openssl.conf \
-extensions ev -days 397 -in /tmp/foo.csr -out /tmp/foo.crt

let's have a closer look to it:

openssl x509 -noout -text -in /tmp/foo.crt 

it must look like as follows:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 4097 (0x1001)
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = CH, O = Carcano SA, CN = CARC I100T
        Validity
            Not Before: Jun  1 15:14:22 2023 GMT
            Not After : Jul  2 15:14:22 2024 GMT
        Subject: C = CH, O = Carcano SA, CN = foo.org
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:be:7e:32:4e:40:9f:13:9a:45:17:cc:62:b3:ca:
                    01:c2:24:d6:0e:08:1a:6f:14:2a:a2:73:97:50:6e:
                    f3:7a:a0:af:57:76:63:17:16:2d:7c:b7:9d:07:88:
                    6d:34:bb:96:b8:ee:2d:0c:2e:76:05:d4:5e:b7:a6:
                    87:1d:5d:d4:60:48:63:79:83:eb:b4:c7:13:6a:b0:
                    ed:95:ad:38:9c:cd:41:49:ba:f0:4f:21:64:a2:30:
                    0f:96:8c:f3:58:13:30
                ASN1 OID: secp384r1
                NIST CURVE: P-384
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                75:15:D7:14:DC:06:98:E2:1F:12:92:8D:F0:0C:EB:7F:D2:61:56:37
            X509v3 Authority Key Identifier: 
                86:9C:25:A8:77:A2:7B:B7:18:5D:1F:A9:9E:4E:AC:E6:76:17:60:DB
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: critical
                TLS Web Server Authentication
            X509v3 CRL Distribution Points: 
                Full Name:
                  URI:http://www.test.carcano.local/crl/carc_i100t.crl                CRL Issuer:
                  DirName:C = CH, O = Carcano SA, CN = CARC I100T
            Authority Information Access: 
                OCSP - URI:http://ocsp.test.carcano.local:8890
                CA Issuers - URI:http://ca.test.carcano.local/carc_i100t.cer
            X509v3 Certificate Policies: 
                Policy: 1.3.6.1.4.1.1234.1.1
                  CPS: http://www.carcano.local/ca/cps.html
                  User Notice:
                    Explicit Text: CARC I100T Certification Practice Statement
                Policy: 2.23.140.1.1
                  User Notice:
                    Explicit Text: Certificate issued in compliance with the Extended Validation Guidelines
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:81:88:02:42:01:fd:9e:91:fb:42:67:6a:68:1e:c2:c9:67:
        2e:99:6b:aa:b0:9e:8b:10:91:fd:1f:d5:aa:8a:22:22:c8:ac:
        ab:51:91:55:d7:da:45:42:d1:e3:1c:a7:53:8c:cd:aa:39:64:
        9f:e8:38:05:55:3b:c5:57:fa:f3:d2:ef:28:9a:9e:07:88:02:
        42:01:b8:75:f8:18:d3:d9:34:a1:76:4a:a6:02:d4:e8:18:fa:
        8c:2e:58:25:fd:ea:03:99:3c:a8:dd:ba:15:57:d4:40:16:91:
        0b:13:1e:46:95:ed:e1:8a:c2:7f:8b:75:9f:6f:e8:e9:ee:ff:
        e5:3d:02:09:c4:aa:d2:c6:17:49:96:4d:bc

Renewing The Certificate Authority's Certificate

Renewing the Certificate Authorities' certificates is a very delicate task: in the ideal world, you must avoid issuing entity certificates that expires after the expiration date of the CA's certificate itself: in such scenario everything is simple - you simply create the new CA's certificates and deliver them to the trust-stores of your systems.

In the real world, ... often things are not tidy at all - as every IT professionals know, very often you have to administer infrastructures designed and maintained by who was here before, ... that maybe had the very bad habit of designing and delivering quick and dirty (quick and dirty is good only during emergency fixes, otherwise you are just accumulating technical debts), or that improvise on everything claiming they are doing thing the Agile way - by the way, have a look to my post on Agile, SCRUM and Lean the proper way.

Reissuing Existing CA's Certificates

Reissuing the exiting CA's certificate's is necessary when you have leaf certificates that expires after the expiration date of the CA's certificate.

Root CA

You must issue a new certificate that matches the current one, but with a new expiration date.

This means that you need to:

  • use the same private key,
  • specify the same subject string
  • add the same X509v3 extensions

Let's start by renaming the current certificate so that we can easily rollback if necessary.

CA_LABEL="carc_r100t"
PKI_CONF_PATH="/etc/corporate-pki/ca/${CA_LABEL}"
mv ${PKI_CONF_PATH}/certs/ca.crt ${PKI_CONF_PATH}/certs/ca.crt.bak

If you don't have the Certificate Signing Request (CSR) you used to create the current certificate, re-create it as follows, otherwise skip this step.

openssl req -new -key ${PKI_CONF_PATH}/keys/ca.key \
-out ${PKI_CONF_PATH}/certs/ca.csr \
-subj "/C=CH/O=Carcano SA/CN=CARC R100T"

Mind to specify the same subject string of the current certificate - an alternate way is to run the following statement:

openssl x509 -x509toreq -in ${PKI_CONF_PATH}/certs/ca.crt.bak \
-signkey ${PKI_CONF_PATH}/keys/ca.key \
-out ${PKI_CONF_PATH}/certs/ca.csr

Generate the new certificate as follows:

openssl x509 -req -extfile ${PKI_CONF_PATH}/conf/openssl.conf \
-extensions root_ca -days 3652 \
-key ${PKI_CONF_PATH}/keys/ca.key \
-in ${PKI_CONF_PATH}/certs/ca.csr \
-out ${PKI_CONF_PATH}/certs/ca.crt

generate the CA bundle with the Root CA's certificate:

cp ${PKI_CONF_PATH}/certs/ca.crt ${PKI_CONF_PATH}/certs/bundle.crt

It is critical to verify that the new certificate can be used to validate a certificate that was signed using the old one - here we verify that the new certificate can be used to validate the "CARC I100T" intermediate CA's certificate:

openssl verify -CAfile ${PKI_CONF_PATH}/certs/bundle.crt /etc/corporate-pki/ca/carc_i100t/certs/ca.crt

if everything is properly working, you must get the following message:

/etc/corporate-pki/ca/carc_i100t/certs/ca.crt: OK

otherwise, if you get the following message:

C = CH, O = Carcano SA, CN = CARC I100T
error 31 at 0 depth lookup: authority and issuer serial number mismatch
error /etc/corporate-pki/ca/carc_i100t/certs/ca.crt: verification failed

you are very out of luck: it means that the certificate you want to verify in the Authority Key Identifier x509v3 extension has a serial number that does not match with the one of the CA's new certificate (this happens when it is specified "issuer" among the values of the authorityKeyIdentifier x509v3 extension).

In such a scenario, ... you have no other way out of re-do everything from scratch, reissuing all the intermediate and entity certificates, and of course delivering them to their consumer.

Intermediate CA

When it comes to re-issue intermediate CA's certificates, you can exploit cross-signing: simply put, you re-issue the current intermediate certificate submitting it to another trusted Certificate Authority that expires later than the entity certificates already issued by the intermediate CA's current certificate.

If you don't have such a Root CA to exploit for this, you just have to create a new one and deliver its certificate to the TrustStore of your systems, along with the newly issued intermediate CA's certificate.

In this example, we create the following new Root CA:

  • Name: CARC R101T
  • Label: carc-r101t
  • Enterprise OID: 1234
  • Organization: Carcano SA
  • Country: CH
  • CA issuers URL: http://ca.test.carcano.local/carc_r101t.cer
  • Certificate Practice Statement URL: http://www.carcano.local/ca/cps.html
  • CRL Endpoint URL: http://www.test.carcano.local/crl/carc_r101t.crl
  • OCSP Responder URL: http://ocsp.test.carcano.local:8888

Let's create its directory tree and configuration files using the automation script we developed:

/usr/local/bin/corporate-pki-newca.sh --ca-name "CARC R101T" \
--ca-label carc_r101t --enterprise-oid 1234 --organization "Carcano SA" --country CH \
--crl-url "http://www.test.carcano.local/crl/carc_r101t.crl" \
--ocsp-url "http://ocsp.test.carcano.local:8888" \
--ca-issuers "http://ca.test.carcano.local/carc_r101t.cer" \
--cps-url "http://www.carcano.local/ca/cps.html"

As we already did, we generate the key as an empty file and immediately restrict access to it:

CA_LABEL="carc_r101t"
PKI_CONF_PATH="/etc/corporate-pki/ca/${CA_LABEL}"
touch ${PKI_CONF_PATH}/keys/ca.key
chmod 400 ${PKI_CONF_PATH}/keys/ca.key
chown pki: ${PKI_CONF_PATH}/keys/ca.key

then generate the private key overwriting the empty file:

openssl ecparam -name secp521r1 -genkey | \
openssl ec -out ${PKI_CONF_PATH}/keys/ca.key -aes256

as usual we are asked for the password we want to use to encrypt the private key.

Create the Certificate Signing Request (CSR) with the following data:

  • Certificate Lifetime: 3652 days (10 years)
  • Country: CH
  • Organization: Carcano SA
  • Common Name: CARC R101T
openssl req -new -key ${PKI_CONF_PATH}/keys/ca.key \
-out ${PKI_CONF_PATH}/certs/ca.csr \
-subj "/C=CH/O=Carcano SA/CN=CARC R101T"

Then, we generate the CA Certificate from the CSR, signing it with the same key used for the CSR:

openssl x509 -req -extfile ${PKI_CONF_PATH}/conf/openssl.conf \
-extensions root_ca -days 3652 \
-key ${PKI_CONF_PATH}/keys/ca.key \
-in ${PKI_CONF_PATH}/certs/ca.csr \
-out ${PKI_CONF_PATH}/certs/ca.crt
Complete the task creating the Validation Authority with the CRL and OCSP Responder - I won't explicitly the steps here since we have thoroughly explained this topic previously.

we are finally ready to re-issue the current "CARC I100T" intermediate CA's certificate.

As a safety measure, we make a backup copy of the current certificate:.

CA_LABEL="carc_i100t"
PKI_CONF_PATH="/etc/corporate-pki/ca/${CA_LABEL}"
mv ${PKI_CONF_PATH}/certs/ca.crt ${PKI_CONF_PATH}/certs/ca.crt.bak
mv ${PKI_CONF_PATH}/certs/bundle.crt ${PKI_CONF_PATH}/certs/bundle.crt.bak

Then, if you don't already have it, re-generate the CSR with the following statement:

openssl x509 -x509toreq -in ${PKI_CONF_PATH}/certs/ca.crt.bak -signkey ${PKI_CONF_PATH}/keys/ca.key \ -out ${PKI_CONF_PATH}/certs/ca.csr

Then, submit the CSR to the "CARC R101T" Root CA:

ISSUING_CA="/etc/corporate-pki/ca/carc_r101t"
openssl ca --notext -batch -config ${ISSUING_CA}/conf/openssl.conf \
-extensions intermediate_ca -days 1826 \
-in ${PKI_CONF_PATH}/certs/ca.csr \
-out ${PKI_CONF_PATH}/certs/ca.crt

generate a new CA bundle with the Root CA's and the new Intermediate CA's certificates:

cp ${PKI_CONF_PATH}/certs/ca.crt ${PKI_CONF_PATH}/certs/bundle.crt
cat ${ISSUING_CA}/certs/ca.crt >> ${PKI_CONF_PATH}/certs/bundle.crt

Again, it is critical to verify that the new certificate can be used to validate a certificate that was signed using the old one - here we verify that the new intermediate certificate can be used to validate the certificate we issued before for "foo.org":

openssl verify -CAfile ${PKI_CONF_PATH}/certs/bundle.crt /tmp/foo.crt

if everything is properly working, you must get the following message:

/tmp/foo.crt: OK
I saw a lot of tutorials claiming that is is best to bundle servers' certificates along with the issuing CA's certificate, and then configure the application to use it for the TLS endpoint - This is not true: it is necessary to bundle them only if for any reason the intermediate certificate can not distributed to every client accessing that service. Anyway, the tradeoff is that this kind of setup prevents cross-signing. For example, a web server providing not only the server's certificate, but also the Intermediate CA's certificate that signed it, is actually forcing a trust-path to the Root CA Certificate: this causes any new cross-signed Intermediate CA's certificate to be considered untrusted, and so not valid.
Public Key Infrastructure is not the only way to deal with trusting digital certificates - PGP uses the web of trust for creating indirect trust with different trust levels - If you are interested in this topic, don't miss "A quick, easy yet comprehensive GPG tutorial".

Footnotes

Here it ends this post dedicated to how to set-up a full-featured Public Key Infrastructure with OpenSSL: we made a complex Lab following the best practices for PKI and certificates, setting up indirect CRLs and OCSP responders. In the "Indirect CRL generation, CRL validation and OCSP validation" post , we see how to generate the indirect CRL and validate the certificate's revocation status using the CRL and the OCSP responders.

I hate blogs with pop-ups, ads and all the (even worse) other stuff that distracts from the topics you're reading and violates your privacy. I want to offer my readers the best experience possible for free, ... but please be wary that for me it's not really free: on top of the raw costs of running the blog, I usually spend on average 50-60 hours writing each post. I offer all this for free because I think it's nice to help people, but if you think something in this blog has helped you professionally and you want to give concrete support, your contribution is very much appreciated: you can just use the above button.

7 thoughts on “OpenSSL CA tutorial – A full-featured openssl PKI

    • Hi Eric, first of all please forgive me for publishing your comment with such a delay – I’m coping with a lot of spam comments and sometimes lecit comments are wrongly automatically classified as spam. Easy RSA is certainly a nice tool that can help and speed-up things. I didn’t mention it only because the post is about making PKI and CA using openssl. Anyway thanks for mentioning it.

  1. Jim S. Smith says:

    Holy smokes!

    And I thought I had a tough time trying to design a good PKI/CA system!

    With most of my scripting being done in PHP, I guess I have a LOT of work to do?

    Anyway,

    This has probably been one of the most comprehensive write-ups I have seen posted on the subject of OpenSSL and PKI’s in general.

    I am in the process of doing a complete rewrite of my “PHP solution”. I wanted to create a PKI system that involves the use of “certificate chains” too.

    I did check out OpenXPKI, but found that there was no version available for Debian Bullseye?! They had versions for “Jessie”, “Buster”, and “Bookworm” ( that is: Versions “8”, “10”, and “12” of Debian ).

    So I had to look elsewhere for an example solution.

    Anyway,

    My “fork” of a previous project – is posted here: https://codeberg.org/AFWS/Open-PKI-Admin

    ( It is still a WIP, BTW. )

    I’ll try this solution you presented here, sometime.

    GREAT WORK! 🙂

    – Jim S.

    • Marco Antonio Carcano says:

      Hello Jim,
      glad to know you liked the post, and even more happy to know you are working on a PKI Web UI.
      My two pences: for a while forget the PKI/certificate suite to use, and focus on real life use cases – design a framework, so that you will provide a very user friendly and effective product – I’m more than happy to contribute on that for free if you want to, bringing some practical experience – just privately contact me via Linkedin.
      Then forget openssl, … they themselves say it is not designed for production systems – they did it just as a swiss-knife or as a tool for proof of concepts: my advice is to use Cloudflare’s PKI And TLS Toolkit, since it supports database backends and, besides supporting OCSP and CRL, provides also online endpoints for automatic certificate enrollment, and a tool for automatic monitoring and renewals of expiring certificates.
      The cherry on the top would be to integrate everything with Cloudflare’s Red October. You may find useful the book I wrote “A full-featured PKI with Cloudflare’s PKI and TLS Toolkit” – since it provides a nearly real life full featured lab you may use as reference to derive your model – the PDF version is really cheap by the way.
      Cheers.

      • Jim S. Smith says:

        Unfortunately,

        Cloudflare’s product must be compiled in “GO”. I don’t know a thing about Go.

        I was looking at the idea of having a decent web-based tool – especially for my own uses.

        I design and/or refine a lot of my own development tools, because this is also how I learn to do things even better.

        Yes. I agree that OpenSSL is mainly a “toolbox” (an apropos description of it), but having a decent “engine” to more effectively use that toolbox is what I look for.

        This idea of a “home PKI engine” – was intended to use in a “corporate intranet” (as I have stated in the documentation I wrote for it).

        The project I posted, was basically a re-write of an old project. I needed to have a fully-working engine – so as to have a foundation on which to create my own project from it. I wasn’t too thrilled that PHP’s own OpenSSL functions were rather limited (as I found out when working with PHPCA). Therefore, PHPKI seemed like a great start towards a more complete project.

        I also wanted to automate some of the tasks in the initial setup, like: generating a CRL-signing cert, the first user’s own intermediate cert, and similar.

        Still working on finding the best uses of the various OpenSSL configuration sections and “extensions”.

        Anyway,

        I appreciate the feedback. It’s always good to see other ideas and methods offered.

        I may even be ambitious enough to port a Python version? – I kind of like Python for a lot of things too.

        • Marco Antonio Carcano says:

          Hello Jim,
          of course CFSSL is written in Go, but it is available pre-built for the most common architectures: just look at this page – scroll down until you find the Assets.
          FYI: CFSSL is a dual personality binary application you can run both as a command line tool as well as a service. Don’t forget to have a go also with certmgr: if you want to see it in action, have a look at my post “Cloudflare’s certmgr tutorial – a certmgr howto“.
          Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>