Yubikey w/ GPG & encrypted LUKS keys

Introduction

OS: Debian Stretch

In my previous post here, I went through the process of creating 2048 bit LUKS keys with openssl and keeping them encrypted with a symmetric GPG key. While this process works, it creates another threat vector for the security of the private GPG key and could be even better designed. So, let's change that.

Yubikey Setup

I have an extra Yubikey 3 Neo so I'm going to use that here, however, with a Yubikey 4 you'd be able to generate a 4096 bit GPG key instead of the 2048 bit key I use below. Perhaps one day I'll upgrade it, but for now, let's proceed.

  1. Insert Yubikey into server with the LUKS keys/drives.
server1# lsusb
...
Bus 001 Device 005: ID 1050:0114 Yubico.com Yubikey NEO(-N) OTP+U2F
...
  1. Install the ykpersonalize tool and GPG dependancies
server1# aptitude install yubikey-personalization gnupg2 pcscd scdaemon
  1. Configure the Yubikey for HID & CCID (OTP and OpenPGP) EJECT Flag set – allows SmartCard and OTP concurrently. See https://www.yubico.com/2012/12/yubikey-neo-composite-device/ for more information on the various modes. This is the mode I use, as having the OTP enabled is still useful. You can chose your own mode that works for your setup. Just ensure that the OpenPGP applet is enabled for that mode.
Server1# ykpersonalize -m86
Firmware version 3.4.3 Touch level 1285 Program sequence 1

The USB mode will be set to: 0x86

Commit? (y/n) [n]: y
  1. Unplug and plug back in the Yubikey. This is required because, we connected it prior to the ykpersonalize tool being installed leading to the proper udev rule not being configured. You should see the following:
server1% gpg --card-status
gpg: detected reader Yubico Yubikey NEO OTP+U2F+CCID 00 00'
# Followed by information about the Yubikey
  1. Nuke all the datas! Just in case there's anything left over (I had some cruft) follow https://developers.yubico.com/ykneo-openpgp/ResetApplet.html to wipe the OpenGPG app on the Yubikey completely.

  2. Confirm the version of the OpenGPG applet on the the card.

server1# gpg-connect-agent --hex "scd apdu 00 f1 00 00" /bye
D[0000]  01 00 10 90 00                                     .....
OK

This means that applet is v1.0.10. Yay not affected by https://developers.yubico.com/ykneo-openpgp/SecurityAdvisory 2015-04-14.html so, continuing on.

  1. Setup gpg config at ~/.gnupg/gpg.conf. All of these settings are not required but, are what I use as defaults and seem to make sense to me. Feel free to modify to suit your needs.
use-agent
personal-cipher-preferences AES256 AES192 AES CAST5
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-digest-algo SHA512
charset utf-8
fixed-list-mode
no-comments
no-emit-version
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
  1. Reset the Yubikey's admin and user pins
server1# gpg2 --card-edit
gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. D2760001240102000006037107190000 detected

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? 3
# Default Admin Password: 12345678
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? 1
# Default User Password: 123456
PIN changed.

# Optionally set a Reset Code
  1. Let's generate the master GPG key.

This key will simply be for creating sub-GPG keys which will be the ones actually used for encryption. Notice here, that we are generating the key directly on the Yubikey device. This isn't required, as you could generate the keys offline on a secure device, and then copy only the subkeys to the Yubikey. This way, you could also backup and recover your GPG keys if you lost the Yubikey. Personally, I do not want these keys anywhere but stored physically on this device.

gpg/card> generate
Make off-card backup of encryption key? (Y/n) n
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 1y
Key expires at Mon 19 Jun 2017 08:17:51 AM CDT
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Server1
Email address: coolemailaddresshere@techsmix.net
Comment:
You selected this USER-ID:
    "Server1 <coolemailaddresshere@techsmix.net>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key 0x1234567891011 marked as ultimately trusted
gpg: directory '/root/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/1234567891011ABCDEFGHIJKLMNOP.rev'
public and secret key created and signed.

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: PGP
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2017-06-19
  1. Now, let's verify the master GPG key was created on the card, and take a look at it's setup.
gpg/card> list

Reader ...........: 1234:1234:X:0
Application ID ...: D1231234567892000006037101230000
Version ..........: 2.0
Manufacturer .....: Yubico
Serial number ....: <snip>
Name of cardholder: Server1 TechsMix Networks
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 4
Signature key ....: 03B3 1234 1234 3F2E 1511  9FA7 1234 E96B 1234 BC92
      created ....: 2016-06-19 13:18:46
Encryption key....: 7B2F 1234 9DFD 4567 9A1D  D717 5432 75DB 1234 7129
      created ....: 2016-06-19 13:18:46
Authentication key: 1234 C817 945C EADF F0F1  ABCD FA33 98F7 1234 86BA
      created ....: 2016-06-19 13:18:46
General key info..: pub  rsa2048/0xABE7E96123456789 2016-06-19 Server1 < coolemailaddresshere@techsmix.net>
sec>  rsa2048/0xA3EABCDEF7123456  created: 2016-06-19  expires: 2017-06-19
                                  card-no: 0006 12312312
ssb>  rsa2048/0xABCDEFCD1F86196A  created: 2016-06-19  expires: 2017-06-19
                                  card-no: 0006 12312312
ssb>  rsa2048/0x2283ABC654327129  created: 2016-06-19  expires: 2017-06-19
                                  card-no: 0006 12312312
root@Balerion:~# gpg2 --list-keys
/root/.gnupg/pubring.gpg
------------------------
pub   rsa2048/0xABE7E96123456789 2016-06-19 [SC] [expires: 2017-06-19]
      Key fingerprint = 7B2F 1234 9DFD 4567 9A1D  D717 5432 75DB 1234 7129
uid                   [ultimate] Server1 < coolemailaddresshere@techsmix.net>
sub   rsa2048/0xA3EABCDEF7123456 2016-06-19 [A] [expires: 2017-06-19]
sub   rsa2048/0xABCDEFCD1F86196A 2016-06-19 [E] [expires: 2017-06-19]
sub   rsa2048/0x2283ABC654327129 2016-06-19 [S] [expires: 2017-06-19]
  1. As we confirmed above, the Yubikey now has the Master GPG key and that GPG key is ultimately trusted locally. Now, this next part is optional but, let's create 3 unique sub-keys off this master key and set them to be used for Authentication, Encryption, and Signing individually.
It's been a long time since I've had to do this. I will edit this section once I've re-tested and confirmed the command sequence. https://github.com/drduh/YubiKey-Guide#create-subkeys looks like be nearly exactly what I remember following. 
  1. Whew, alright, that was most of the hard work. At this point, all that's left is to convert the existing, GPG encrypted, 2048 bit LUKS encryption keys to be encrypted with the Encryption sub-key and while we're at it, signed each key with the Signing Subkey.

I wrote and use https://github.com/jaredledvina/array-scripts/blob/master/array-key-rotate.sh to do the magic.

  1. Finally, we can test the process by rebooting the server (or unmounting and manually closing the LUKS volumes for each drive) and run the following:

https://github.com/jaredledvina/array-scripts/blob/master/array-unlock.sh is great and handles looking up the keys by drive UUID and decrypted the keyfile and finally tries to open the LUKS device.

There we have it, unique 2048 bit LUKS keys, now encrypted and signed with GPG sub-keys that have and only exist physically on the Yubikey 3. The Yubikey from here on out, will need to be physically attached to the server, and when accessed, unlocked with the User pin. This allows us to have much greater confidence that the private GPG key is secured on a physical medium which could be destroyed with a small script like the following if need be:

#!/bin/bash

echo "Performing emergency wipe of Yubikey"
/usr/bin/gpg-connect-agent -r /root/kill-yubikey

for drive in $(lsblk -dlnp --output NAME)
do
    echo "Performing emergency wipe of drive: $drive"
    /sbin/cryptsetup luksErase $drive
done

echo "Forcing emergency shutdown procedure now"
/sbin/shutdown --no-wall now

Where the kill-yubikey file is:

/hex
 scd serialno
 scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
 scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
 scd apdu 00 e6 00 00
 scd apdu 00 44 00 00
 /echo Yubikey has been successfully wiped.

Enjoy!