PKI OpenSSL (pt 2)

OpenSSL PKI (part 2) - IntermediateCA

Creating an IntermediateCA

Once again, the intermediateCA(s) must be hosted in a separate system (server) from that which hosts the RootCA, and the RootCA’s system (root) should be airgapped and offline.
In the online system I don’t think it’s a bad idea to keep the secondary CAs under /root/ca, which allows the placing the RootCA’s certificate on the same path (/root/ca/certs/ca.cert.pem). However, this is the only information from the RootCA that needs to be brought to the online world where the secondary CAs reside; it is even debatable if it is worth bringing the RootCA’s CRL in PEM format and putting it in /root/ca/crl/ca.crl.pem.

1
2
3
4
5
mkdir -p /root/ca/certs
cp ca.cert.pem /root/ca/certs/
mkdir /root/ca/crl
cp ca.crl.pem /root/ca/crl/
mkdir /root/ca/csr

Create the complete folder tree that is needed to support the IntermediateCA’s operations:

1
2
3
4
5
6
inter="/root/ca/inter-ca"
mkdir $inter/{certs,crl,csr,newcerts,private}
chmod 700 $inter/private
touch $inter/index.txt
echo 1000 > $inter/serial
echo 1000 > $inter/crlnumber

Create the configuration file for the InterCA:

1
cat > $inter/openssl.cnf

/root/ca/inter-ca/openssl.cnf

# OpenSSL intermediate CA configuration file.
# Copy to '/root/ca/inter-ca/openssl.cnf'.

[ ca ]
# 'man ca'
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = /root/ca/inter-ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key       = $dir/private/inter-ca.key.pem
certificate       = $dir/certs/inter-ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/inter-ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha512

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

# copy SAN, OCSP and other stuff to downstream certificates
copy_extensions = copy

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of 'man ca'.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the 'ca' man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the 'req' tool ('man req').
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha512

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = PT
stateOrProvinceName_default     = Lisboa
localityName_default            = Lisboa
0.organizationName_default      = TiagoJoaoSilva
organizationalUnitName_default  = TJS
emailAddress_default            = bofh@tjs.lan

[ v3_ca ]
# Extensions for a typical CA ('man x509v3_config').
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA ('man x509v3_config').
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs ('man x509v3_config').
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates ('man ocsp').
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning


In an ideal world, there would be several subCAs, named by the use to which they would be intended (usually with different extension sets for each use):

  • tls-ca: Certificates for servers and clients
  • vpn-ca: Certificates for VPNs
  • mime-ca: Certificates for email
  • code-ca: Certificates for code signing
  • net-ca: Certificates for network assets
    The configuration of these extensions is outside the scope of this document. But each of them would have their own folder tree and their copy of the openssl.cnf, to which I would give a more specific name in each case.
    Actually, it seems that almost no one bothers to do all this; there’s usually a separate CA for external VPNs, if the company develops software there’s a CA for code-signing, and then a single CA for all internal systems.
    To avoid having to create separate configuration files for each certificate that re-specify all the functionalities that are common to the InterCA, we add the following to /root/ca/inter-ca/openssl.cnf:
[ CA_default ]
(…)
# copy extensions missing from the CSR to the final certificate
copy_extensions = copy

Note that since this IntermediateCA is more dynamic than the RootCA, its CRL’s validity should not be greater than 30 days; I’d advise you to set up a periodic task that refreshes the CRL every 29 days.

default_crl_days  = 30

Generate a complex password for the InterCA:

1
2
openssl rand -base64 12 > $inter/private/inter-ca.key.pass
chmod 400 $inter/private/inter-ca.key.pass

We create the InterCA’s key providing the complex password:

1
2
openssl genrsa -aes256 -passout file:$inter/private/inter-ca.key.pass \
    -out $inter/private/inter-ca.key.pem 8192

Generating RSA private key, 8192 bit long modulus (2 primes)
......................................................................................................................................................................................................+++
.....................................................................................................................................................................................................+++
e is 65537 (0x010001)

1
chmod 400 $inter-ca/private/inter-ca.key.pem

Create the Certificate Signing Request that will be signed by the RootCA (notice that it is placed on the csr folder of the RootCA):

1
2
3
4
openssl req -config $inter/openssl.cnf -new -sha512 \
	-passin file:$inter/private/inter-ca.key.pass \
    -key $inter/private/inter-ca.key.pem \
    -out /root/ca/csr/inter-ca.csr.pem

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [PT]:
State or Province Name [Lisboa]:
Locality Name [Lisboa]:
Organization Name [TiagoJoaoSilva]:
Organizational Unit Name [TJS]:
Common Name []:**inter-ca-tjs-2**
Email Address [bofh@tjs.lan]

Sign the CSR with the RootCA

Go back to the offline system of the RootCA and add some folder for the IntermediateCA.
As it will only support the creation of the InterCA(s) and not its operation, it is much simpler:

1
2
3
inter="/root/ca/inter-ca"
mkdir $inter/certs
chmod 740 $inter/certs

Bring in from the online system the IntermediateCA’s CSR, which we place on the same path where it was (the RootCA’s csr folder):

1
cp inter-ca.csr.pem /root/ca/csr

To make sure that the CDL and/or AIA information of the RootCA is passed to the IntermediateCA, we add this directive to the [ CA_default ] section on /root/ca/openssl.cnf

# copy extensions missing from the CSR to the final certificate
copy_extensions = copy

Sign the CSR; notice that we specify the extension from /root/ca/openssl.cnf to be used is [ v3_intermediate_ca ]!

1
2
3
4
5
openssl ca -config /root/ca/openssl.cnf -extensions v3_intermediate_ca \
    -days 3650 -notext -md sha512 \
    -passin file:/root/ca/private/ca.key.pass \
    -in /root/ca/csr/inter-ca.csr.pem \
    -out $inter/certs/inter-ca.cert.pem


Using configuration from /root/ca/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 4099 (0x1003)
        Validity
            Not Before: Aug 13 23:44:07 2021 GMT
            Not After : Aug 11 23:44:07 2031 GMT
        Subject:
            countryName               = PT
            stateOrProvinceName       = Lisboa
            organizationName          = TiagoJoaoSilva
            organizationalUnitName    = TJS
            commonName                = inter-ca-tjs-2
            emailAddress              = bofh@tjs.lan
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                E3:75:25:D7:74:08:0B:23:F0:AF:E4:EC:D0:D4:52:CB:5A:3A:10:2E
            X509v3 Authority Key Identifier:
                keyid:01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0

            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
            X509v3 CRL Distribution Points:

                Full Name:
                  URI:http://server.tjs.lan:7788/root-ca.crl

Certificate is to be certified until Aug 11 23:44:07 2031 GMT (3650 days)
Sign the certificate? [y/n]:Y

1 out of 1 certificate requests certified, commit? [y/n]Y
Write out database with 1 new entries
Data Base Updated


We check the signing:

1
openssl verify -CAfile /root/ca/certs/ca.cert.pem $inter/certs/inter-ca.cert.pem


/root/ca/inter-ca/certs/inter-ca.cert.pem: OK


Create a certificate for the Chain of Authority with a Chain certificate (also called Bundle), by concatenating the certificates in ascending order - the latter will be the RootCA or the nearest to it.

1
2
3
cat $inter/certs/inter-ca.cert.pem /root/ca/certs/ca.cert.pem > \
    $inter/certs/inter-ca_chain.cert.pem
chmod 444 $inter/certs/inter-ca_chain.cert.pem

Activate the IntermediateCA on the online system

Since we left on the online system all the things the IntermediateCA needs to do its job, we only have to bring the certificates signed by the RootCA to their proper place (using safe methods); I didn’t find any guidance whether the IntermediateCA’s certificates should be placed in /root/ca/certs (as it would be logical to do), so I’ll place them in /root/ca/inter-ca/certs

1
cp inter-ca.cert.pem inter-ca_chain.cert.pem $inter/certs

Revoking IntermediateCA certificates

As the number of certificates to issue (or revoke) by the IntermediateCA will be much larger than the RootCA’s, it’s better to use the OCSP Responder method (also called AIA because of the directive AuthorityInfoAccess that sets it up), to check for certificate revocation.

Setting up the OCSP Responder’s address on the InterCA’s CNF

We start by putting the following in the [server_cert] and [usr_cert] sections of /root/ca/inter-ca/openssl.cnf

authorityInfoAccess = OCSP;URI:http://ocsp.tjs.lan:7888

Create the certificate

Generate a key:

1
openssl genrsa -out /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem 4096


Generating RSA private key, 4096 bit long modulus (2 primes)
...............................++++
................................................++++
e is 65537 (0x010001)


Insert the OCSP-specific SAN (Subject Alternative Name) informations on another .CNF:

1
cat > /root/ca/inter-ca/san/ocsp.tjs.lan-san.cnf
[req]
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no

[req_distinguished_name]
C   = PT
ST  = Lisboa
L   = Lisboa
O   = Tiago Joao Silva
OU  = TJS
CN  = ocsp.tjs.lan
[req_ext]
subjectAltName = @alt_names

[alt_names]
IP.1 = 192.168.1.70
DNS.1 = ocsp.tjs.lan 

Build the CSR with that SAN info:

1
2
3
openssl req -config /root/ca/inter-ca/san/ocsp.tjs.lan-san.cnf -new -sha512 \
      -key /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem \
      -out /root/ca/inter-ca/csr/ocsp.tjs.lan.csr.pem

Confirm the CSR has the SAN info:

1
2
openssl req -noout -text -in /root/ca/inter-ca/csr/ocsp.tjs.lan.csr.pem | \
    grep -A 1 "Subject Alternative Name"


            X509v3 Subject Alternative Name: 
                IP Address:192.168.1.70, DNS:ocsp.tjs.lan


Sign the CSR with -extensions ocsp:

1
2
3
4
5
openssl ca -config /root/ca/inter-ca/openssl.cnf \
    -extensions ocsp -days 823 -notext -md sha512 \
    -passin file:/root/ca/inter-ca/private/inter-ca.key.pass \
    -in /root/ca/inter-ca/csr/ocsp.tjs.lan.csr.pem \
    -out /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem


Using configuration from /root/ca/inter-ca/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 4099 (0x1003)
        Validity
            Not Before: Aug 22 22:53:56 2021 GMT
            Not After : Nov 23 22:53:56 2023 GMT
        Subject:
            countryName               = PT
            stateOrProvinceName       = Lisboa
            localityName              = Lisboa
            organizationName          = Tiago Joao Silva
            organizationalUnitName    = TJS
            commonName                = ocsp.tjs.lan
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Subject Key Identifier:
                BD:A6:EE:6D:0F:73:0E:47:DC:26:9A:A8:B0:40:18:55:DB:35:6B:5A
            X509v3 Authority Key Identifier:
                keyid:E3:75:25:D7:74:08:0B:23:F0:AF:E4:EC:D0:D4:52:CB:5A:3A:10:2E

            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: critical
                OCSP Signing
Certificate is to be certified until Nov 23 22:53:56 2023 GMT (823 days)
Sign the certificate? [y/n]:Y

1 out of 1 certificate requests certified, commit? [y/n]Y
Write out database with 1 new entries
Data Base Updated


Confirm the certificate has the OCSP extension info:

1
2
openssl x509 -noout -text -in /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem | \
    grep -A1 X509v3

        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Subject Key Identifier:
                BD:A6:EE:6D:0F:73:0E:47:DC:26:9A:A8:B0:40:18:55:DB:35:6B:5A
            X509v3 Authority Key Identifier:
                keyid:E3:75:25:D7:74:08:0B:23:F0:AF:E4:EC:D0:D4:52:CB:5A:3A:10:2E
--
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: critical
                OCSP Signing

1
chmod 400 $inter/certs/ocsp.tjs.lan.cert.pem 

Use OpenSSL to create a testing OCSP Responder (do not use in production)

For testing, we’ll activate OpenSSL’s in-built OCSP Responder temporarily:

  • we’ll use port 7888, one of the previously known free ports (see Part 1)
  • with the-nrequest 1 option, it wil close itself after a single query
  • and we’ll gather a log.
1
2
3
4
5
6
openssl ocsp -index /root/ca/inter-ca/index.txt -port 7888 -text \
    -CA /root/ca/inter-ca/certs/inter-ca_chain.cert.pem \
    -rkey /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem \
    -rsigner /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem \
    -out /root/ca/inter-ca/ocsp-log.txt \
    -nrequest 1 & 


ocsp: waiting for OCSP client connections...


Open a port on the firewall

1
ufw allow 7888

Query the Responder about a certificate (the Responder’s own, or another):

1
2
3
4
openssl ocsp -CAfile /root/ca/inter-ca/certs/ca-chain.cert.pem \
      -url http://127.0.0.1:7888 -resp_text \
      -issuer /root/ca/inter-ca/certs/inter-ca.cert.pem \
      -cert /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem


[…]
Response verify OK
/root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem: good
        This Update: Aug 22 23:06:21 2021 GMT
[1]+  Done                    openssl ocsp -index /root/ca/inter-ca/index.txt -port 7888 -text -CA /root/ca/inter-ca/certs/inter-ca_chain.cert.pem -rkey /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem -rsigner /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem -out /root/ca/inter-ca/ocsp-log.txt -crl_check_all -nrequest 1


Examine the log to check what’s happened:

1
head -30 /root/ca/inter-ca/ocsp-log.txt

OCSP Request Data:
    Version: 1 (0x0)
    Requestor List:
        Certificate ID:
          Hash Algorithm: sha1
          Issuer Name Hash: AE59C598DC56A005EA5239AE471093B7924F5018
          Issuer Key Hash: E37525D774080B23F0AFE4ECD0D452CB5A3A102E
          Serial Number: 1003
    Request Extensions:
        OCSP Nonce:
            04104CA2F5C63FA8F6A8D89256935DB37177
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    Version: 1 (0x0)
    Responder Id: C = PT, ST = Lisboa, L = Lisboa, O = Tiago Joao Silva, OU = TJS, CN = ocsp.tjs.lan
    Produced At: Aug 22 23:06:21 2021 GMT
    Responses:
    Certificate ID:
      Hash Algorithm: sha1
      Issuer Name Hash: AE59C598DC56A005EA5239AE471093B7924F5018
      Issuer Key Hash: E37525D774080B23F0AFE4ECD0D452CB5A3A102E
      Serial Number: 1003
    Cert Status: good
    This Update: Aug 22 23:06:21 2021 GMT

    Response Extensions:
        OCSP Nonce:
            04104CA2F5C63FA8F6A8D89256935DB37177
    Signature Algorithm: sha256WithRSAEncryption

Set the OCSP Responder up as a permanent service

If your distribution uses systemd, create a Service Unit (other inits have their own ways)

1
cat > /etc/systemd/system/ocsp-responder_inter-ca.service
[Unit]
Description = OCSP responder using OpenSSL (for CA inter-ca)
After = network.target

[Service]
ExecStart = /usr/bin/openssl ocsp \
-index /root/ca/inter-ca/index.txt \
-port 7888 \
-rkey /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem \
-rsigner /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem \
-CA /root/ca/inter-ca/certs/inter-ca_chain.cert.pem \
-text -crl_check_all\
-out /root/ca/inter-ca/ocsp-log.txt

[Install]
WantedBy = multi-user.target
1
systemctl daemon-reload

Test the service:

1
2
systemctl start ocsp-responder_inter-ca
systemctl status ocsp-responder_inter-ca

● ocsp-responder_inter-ca.service - OCSP responder using OpenSSL (for CA inter-ca)
     Loaded: loaded (/etc/systemd/system/ocsp-responder_inter-ca.service; disabled; vendor preset: enabled)
     Active: active (running) since Mon 2021-08-23 00:21:27 WEST; 22s ago
[…]
Aug 23 00:21:27 debian systemd[1]: Started OCSP responder using OpenSSL (for CA inter-ca).
Aug 23 00:21:27 debian openssl[2585]: ocsp: waiting for OCSP client connections...

Query the service:

1
2
3
4
openssl ocsp -CAfile /root/ca/inter-ca/certs/ca-chain.cert.pem \
      -url http://127.0.0.1:7888 -resp_text \
      -issuer /root/ca/inter-ca/certs/inter-ca.cert.pem \
      -cert /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem

Confirm that the answer is identical to the previously done test. Then leave the service as permanent:

1
systemctl enable ocsp-responder_inter-ca

Other CAs

If this is not the only IntermediateCA, others could be set up following exactly the same steps, and just changing the listening port (they’re free up to 7899/TCP, so 10 more potential ports).
However, it is not recommended to use OpenSSL as an OCSP Responder, because the implementation is barebone.