NixOS disk encryption with YubiKey 1FA/2FA. From scratch, USB bootable, potentially including swap
Why would you need this? What is the threat?
A challenge facing me regularly is trusted computing on untrusted hardware. It is incredibly useful to have a bootable installation of an operating system with you, with all the software stack you know and trust. However, can you actually trust the hardware? Depending on the device you use, yes. Then this is likely an overcomplicated solution looking for a problem. If your answer however is no (as it is for me), then this is the thing. The goal is to reduce the risk of falling victim to key loggers by making passwords alone useless. This is commonly achieved by using 2FA. However, good 2FA on a device you want to use on the go is hard. My solution is Yubikey+Password, with a keylogger never fully compromising your disk unless the yubikey is also stolen/taken by force. For network facing things, you can for example use the yubikey pgp function to unlock a gnu passwordstore as password manager with mfa, alternatives exist. For booting, one can use a combination of yubikey + salted user passphrase to calculate a (ever identical) luks passphrase, without the whole luks passphrase being what you enter as password.
Alternatively, setting up 1FA yubikey based luks, you can achieve an encrypted disk without the slowdown on the start of an exciting day caused by entering a long (secure) password. 1FA yubikey allows you to be lazy, but this was not my primary intention when setting this up.
Resources
- Nixos wiki: https://nixos.wiki/wiki/Yubikey_based_Full_Disk_Encryption_(FDE)_on_NixOS
- nixos-yubikey-luks project: https://github.com/sgillespie/nixos-yubikey-luks
- Disk encrypted install on nixos: https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134
- NixOS installation guide: https://nixos.wiki/wiki/NixOS_Installation_Guide
None of these guides worked well, yielding half-broken installs or installs that’d combust after the first boot. Or just installs that did have encryption, but no yubikey support. This writeup aims to be functionally complete to achieve a full working setup.
Getting started/Requirements:
- Disk to install to (further denoted as
/dev/sda, replace as required), Minimum size as you’d expect with any NixOS install. I’d recommend 16Gb or more. - Any computer already running NixOS with a slot to put the disk you want to install to. The easiest way to install to a built-in SSD/HDD is to boot the NixOS ISO image.
- Setup as described in the wiki or nixos-yubikey-luks repository, automatic setup is fine but manual works too.
nix-shell https://github.com/sgillespie/nixos-yubikey-luks/archive/master.tar.gzgdiskcommand to be available (e.g.nix-shell -p gptfdisk) to define UUIDs and partition type IDsmkfs.ext4andmkfs.fatcommands to format the partitions- internet connection
Create the luks disk key
This should be ran in the nix shell specifically prepared to deal with yubikey stuff.
SLOT=2 # either 1 or 2 works, depends on which slots of your yubikey are used
# optional; overrides the existing cryptographic key on that yubikey slot.
# Making multiple installs with one yubikey, you might not want to override your slot
ykpersonalize -"$SLOT" -ochal-resp -ochal-hmac
SALT_LENGTH=16
SALT="$(dd if=/dev/random bs=1 count=$SALT_LENGTH 2>/dev/null | rbtohex)"
read -s USER_PASSPHRASE # Skip if you want only Yubikey 1FA
echo $USER_PASSPHRASE # check your password! Otherwise, you can pretty much restart from here.
# make sure to not compromise security by storing password in your shell history.
CHALLENGE="$(echo -n $SALT | openssl dgst -binary -sha512 | rbtohex)"
# this is what will happen on unlock to calculate the disk key
RESPONSE="$(ykchalresp -"$SLOT" -x $CHALLENGE 2>/dev/null)" # replace -2 with -1 if you used slot 1 on the yubikey
KEY_LENGTH=512 # if AES-256, use 512 key length. Other algos will have different.
ITERATIONS=1000000 # Make it hard to reverse by doing a bunch of iterations. Too much will make the unlock slow, too little will reduce cracking effort needed.
# run ONLY the one appropriate one!!
# 2FA (with user password):
LUKS_KEY="$(echo -n $USER_PASSPHRASE | pbkdf2-sha512 $(($KEY_LENGTH / 8)) $ITERATIONS $RESPONSE | rbtohex)"
# 1 FA (no user password):
LUKS_KEY="$(echo | pbkdf2-sha512 $(($KEY_LENGTH / 8)) $ITERATIONS $RESPONSE | rbtohex)"
CIPHER=aes-xts-plain64
HASH=sha512Partitioning
Start by creating a formatted disk:
gdisk /dev/sda # can be ran in any environment that has gdisko, new partition table, gpt)n, create a partition, 500M, partition type idef00for EFI)n, create a partition, roughly 1G (less works too), partition type id8300(Linux file system) to store the salt and key length for luks yubikeyn, create a partition, remaining space, type8300or8309(Linux file system or Luks encrypted filesystem respectively), to contain all the encrypted partitions. In theory id8e00is designated for Linux LVM, but only unencrypted. At the time of writing this, allegedly debian installers have a tendency to wipe that particular partition type if it is encrypted. Use with care! It is also possible to later change these IDs, so this step is not immediately critical. Pretty much anything should work anyways, systemd-boot afaik does not care about the id. Only some indexing tools might get confused.p(print and check)w(write to disk. Be careful to have selected the correct disk!!!)
After doing these steps, the planned setup should look as follows:
Command (? for help): p
Disk /dev/sda: 1953525168 sectors, 931.5 GiB
Model: RTL9210 NVME
Sector size (logical/physical): 512/2048 bytes
Disk identifier (GUID): D894E466-C549-40DA-94E8-5503E3814FF7
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 1953525134
Partitions will be aligned on 2048-sector boundaries
Total free space is 3437 sectors (1.7 MiB)
Number Start (sector) End (sector) Size Code Name
1 2048 1026047 500.0 MiB EF00 EFI system partition
2 1026048 3123199 1024.0 MiB 8300 Linux filesystem
3 3123200 1953523711 930.0 GiB 8309 Linux LUKS
Command (? for help):
Don’t worry, we didn’t yet actually set up any root partition, that follows in the one LUKS container!
After creating the partition, you’ll have to format them.
# using above defined shell variables to create the luks container:
echo -n "$LUKS_KEY" | hextorb | cryptsetup luksFormat --cipher="$CIPHER" \
--key-size="$KEY_LENGTH" --hash="$HASH" --key-file=- /dev/sda3
# open the luks container:
echo -n "$LUKS_KEY" | hextorb | cryptsetup open /dev/sda3 encrypted --key-file=-
# create volume groups and partitions
pvcreate /dev/mapper/encrypted
vgcreate vg /dev/mapper/encrypted
lvcreate -L 8G -n swap vg # optional
lvcreate -l '100%FREE' -n root vg
# format the partitions
mkfs.fat /dev/sda1 -n BOOT
mkfs.ext4 -L salt /dev/sda2
mkfs.ext4 -L root /dev/vg/root
mkswap -L swap /dev/vg/swap # optional, do if you created swap
# store salts, mount partitions
mkdir /mnt
mount /dev/vg/root /mnt
mkdir /mnt/boot
mkdir /mnt/crypt-storage
mount /dev/sda1 /mnt/boot
mount /dev/sda2 /mnt/crypt-storage
echo -ne "$SALT\n$ITERATIONS" | tee /mnt/crypt-storage/default # store salt and iterations. tee because thats easier to sudo :3Once this is done, lsblk -f should output the partitions
and mount points along with IDs we’ll need in the next step.
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
sda
├─sda1 vfat FAT16 BOOT 12CE-A600 499,7M 0% /mnt/boot
├─sda2 ext4 1.0 salt 541a42a8-3423-4e01-8006-6d50e67ed2b8 906,2M 0% /mnt/crypt-storage
└─sda3 crypto_LUKS 2 e2e2c887-b7f8-4e47-b536-fe1722619836
└─encrypted LVM2_member LVM2 001 4Wt72L-clvC-gsag-AWHk-r3rp-Zxqd-W3hgSs
├─vg-swap swap 1 swap 6ee108ac-800e-486b-8219-de70cb61d077
└─vg-root ext4 1.0 root 77eb233e-3131-471b-8829-8b832eae6e1d 860,3G 0% /mnt
Fat16 is afaik spec for external bootables, it might make sense to format to fat32 if fat16 does not work or you want to install to internal drive.
Actually installing NixOS
nixos-generate-config --root /mntAfter generating the config, do the standard nix stuff like changing locale, maybe adding wifi, creating a user account etc etc.
Make sure your configuration uses systemd-boot:
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
Add kernel modules to allow inputs on systemd-boot screen to unlock the disk:
boot.kernelModules = [ "kvm-intel" "vfat" "nls_cp437" "nls_iso8859-1" "usbhid" "usb_storage" "nvme" ];I added usb_storage and nvme modules
because i run an external SSD via USB, nvme is for (modern) SSDs and usb
storage is for USB. Depending on your hardware, you’d want to change it
to sd_mod and/or ahci or similar, see https://gist.github.com/CMCDragonkai/810f78ee29c8fce916d072875f7e1751
Add the mount points for your system:
fileSystems."/" = {
# /dev/vg/root, use the ID for the partition inside the volume group!
device = "/dev/disk/by-uuid/77eb233e-3131-471b-8829-8b832eae6e1d";
fsType = "ext4";
};
fileSystems."/crypt-storage" = {
device = "/dev/disk/by-uuid/541a42a8-3423-4e01-8006-6d50e67ed2b8"; # /dev/sda2
fsType = "ext4";
options = [ "umask=077" ]; # read only so a fat-finger can't accidentially bonk our salts, rendering the disk useless.
# Keep backups of this partition, as well as of your luks headers!!!
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/12CE-A600"; # /dev/sda1
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" "umask=077" ];
};
# if you set up swap:
swapDevices = [ {
device = "/dev/disk/by-uuid/6ee108ac-800e-486b-8219-de70cb61d077"; # /dev/vg/swap
}];Make systemd-boot use the yubikey:
boot.initrd.luks.yubikeySupport = true; # enable yubikey support
boot.initrd.luks.devices."root" = {
device = "/dev/disk/by-uuid/e2e2c887-b7f8-4e47-b536-fe1722619836"; # /dev/sda3
preLVM = true;
allowDiscards = true;
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/disk/by-uuid/541a42a8-3423-4e01-8006-6d50e67ed2b8"; # same ID as the crypt-storage mount earlier
fsType = "ext4";
path = "/default";
};
};
};Once everything is ready, run the install:
nixos-install