Website Security Solutions | Latest Guides | Blog

In our last article, we have covered getting started with the NitroKey HSM. Today we will go through the process of setting up an entire internal PKI backed by the security guarantee the HSM provides. First, we will generate a root CA with a private key living on the HSM’s hardware. Then, we will generate an Intermediate CA, whose private key will live secured by file system permissions in Linux. Then, we will walk through an example of generating a client certificate for use on internal endpoints. The root certificate can be distributed to the root certificate store of managed endpoints, and the machine used to generate the root CA’s private key can subsequently be kept offline.



Requirements

Before getting started, make sure to have setup a machine running Ubuntu Linux capable of talking to the NitroKey. Make sure to have your user PIN handy – you’ll need it! Additionally, you’ll want to run through and install the following packages which are needed to perform the following tasks.

apt-get install libengine-pkcs11-openssl
apt install gnutls-bin



Setting up your Root CA

First, perform the following:

mkdir /root/ca
cd /root/ca
mkdir certs crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial

This sets up the files required for openssl’s CA module to function.

Next, create a file openssl.cnf in this directory populated with the following:
We will specify this file when working with our root certificate. Notice that it does not provide the location of the root CA’s private key – this is because we will later specify a key located on the NitroKey HSM by its PKCS11 URI!

[ ca ]
# `man ca`
default_ca = CA_default

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

certificate       = /root/ca/root.crt

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

default_md        = sha256

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

[ 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        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

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

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

[ req_distinguished_name ]
# See .
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             = GB
stateOrProvinceName_default     = England
localityName_default            =
0.organizationName_default      = Alice Ltd
organizationalUnitName_default  =
emailAddress_default            =

[ 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



Generate Root Certificate Private Key

pkcs11-tool --module /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so -l --pin 123456 --keypairgen --key-type rsa:2048 --id 13

make sure to specify your actual pin!

NitroKey HSM pkcs11-tool



PKCS11 URIs

In the next section, we are going to reference the private key of the keypair we just created in order to generate a root CA certificate. Each object on a smartcard-style HSM has a PKCS11 URI, which is a unique identifier generated when creating the object. Historically, keys and certificates referenced via PKCS11 were referenced by the syntax slotx_IDxx. This created a lot of confusion, as creating new keys and certificates or moving to another HSM device could potentially change the slot or ID number!

Working with PKCS11 URIs can be a little tricky at first, but thankfully the p11tool command comes to our rescue. (note that this is what requires the gnutls-bin package)

First, issue:

p11tool --list-tokens

This will list the PKCS11 URI of the NitroKey itself. In the screenshot below, mine is considered Token 1.

NitroKey HSM p11tool

Next, in order to enumerate all objects on the token, issue the following:

p11tool --login --list-all "pkcs11:model=PKCS%2315%20emulated;manufacturer=www.CardContact.de;serial=DENK0103274;token=SmartCard-HSM%20%28UserPIN%29%00%00%00%00%00%00%00%00%00"

Notice that the final operand is derived from the URL field of the previous command

NitroKey HSM p11tool Login

You will be prompted for your user PIN, as this is an authenticated operation.

NitroKey HSM p11tool Login Pin

Locate the object of type “Private” with the ID you tagged your private key with above. Note that under “Label” even public keys default to the label “Private Key” which can be confusing.

Copy off your PKCS11 URI before proceeding to the next section. Mine is:

pkcs11:model=PKCS%2315%20emulated;manufacturer=www.CardContact.de;serial=DENK0103274;token=SmartCard-HSM%20%28UserPIN%29%00%00%00%00%00%00%00%00%00;id=%14;object=Private%20Key;type=private.

Create the Root CA Certificate from the private key on the HSM. This is what you will distribute to your clients.

openssl req -new -x509 -days 7300 -sha256 -extensions v3_ca -engine pkcs11 -keyform engine -key " pkcs11:model=PKCS%2315%20emulated;manufacturer=www.CardContact.de;serial=DENK0103274;token=SmartCard-HSM%20%28UserPIN%29%00%00%00%00%00%00%00%00%00;id=%14;object=Private%20Key;type=private " -out /root/ca/root.crt

You will once again be prompted for your user pin. Then, you will be asked to enter information about your Root CA.

Enter Root CA Information and Pin Root CA Information Entered

Creating your Intermediate CA:

Execute the following to prepare the files needed for the OpenSSL CA module to work:

mkdir /root/ca/intermediate
cd /root/ca/intermediate
mkdir certs crl csr newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial

In this directory, create an openssl.cnf file with the following contents:
This will be specified when working with your intermediate CA’s private key in OpenSSL.
Highlighted in bold are the locations of the intermediate certificate and key.

# OpenSSL intermediate CA configuration file.
# Copy to `/root/ca/intermediate/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = /root/ca/intermediate
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       = /root/ca/intermediate/intermediate.key.pem
certificate       = /root/ca/intermediate/intermediate.cert.pem

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

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

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

[ 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        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

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

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

[ req_distinguished_name ]
# See .
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             = GB
stateOrProvinceName_default     = England
localityName_default            =
0.organizationName_default      = Alice Ltd
organizationalUnitName_default  =
emailAddress_default            =

[ 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

Generate Private key for intermediate certificate authority:

openssl genrsa -aes256 -out intermediate.key.pem 4096

Generate a CSR to be signed by the Root Certificate Authority

openssl req -config /root/ca/intermediate/openssl.cnf -new -sha256 -key intermediate.key.pem -out intermediate.csr.pem

Sign the intermediate CA using the root CA’s private key on the Nitrokey HSM:

openssl ca -config /root/ca/openssl.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -engine pkcs11 -keyform engine -keyfile "pkcs11:model=PKCS%2315%20emulated;manufacturer=www.CardContact.de;serial=DENK0103274;token=SmartCard-HSM%20%28UserPIN%29%00%00%00%00%00%00%00%00%00;id=%12;object=Private%20Key;type=private" -in intermediate.csr.pem -out intermediate.cert.pem

You will once again be prompted for your user PIN.



Generating Client Certificates from the intermediate certificate authority

Generate a CSR:

openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr

Sign the CSR using the private key of the intermediate CA:

openssl ca -config /root/ca/intermediate/openssl.cnf -extensions server_cert -days 375 -notext -md sha256 -in server.csr -out clientcert.pem



Next Steps

At this time, the machine with the NitroKey plugged in should be powered down. The root CA will only be needed if introducing a new intermediate CA, or in the case of a key compromise of the intermediate CA. (At this point the root CA should be revoked). If using this setup in an enterprise environment, ideally the CRL would be provided over HTTP and tacked onto the root CA’s public certificate as an extension.

Acknowledgements:
Configuration files are modified versions of those provided by https://jamielinux.com/docs/openssl-certificate-authority/.


Author: Jeremy Schatten
Published:

    Next Guide...
    M of N Setup with NitroKey HSM

    This is perhaps one of the most abstract uses of an HSM, so let’s start with a real-world scenario. Your IT department recently read the last article in this series and wants to setup an offline root CA whose private key is stored on the Nitrokey HSM. Just like any hardware, the NitroKey has the p…