Vault Authentication with YubiKey

This guide provides step-by-step instructions on how to use Yubikey NEO or any other PKCS#11 enabled hardware token to authenticate with HashiCorp Vault’s TLS Certificates Auth backend.

Steps in a nutshell:

Vault server with self-signed TLS certificate

Let’s setup Vault instance with self-signed certificate. We need to have TLS enabled, so we can use curl certificate authentication functions later. You can skip this part if you already have running Vault server.

Generate self-signed certificate

$ openssl req \
    -x509 -newkey rsa:2048 -nodes -keyout server.key \
    -subj "/C=SK/O=Example s.r.o./CN=localhost" \
    -sha256 -days 1825 -out server.crt

Write Vault configuration to disk

$ cat <<EOT >> config.hcl
backend "inmem" {
}

listener "tcp" {
  tls_cert_file = "server.crt"
  tls_key_file  = "server.key"
}
disable_mlock = true
default_lease_ttl = "768h"
max_lease_ttl = "768h"
api_addr = "https://127.0.0.1:8200"
EOT

Start the Vault instance with our configuration file

$ vault server -config=config.hcl

** The Vault is not initialized, so let’s initialize and unseal it in other console window**

# set the environment variables for the next steps
export VAULT_SKIP_VERIFY=True
export VAULT_ADDR='https://127.0.0.1:8200'

# initialize Vault
$ vault operator init -key-shares=1 -key-threshold=1
Unseal Key 1: JGpSgUGI9R4jqH/3JErxM++tDvxaJh25U1hr51wSfFU=

Initial Root Token: s.f4UtQFTyPAk9oZ7xpEbQXxB1

# unseal
$ vault operator unseal JGpSgUGI9R4jqH/3JErxM++tDvxaJh25U1hr51wSfFU=

# export root token for further setup
export VAULT_TOKEN='s.f4UtQFTyPAk9oZ7xpEbQXxB1'

Create certification authority and issue a certificate for authentication

In this step, we will generate certification authority, then we will generate a key pair for YubiKey and sign it with our authority. I will use openssl as I am used to it, but you can use also Vault PKI backend or any other software for certificate management.
You can also generate private key directly on the YubiKey, but I will import it instead.

# create certification authority
$ openssl req \
    -x509 -newkey rsa:2048 -nodes -keyout rootca.key \
    -subj "/C=SK/O=Example s.r.o./CN=Certification Authority" \
    -sha256 -days 1825 -out rootca.crt

# generate a private key and certificate for our hardware token
$ openssl req \
    -newkey rsa:2048 -nodes -keyout hwtoken.key \
    -subj "/C=SK/O=Example s.r.o./CN=Vault Auth key pair" \
    -out hwtoken.csr

# issue certificate using previously generated certification authority and CSR file
$ openssl x509 -req \
    -in hwtoken.csr -CA rootca.crt -CAkey rootca.key -CAcreateserial -out hwtoken.crt -days 365 -sha256

Enable TLS certificates authentication method

Now let’s enable TLS certificates authentication method in Vault and configure our certification authority as a verification mechanism. I will also set a hwtoken_policy policy as a policy that will be assigned to a token after successful authentication.

# enable auth backend
$ vault auth enable cert
Success! Enabled cert auth method at: cert/

# set CA certificate as an auth mechanism
$ vault write auth/cert/certs/hwtoken \
    display_name=hwtoken \
    policies=hwtoken_policy \
    certificate=@rootca.crt \
    ttl=360

Success! Data written to: auth/cert/certs/hwtoken

Import private key and certificate to YubiKey

Let’s install dependencies on Ubuntu 19.10, and setup our YubiKey.
We are using YubiKey PIV 9a slot which should authenticate the cardholder and require PIN for any private key operations.

# install YubiKey related libraries
$ sudo apt install yubikey-manager yubico-piv-tool

# install pkcs11 SSL Engine and p11tool
$ sudo apt install libengine-pkcs11-openssl gnutls-bin

Now, we will reset YubiKey PIV slot and import the private key and certificate. I will use default values of PIN and management key, but you definitely want to set your own management key, pin and puk according to a Yubico manual.

# reset yubikey PIV slot
$ ykman piv reset
WARNING! This will delete all stored PIV data and restore factory settings. Proceed? [y/N]: y
Resetting PIV data...
Success! All PIV data have been cleared from your YubiKey.
Your YubiKey now has the default PIN, PUK and Management Key:
	PIN:	123456
	PUK:	12345678
	Management Key:	010203040506070801020304050607080102030405060708

# export the management key
$ export key=010203040506070801020304050607080102030405060708

# import authentication key and certificate to a PIV slot of the YubiKey token
$ yubico-piv-tool --key=$key -a import-key -s 9a < hwtoken.key
Successfully imported a new private key.

$ yubico-piv-tool --key=$key -a import-certificate -s 9a < hwtoken.crt
Successfully imported a new certificate.

# show information about the PIV slot
$ ykman piv info
PIV version: 1.0.4
PIN tries remaining: 3
CHUID:	No data available.
CCC: 	No data available.
Slot 9a:
	Algorithm:	RSA2048
	Subject DN:	C=SK,O=Example s.r.o.,CN=Vault Auth key pair
	Issuer DN:	C=SK,O=Example s.r.o.,CN=Certification Authority
	Serial:		366488651603630184900744804202490464178266778093
	Fingerprint:	c833125e86db447cb566ced42bde33a76dfeeb51dfb0a32a6a63676a0bcfc73f
	Not before:	2020-03-19 19:08:41
	Not after:	2021-03-19 19:08:41

Finally, let’s authenticate

Before calling Vault authentication we have to find out pkcs11 URI. We can use p11tool which we have installed.
To make the curl command less ugly, you can create a bash alias.

$ p11tool --list-tokens
Token 0:
	URL: pkcs11:model=p11-kit-trust;manufacturer=PKCS%2311%20Kit;serial=1;token=System%20Trust
	Label: System Trust
	Type: Trust module
	Flags: uPIN uninitialized
	Manufacturer: PKCS#11 Kit
	Model: p11-kit-trust
	Serial: 1
	Module: p11-kit-trust.so


Token 1:
	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=Vault%20Auth%20key%20pair
	Label: Vault Auth key pair
	Type: Hardware token
	Flags: RNG, Requires login
	Manufacturer: piv_II
	Model: PKCS#15 emulated
	Serial: 00000000
	Module: opensc-pkcs11.so

Let’s log in to Vault using certificate stored in YubiKey:

$ curl \
    -E 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=Vault%20Auth%20key%20pair' \
    --request POST \
    --insecure \
    --data '{"name": "hwtoken"}' \
    https://127.0.0.1:8200/v1/auth/cert/login

Enter PKCS#11 token PIN for Vault Auth key pair:
{
  "request_id": "fa7077ac-7f0a-7446-c31b-a541b8ac599e",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": null,
  "wrap_info": null,
  "warnings": null,
  "auth": {
    "client_token": "s.6xu4yB6qVWakZGk4zLW6C4T4",
    "accessor": "IKxMB4zoJdoKwIB1EGNRoOQC",
    "policies": ["default", "hwtoken_policy"],
    "token_policies": ["default", "hwtoken_policy"],
    "metadata": {
      "authority_key_id": "",
      "cert_name": "hwtoken",
      "common_name": "Vault Auth key pair",
      "serial_number": "366488651603630184900744804202490464178266778093",
      "subject_key_id": ""
    },
    "lease_duration": 360,
    "renewable": true,
    "entity_id": "e639e6fe-d5c4-92eb-f757-785276d01866",
    "token_type": "service",
    "orphan": true
  }
}

Software, hardware used: