/nixos-yubikey-luks

Set up a LUKS-encrypted filesystem for Yubikey in NixOS

Primary LanguageNixMIT LicenseMIT

LUKS-Encrypted Filesystem with Yubikey PBA

In this guide, we describe how to set up an encrypted filesystem with Yubikey pre-boot authentication (PBA) on NixOS. While the focus is on NixOS, the same techniques should be able to be used on any Linux system where Linux Unified Key Setup (LUKS) is available.

This guide is inspired by and based on Yubikey based Full Disk Encryption (FDE) on NixOS.

Other methods exist for other Linux distributions:

Design

We have the option of using either one (1FA) or two (2FA) factors for authentication. Using 1FA, the Yubikey must be inserted to open the LUKS device, but no extra passphrase is required. With 2FA, once the Yubikey is inserted, we'll be asked to enter a passphrase in order to open the LUKS device.

We'll program the Yubikey in Challenge-Response (HMAC-SHA1) mode in an alternate slot. Then we'll calculare the salt and iterations and store them on an unencrypted partition. These values will be used to calculate the challenge for the Yubikey. The response, along with a user-entered passphrase in 2FA, will be used to calculate the LUKS key.

At boot time, NixOSs Yubikey PBA will read the salt and iterations, which is again used to calculate the challenge. The Yubikey's response will be used to calculate the LUKS key. If we're using 2FA, we'll enter a passphrase which will be combined with the challenge-response key. If the key is successfully unlocked, NixOS will recalculate the salt and iterations values, and the expected Yubikey response. It will use the response to update the LUKS key so the passphrase is different at each time the machine is booted.

Requirements

Before beginning the process, it's assumed that you have

  • An unencrypted partition (Here we use ESP, but any partition is fine)
  • A Yubikey with a free configuration slot
  • A running NixOS system

Setup

For convenience, I've created a Nix expression that includes all dependencies. Enter the nix-shell:

nix-shell https://github.com/sgillespie/nixos-yubikey-luks/archive/master.tar.gz

Setup - Manual

If you don't want to use the nix expression, we can set up the same environment manually.

You'll need the following software dependencies:

  • cryptsetup
  • gcc
  • openssl
  • pbkdf2-sha512
  • yubikey-personalization

pkdf2-sha512 is a simple program included in nixpkgs that exposes OpenSSLs PKDF2 implementation. Grap the source file at https://raw.githubusercontent.com/NixOS/nixpkgs/master/nixos/modules/system/boot/pbkdf2-sha512.c and compile it.

Finally, we need a couple of bash helper functions.

rbtohex() {
    ( od -An -vtx1 | tr -d ' \n' )
}

hextorb() {
    ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI'| xargs printf )
}

Add these to a shell file and source it.

Procedure

Step 1 - Program the Yubikey Free Slot

Program the Yubikey's free slot challenge-response mode (HMAC-SHA1). We let the Yubikey generate a key for us.

ykpersonalize -2 -ochal-resp -ochal-hmac

Step 2 - Create a New Partition

Create a new partition. As an example, we'll create a new 100G partition on sdb starting at 208G.

parted /dev/sdb -- mkpart primary 208G 308G

Step 3 - Calculate the LUKS Passphrase

Generate the initial salt. It can be any integer value between 16 and 64.

SALT_LENGTH=16
SALT="$(dd if=/dev/random bs=1 count=$SALT_LENGTH 2>/dev/null | rbtohex)"

Enter the 2FA passphrase, if desired

read -s USER_PASSPHRASE

Calculate the initial challenge and response

CHALLENGE="$(echo -n $SALT | openssl dgst -binary -sha512 | rbtohex)"
RESPONSE=$(ykchalresp -2 -x $CHALLENGE 2>/dev/null)

Calculate the LUKS slot key from the desired factors

KEY_LENGTH=512
ITERATIONS=1000000

If you want to use 2FA

LUKS_KEY="$(echo -n $USER_PASSPHRASE | pbkdf2-sha512 $(($KEY_LENGTH / 8)) $ITERATIONS $RESPONSE | rbtohex)"

Otherwise

LUKS_KEY="$(echo | pbkdf2-sha512 $(($KEY_LENGTH / 8)) $ITERATIONS $RESPONSE | rbtohex)"

Step 4 - Create the LUKS device

Create the LUKS device.

CIPHER=aes-xts-plain64
HASH=sha512

As an example, we'll use the partition we created in Step 1: /dev/sdb5.

echo -n "$LUKS_KEY" | hextorb | cryptsetup luksFormat --cipher="$CIPHER" \ 
  --key-size="$KEY_LENGTH" --hash="$HASH" --key-file=- /dev/sdb5

Step 5 - Store Salt and Iterations

Store the salt and iterations on an unencrypted partition. Here, we use the ESP partition mounted on /boot

mkdir -p /boot/crypt-storage
echo -ne "$SALT\n$ITERATIONS" > /boot/crypt-storage/default

Step 6 - Open the LUKS device

Open the LUKS device. As an example, we again use /dev/sdb5.

echo -n "$LUKS_KEY" | hextorb | cryptsetup open /dev/sdb5 encrypted --key-file=-

We can now access the volume at /dev/mapper/encrypted. For example, to format it as ext4

mkfs.ext4 /dev/mapper/encrypted

Step 7 - Update NixOS Configuration

Open up your hardware configuration at /etc/nixos/hardware-configuration.nix and set up the new LUKS-encrypted disk

boot.initrd = {
  # Required to open the EFI partition and Yubikey
  kernelModules = ["vfat" "nls_cp437" "nls_iso8859-1" "usbhid"];
  
  luks = {
    # Support for Yubikey PBA
    yubikeySupport = true;
    
    devices."encrypted" = {
      device = "/dev/sdb5"; # Be sure to update this to the correct volume
      
      yubikey = {
        slot = 2;
        twoFactor = true; # Set to false for 1FA
        gracePeriod = 30; # Time in seconds to wait for Yubikey to be inserted
        keyLength = 64; # Set to $KEY_LENGTH/8
        saltLength = 16; # Set to $SALT_LENGTH
        
        storage = {
          device = "/dev/sdb1"; # Be sure to update this to the correct volume
          fsType = "vfat";
          path = "/crypt-storage/default";
        };
      };
    };
  };
};

Step 8 - Reboot

Rebuild your NixOS configuration and reboot

nixos-rebuild boot # Rebuild NixOS configs and set as the default for next boot
reboot