Certificate Authority with NEO
------------------------------

This document explains how to set up a Certificate Authority (CA) with
Sub-CA private keys stored on YubiKey NEOs.  Typical use for this is
to generate HTTPS certificates for internal servers.

Considerations
--------------

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 YubiKey NEOs, 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 NEO Sub-CAs and EE certificates.  The NEO 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 if information going into the randomness generation.

Preparations
------------

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 NEO with the PIV applet on, which you can purchase
from Yubico.

You need to install the PKCS#11 Engine:

  sudo dpkg -i libengine-pkcs11-openssl*

or if you are on a connected machine, more simpler:

  sudo apt-get install libengine-pkcs11-openssl

Creating a Root CA
-------------------

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

Preparing a Sub-CA NEO
----------------------

We need to change the management key, PIN and PUK code following the
YubiKey-NEO-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 NEO.

This step is parametrized with the name of the YubiKey NEO user.
Generate new management code, PIN and PUK as follows:

  user=Simon
  key=`dd if=/dev/random bs=1 count=24 2>/dev/null | hexdump -v -e '/1 "%02X"'`
  echo $key > yubico-internal-https-$user-key.txt
  pin=`dd if=/dev/random bs=1 count=6 2>/dev/null | hexdump -v -e '/1 "%u"'|cut -c1-6`
  echo $pin > yubico-internal-https-$user-pin.txt
  puk=`dd if=/dev/random bs=1 count=6 2>/dev/null | hexdump -v -e '/1 "%u"'|cut -c1-8`
  echo $puk > yubico-internal-https-$user-puk.txt

Configure a fresh NEO 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

Creating a Sub-CA
-----------------

This step is parametrized with the name of the YubiKey NEO 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 NEO:

  yubico-piv-tool -k $key -a import-key -s 9c < yubico-internal-https-subca-$user-key.pem 

Import Sub-CA cert to NEO:

  yubico-piv-tool -k $key -a import-certificate -s 9c < yubico-internal-https-subca-$user-crt.pem 

Creating End-Entity Certificates
--------------------------------

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 NEO:

  cat>yubico-internal-https-ee-$host-crt.conf<<EOF
  keyUsage=critical,digitalSignature,keyEncipherment
  extendedKeyUsage=critical,serverAuth
  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
