Network-bound disk encryption on Arch Linux

Posted on zo 05 juni 2022 in Arch Linux

While in a discussion with my coworkers, a coworker brought up that they wanted to have automatic LUKS disk decryption on their desktop while it was at home. Normally they would use a passphrase to decrypt the LUKS volume but would prefer automatic decryption. There are multiple ways to achieve this with TPM2 or Tang.

A Trusted Platform Module (TPM) is a secure processor which contains the secrets required to decrypt the LUKS volume when certain conditions are met (secure boot not disabled, CMOS not reset and others..).

Tang is a network service which runs in your network and on boot is used to decrypt your LUKS volume automatically.

I currently encrypt my desktop for the scenario that my desktop is stolen, in that case it's powered off and encrypted. As it's most likely that someone would steal the whole desktop using a TPM didn't make sense and Tang seems to be a good solution. It is highly unlikely that someone will replicate my home setup when stealing my desktop it is even more unlikely that they will try to decrypt the data. So I decided to go with Tang.

First off you will need a server and install tang there, by default it runs on port 80. If this does not suite your setup you can override it using systemctl edit tangd.socket:

[Socket]
ListenStream=
ListenStream=6666

Then enable it with systemctl enable --now tangd.socket, now you'll want to check if you can fetch tang's key:

tang-show-keys 6666

On your desktop you'll need to install clevis and libpwquality. To verify the tang service works run the following commands:

echo "Hello World..." | clevis encrypt tang '{ "url": "http://tang.local"}' > secret.jwe

This command will ask you to verify tang's key and this should correspond to the output of tang-show-keys.

To verify that the tang server can be used to decrypt the secret run:

clevis decrypt < secret.jwe

To let tang decrypt your desktop automatically, on boot in initramfs clevis needs to contact the tang server and call clevis to decrypt your LUKS volume. Ideally if no network is available your normal LUKS passphrase prompt is shown.

As of now mkinitcpio has no official support for clevis yet, thanks to the excellent work of diabonas there is now a pull request open support it. To have network when in initramfs the net hook can be used from mkinitcpio-nfs-utils, to set this all up:

Adding tang to the LUKS Slot

clevis luks bind -d /dev/vda2 tang '{"url": "http://tang.local"}'

Now to configure the initramfs install mkinitcpio-nfs-utils and then copy the snippet below to /etc/initcpio/hooks/clevis:

#!/usr/bin/ash

run_hook() {
    IFS=: read cryptdev cryptname cryptoptions <<EOF
$cryptdevice
EOF
    if resolved=$(resolve_device "${cryptdev}" ${rootdelay}); then
        clevis luks unlock -d "$resolved" -n "$cryptname"
    fi
}

And create /etc/initcpio/install/clevis

#!/bin/bash

build() {
    local mod

    add_module "dm-crypt"
    add_module "dm-integrity"
    if [[ $CRYPTO_MODULES ]]; then
        for mod in $CRYPTO_MODULES; do
            add_module "$mod"
        done
    else
        add_all_modules "/crypto/"
    fi

    add_binary "cryptsetup"

    map add_udev_rule \
        '10-dm.rules' \
        '13-dm-disk.rules' \
        '95-dm-notify.rules' \
        '/usr/lib/initcpio/udev/11-dm-initramfs.rules'

    # cryptsetup calls pthread_create(), which dlopen()s libgcc_s.so.1
    add_binary "/usr/lib/libgcc_s.so.1"

    add_binary "clevis"
    add_binary "clevis-decrypt"
    add_binary "clevis-decrypt-sss"
    add_binary "clevis-decrypt-tang"
    add_binary "clevis-decrypt-tpm2"
    add_binary "clevis-luks-common-functions"
    add_binary "clevis-luks-unlock"

    add_binary "bash"
    add_binary "curl"
    add_binary "grep"
    add_binary "jose"
    [ -f "/usr/bin/luksmeta" ] && add_binary "luksmeta"

    if [ -f "/usr/bin/tpm2" ]; then
        add_checked_modules '/tpm/'
        add_binary "tpm2_createprimary"
        add_binary "tpm2_unseal"
        add_binary "tpm2_load"
        add_binary "tpm2_flushcontext"
        add_binary "/usr/lib/libtss2-tcti-device.so.0"
    fi

    add_runscript
}

For networking to work the net hook needs to know how your network interface should be configured. The configuration happens through kernel parameters, note that it uses the kenrel's interface naming so it's eth0 not enps0 or something similiar for more information see the wiki.

I use dhcp and have one interface so my kernel parameter is:

ip=:::::eth0:dhcp

Note: there is no timeout by default configured in the net hook, so if your machine can't connect to the network it won't fall back to the encrypt hook. So I recommend editing /usr/lib/initcpio/hooks/net and changing the ipconfig invocation to:

ipconfig -t 30 "ip=${ip}"

This means ipconfig will stop trying to try to connect after 30 seconds.

Now all that's left is to configure your mkinitcpio HOOKS, I prepend net clevis before encrypt so it shows up as following:

HOOKS=(base udev autodetect keyboard modconf block net clevis encrypt filesystems btrfs fsck)

Now re-generate your mkinitcpio hook:

mkinitcpio -p linux

Now rebooting should automatically decrypt your LUKS volume. Once again a huge thanks to diabonas for writing the clevis hook and helping out with setting it up.