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.
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
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 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"
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:
[ 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
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.
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.
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.
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
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.
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
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
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.
Eric says:
I’m surprised you didn’t at least mention EasyRSA
grimoire_hw63hb says:
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.
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