This document explains how to set up a Certificate Authority (CA) with Sub-CA private keys stored on YubiKeys. Typical use for this is to generate HTTPS certificates for internal servers.
For our example, we have chosen to use one root CA with a private key stored in an offline machine, that signs sub-CAs with private keys stored on YubiKeys, which signs end-entity (EE) certs. We’ll generate the Sub-CA private keys on an offline host and save a copy of those keys.
We have chosen to use a RSA 3744 bit root CA key, and RSA 2048 bit keys for the Sub-CAs and EE certificates. The YubiKey is limited to RSA 1k and 2k keys (it supports ECDSA too but we chose to not use that here).
By setting some name constraints, we are trying to limit to powers of this CA. This is not fully supported by all environments, but it should do no harm, and may be useful in some environments.
The root also has a path length constraint of 1 to prevent the Sub-CAs from issuing further Sub-Sub-CAs.
We also set a expiry date far away in the future on the root CA (expiring in 1000000 days) and use datefudge to set an arbitrary start date for the CA, to avoid leaking the time of CA creation which would leak some bits of information going into the randomness generation.
We use OpenSSL to generate keys and certificates. This is done on an offline machine, booted from a LiveCD. Some additional packages may be required (pcscd, etc, see below) and will have to be transferred on a USB stick.
You need a YubiKey with the PIV application on, which you can purchase from Yubico.
You need to install the PKCS#11 Engine:
dpkg -i libengine-pkcs11-openssl*
or if you are on a connected machine, more simpler:
apt-get install libengine-pkcs11-openssl
Generate the private key as follows:
openssl genrsa -out yubico-internal-https-ca-key.pem 3744
Generate the Root CA certificate and initialize the CA serial number counter as follows:
cat>yubico-internal-https-ca.conf<<EOF [ req ] x509_extensions = v3_ca distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] CN=Yubico Internal HTTPS CA [ v3_ca ] subjectKeyIdentifier=hash basicConstraints=critical,CA:true,pathlen:1 keyUsage=critical,keyCertSign,cRLSign nameConstraints=critical,@nc [ nc ] permitted;otherName=1.3.6.1.5.5.7.8.7;IA5:yubico.com permitted;email.0=yubico.com permitted;email.1=.yubico.com permitted;DNS=yubico.com permitted;URI.0=yubico.com permitted;URI.1=.yubico.com permitted;IP.0=0.0.0.0/255.255.255.255 permitted;IP.1=::/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff EOF datefudge "2014-01-01 UTC" openssl req -new -sha256 -x509 -set_serial 1 -days 1000000 -config yubico-internal-https-ca.conf -key yubico-internal-https-ca-key.pem -out yubico-internal-https-ca-crt.pem echo 01 > yubico-internal-https-ca-crt.srl
You may inspect the newly generated root CA with:
openssl x509 -text < yubico-internal-https-ca-crt.pem
We need to change the management key, PIN and PUK code following the YubiKey-PIV-Introduction.txt document. We also want to save a copy of these values. Here are the steps that are needed to be done for each new Sub-CA.
This step is parametrized with the name of the YubiKey user. Generate new management code, PIN and PUK as follows:
user=Simon key=$(export LC_CTYPE=C; dd if=/dev/urandom 2>/dev/null | tr -d '[:lower:]' | tr -cd '[:xdigit:]' | fold -w48 | head -1) echo $key > yubico-internal-https-$user-key.txt pin=$(export LC_CTYPE=C; dd if=/dev/urandom 2>/dev/null | tr -cd '[:digit:]' | fold -w6 | head -1) echo $pin > yubico-internal-https-$user-pin.txt puk=$(export LC_CTYPE=C; dd if=/dev/urandom 2>/dev/null | tr -cd '[:digit:]' | fold -w8 | head -1) echo $puk > yubico-internal-https-$user-puk.txt
Configure a fresh YubiKey with these parameters as follows:
yubico-piv-tool -a set-mgm-key -n $key yubico-piv-tool -k $key -a change-pin -P 123456 -N $pin yubico-piv-tool -k $key -a change-puk -P 12345678 -N $puk
This step is parametrized with the name of the YubiKey user. This means we will have one Sub-CA for every person authorized to sign certificates in our CA.
user=Simon
We first need to load the management key and PIN code from the previous section.
key=`cat yubico-internal-https-$user-key.txt` pin=`cat yubico-internal-https-$user-pin.txt`
Generate the private key:
openssl genrsa -out yubico-internal-https-subca-$user-key.pem 2048
Generate the Sub-CA certificate request:
cat>yubico-internal-https-subca-$user-csr.conf<<EOF [ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] CN=Yubico Internal HTTPS $user Sub-CA EOF openssl req -sha256 -new -config yubico-internal-https-subca-$user-csr.conf -key yubico-internal-https-subca-$user-key.pem -nodes -out yubico-internal-https-subca-$user-csr.pem
Generate the Sub-CA certificate:
cat>yubico-internal-https-subca-$user-crt.conf<<EOF basicConstraints = critical, CA:true, pathlen:0 keyUsage=critical, keyCertSign EOF openssl x509 -sha256 -CA yubico-internal-https-ca-crt.pem -CAkey yubico-internal-https-ca-key.pem -req -in yubico-internal-https-subca-$user-csr.pem -extfile yubico-internal-https-subca-$user-crt.conf -out yubico-internal-https-subca-$user-crt.pem echo 00 > yubico-internal-https-subca-$user-crt.srl
You may inspect the newly generated EE cert with this command:
openssl x509 -text < yubico-internal-https-subca-$user-crt.pem
Import Sub-CA key to the YubiKey:
yubico-piv-tool -k $key -a import-key -s 9c < yubico-internal-https-subca-$user-key.pem
Import Sub-CA cert to the YubiKey:
yubico-piv-tool -k $key -a import-certificate -s 9c < yubico-internal-https-subca-$user-crt.pem
This step is parametrized with the hostname, and the name of the Sub-CA used to sign the EE, so set it first:
host=munin user=Simon
We first need to load the PIN code from the previous section.
pin=`cat yubico-internal-https-$user-pin.txt`
Then generate a new private key and certificate request:
openssl genrsa -out yubico-internal-https-ee-$host-key.pem 2048 cat>yubico-internal-https-ee-$host-csr.conf<<EOF [ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] CN=$host.yubico.com EOF openssl req -sha256 -new -config yubico-internal-https-ee-$host-csr.conf -key yubico-internal-https-ee-$host-key.pem -nodes -out yubico-internal-https-ee-$host-csr.pem
Then sign the certificate using the:
cat>yubico-internal-https-ee-$host-crt.conf<<EOF basicConstraints = critical,CA:false keyUsage=critical,digitalSignature,keyEncipherment extendedKeyUsage=critical,serverAuth subjectAltName=critical,DNS:$host.yubico.com EOF openssl << EOF engine dynamic -pre SO_PATH:/usr/lib/engines/engine_pkcs11.so -pre ID:pkcs11 -pre NO_VCHECK:1 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so -pre VERBOSE x509 -engine pkcs11 -CAkeyform engine -CAkey slot_1-id_2 -sha256 -CA yubico-internal-https-subca-$user-crt.pem -req -passin pass:$pin -in yubico-internal-https-ee-$host-csr.pem -extfile yubico-internal-https-ee-$host-crt.conf -out yubico-internal-https-ee-$host-crt.pem EOF
You may inspect the newly generated EE cert with this command:
openssl x509 -text < yubico-internal-https-ee-$host-crt.pem