Systemd-fde

Jump to: navigation, search
Full disk encryption (FDE) with systemd in openSUSE is an alternative to FDE based on GRUB2.

Full disk encryption with systemd

We can produce installations with full disk encryption (FDE) based on GRUB2 or systemd.

For instructions about how to use the installed to generate FDE installations based on GRUB2, please refers to the SDB:Encrypted_root_file_system page.

For a systemd' based FDE, the tools and components are somehow evolving, but we have a clear proposal that can be used now in openSUSE. This page will describe several recipes, sometimes too low level for our taste. Over the time most of these procedures will be migrated or into other tools in the distribution.

systemd-boot

Using systemd-boot instead of GRUB2 as a boot loader is optional, but this page will assume that this is the case.

Because the seal / unseal of the LUKS2 device is delegated into systemd services, the use of one boot loader or another should be an orthogonal decision most of the times. In reality, the proposed architecture depends on the kind of boot entries that systemd-boot is supporting, and that are described in the boot loader specification, so using GRUB2 will require some adjustments.

The YaST can now produce installations with systemd-boot, but if we are currently using GRUB2 we can manually migrate. See Systemd-boot for instructions.

Encryption of a secondary device

If the secondary device is empty, we can directly create a encrypted volume with:

# Take note of the master key, as it will be required
# for doing any modification
MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 8)
echo "$MASTER_KEY" | cryptsetup -q luksFormat "$DEV"

The device can be open and mapped with:

NAME=cr-root
echo "$MASTER_KEY" | cryptsetup -q luksOpen "$DEV" "$NAME"

The device is now is /dev/mapper/$NAME and can be partitioned and formatted with a file system.

Re-encryption of the primary device

We can encrypt out primary device, the one that contains all the system (sysroot). Before doing that be sure to follow the instructions that comes from the Systemd-boot page, as we need to move the kernel and the initrd outside the /boot into the ESP one (that is mounted on /boot/efi).

We need to do that to be sure that the kernel and the initrd are not located in the encrypted device, so the boot loader can find and load them during the boot process.

After that, we need to boot from an openSUSE rescue CD image.

From that, we can safely access to the main device. The first step is to resize the file system to make room for the LUKS2 header:

mount "$DEV" /mnt
btrfs filesystem resize -32m /mnt
umount /mnt

Now we can re-encrypt the device:

# Take note of the master key, as it will be required
# for doing any modification
MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 12)
echo "$MASTER_KEY" | cryptsetup -q reencrypt --encrypt \
    --reduce-device-size 32m "$DEV"

We can open it and be sure that the size is the correct one:

NAME=cr-root
echo "$MASTER_KEY" | cryptsetup -q luksOpen "$DEV" "$NAME"
# If there is a complain about the file size, we can fix it with:
btrfs rescue fix-device-size /dev/mapper/"$NAME"

Now that the device has been open, we need to mount the default subvolume, the ESP partition and some special directories in preparation for a chroot:

mount /dev/mapper/"$NAME" /mnt
mount "$ESP_DEV" /mnt/boot/efi
for i in /dev /dev/pts /proc /sys /run /var; do
  mount -B $i /mnt"$i"
done
chroot /mnt

From inside the chroot we need to edit /etc/crypttab and be sure that a line like this one is present (please, replace the different variables with the correct value):

$NAME  $DEV  none  x-initrd.attach

This file should be inside a new initrd that can be generated with dracut -f, or calling the tool sdbootutil

sdbootutil add-all-kernels --no-reuse-initrd

Reboot now and introduce the "$MASTER" key generated for this volume.

Enrolling a [recovery] password

During the encryption stage we generated a key that we will use now to enroll new keys.

Ideally during the installation the master key will be randomly generated, and later the user will replace with its own key. We can list all the available key slots:

systemd-cryptenroll "$DEV"

At this stage we should have only one slot, name "0 password". That is the current master key that we can replace with:

systemd-cryptenroll --wipe-slot=password -password "$DEV"

This command will ask for a the master password to enter before it can enroll a new one. We can avoid this exporting the PASSWORD environment variable. This method is not recommended, and a better mechanism is to use the kernel keyring:

echo -n "$MASTER_KEY" | keyctl padd user cryptenroll @u

This will add into the user keyring the master key under the cryptenroll name. This name will be used internally by systemd to recover the password and avoid the prompt.

We can add a recovery key in a similar way:

systemd-cryptenroll --recovery-key "$DEV"

This will present a high entropy key that we can take note, and a QR code that we can scan for convenience.

Enrolling a FIDO2 key

FIDO2 keys are tokens that prove the presence of the user by owning them. If our key has the "hmac-secret" extension we can enroll one with:

systemd-cryptenroll --fido2-device=auto "$DEV"

To detect if we have inserted one of those keys, we can listen all the ones available:

systemd-cryptenroll --fido2-device=list

We need to annotate /etc/crypttab adding ,fido2-device=auto at the end of the line, like for example:

$NAME  $DEV  none  x-initrd.attach,fido2-device=auto

Now we can generate the initrd as before:

sdbootutil add-all-kernels --no-reuse-initrd

Enrolling a TPM2 device

We can use a TPM2 to unseal the LUKS2 password. Using the measured boot process, the TPM2 will decrypt the password only if the status of the system is a known one, by creating policies that condition the unseal over a set of PCR values.

You can find more information about TPM2 in SDB:Encrypted_root_file_system and Portal:MicroOS/RemoteAttestation pages.

We will use authorized policies, that are the ones where the expected values for the PCRs are signed, and the TPM2 will unseal the key if the signature is validated (via the public key) and the predicted PCR values matches the current ones.

systemd-cryptenroll requires authorization policies stored in a JSON file. Tools like systemd-measure and systemd-pcrlock can generate this kind of file, but this last component is only available since systemd v255.

The pcr-oracle tool used for FDE based on GRUB2 has been extended to make predictions over PCRs that are used beyond the boot loader (mostly PCRs 9, 12, 14 and in some situation 7), and now also able to generate the JSON files required by systemd.

The first step is to generate a RSA 2048 pair of keys that will be used to sing the policies. We can use openssl:

openssl genpkey \
  -algorithm RSA \
  -pkeyopt rsa_keygen_bits:2048 \
  -out /etc/systemd/tpm2-pcr-private-key.pem
openssl rsa \
  -pubout \
  -in /etc/systemd/tpm2-pcr-private-key.pem \
  -out /etc/systemd/tpm2-pcr-public-key.pem

... or pcr-oracle itself:

pcr-oracle \
  --rsa-generate-key \
  --private-key /etc/systemd/tpm2-pcr-private-key.pem \
  --public-key /etc/systemd/tpm2-pcr-public-key.pem

In both cases the keys will be stored in /etc/systemd.

We can enroll now the TPM2, allocating one LUKS2 keyslot.

# Select the PCRs that are involved in the measured boot process
PCRS="0,2,4,7,9"
systemd-cryptenroll \
  --tpm2-device=auto \
  --tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
  --tpm2-public-key-pcrs="$PCRS" \
  "$DEV"

The selected PCRs are covering the code side of measured boot. We can change the selection to cover also the data parameters. In any case we are covering the full firmware, secure boot, boot loader, kernel, command line and initrd.

We are also enrolling the public key, so if we want to change the selected set of PCRs, or change the key, we need to wipe the TPM2 keyslot and enroll the new one.

systemd-cryptenroll --wipe-slot=tpm2 "$DEV"

In any case, after the enrolling we need to update the /etc/crypttab file and re-generate the initrd:

# Add a line like this one in /etc/crypttab
# $NAME  $DEV  none  x-initrd.attach,tpm2-device=auto
sdbootutil add-all-kernels --no-reuse-initrd

We can now generate the predictions:

rm -f /etc/systemd/tpm2-pcr-signature.json
for entry in /boot/efi/loader/entries/*.conf; do
  entry_id="$(basename "$entry")"
  pcr-oracle \
    --private-key /etc/systemd/tpm2-pcr-private-key.pem \
    --from eventlog \
    --output /etc/systemd/tpm2-pcr-signature.json \
    --target-platform=systemd \
    --boot-entry "$entry_id" \
    sign "$PCRS"
done

The prediction file gets removed, as pcr-oracle will append new prediction in this file. We are also iterating over all the boot loader entries, to select all the available combinations of kernel, command line and initrd that are valid for our system.

Somehow we need to make those predictions available during the boot time, but there are two issues: they are stored in /etc/systemd that should be encrypted, and we have a prediction over initrd, so we cannot change it anymore to include the files.

We can resolve this issue using the dracut-pcr-signature package, and copying the required data in the ESP.

zypper in dracut-pcr-signature
cp /etc/systemd/tpm2-pcr-public-key.pem /boot/efi/EFI/systemd
cp /etc/systemd/tpm2-pcr-signature.json /boot/efi/EFI/systemd

After every update need to generate new predictions and copy them into the ESP to void the request of the password.

The sdbootutil is doing this for us. For every update it will generate a new set of predictions and make them available in the expected place.


See also

Related articles