/grub-fde

Helper scripts to create a minimal grub.efi for (an existing) full-disk-encryption with encrypted /boot to allow password retries.

Primary LanguageShellGNU General Public License v3.0GPL-3.0

Creating a custom grub BOOTX64.EFI for the esp

This project should help creating a minimal efi grub executable as an initial bootloader to decrypt a luks partition and chainload the actual bootloader within the decrypted partition. The generated efi executable replaces the BOOTX64.EFI in the esp (usually mounted in /boot/efi) generated by grub-mkinstall.

It embeds a custom grub.cfg generated from a template.

The primary use case is to retry passwords for the cryptomount without being dropped into the grub rescue shell or having to reboot via CTRL+ALT+DEL.

How it works

./configure is used to determine the cryptodisk uuids and grub path to be embedded into the config (double checks the printed values!). Create and soure a .env file with these values.

./build is using the values set in the environment to generate the grub.cfg from the template file. It creates a memdisk tar file (by default compressed with xz) containing the generated grub.cfg and all grub modules specified in the environment. and finally calls grub-mkimage to embed the memdisk, an initial.cfg and basic grub modules.

When booting the generated efi file, the initial.cfg is run, which decompresses and mounts the memdisk, inserts some basic modules required for scripting and sources the embedded grub.cfg for the actual decryption and retry logic.

Dependencies

Aside from bash, sudo, df, sed, xz and tar, ./configure checks for needed commands and files:

  • grub for the commands grub-probe and grub-mkimage
  • gettext for the envsubst command
  • qemu-system-x86 for the qemu-system-x86_64 command for testing purposes
    • qemu-desktop to get proper visual output
  • edk2-ovmf for uefi firmware to test in qemu

Usage

# to check dependencies and configure the .env file
./configure > .env
# to check if everything looks fine
cat .env
# to set the environment for the following commands
source .env

# to build the grub.cfg and embedd it into a BOOTX64.EFI file
./build

You can optionally provide a filename to the build command.

To test if the resulting file actually works, run ./test. It should open a qemu window and showing the usual password prompt. Check with <enter>, that it asks again for your password without throwing you into a prompt. After entering your password, it should show you the actual grub menu. If it does, I would assume this test to work. You could check the terminal (press C) and then TAB if all relevant commands are there (e.g. linux).

This uses your actual encrypted drive. I'm not sure if it is safe to boot from that, so make sure to cancel by going to the terminal that invoked ./test and running Ctrl+C!

You can now copy the BOOTX64.EFI file into your esp partition. Make sure to make a backup of your previous file. PLEASE DO NOT JUST OVERRIDE ANY FILES IN YOUR ESP.

Using a different grub version

To use a different version of grub and its modules than provided by your distro, you can use the Containerfile to build a podman/docker image. This image checksout a grub version (currently 2.12 by default; configurable via build arg) and compiles that.

Build custom grub container image

podman build -t grub:2.12 .`
docker build -f Containerfile -t localhost/grub:2.12 .

Optional build args and their default values:

  • --build-arg BASEIMAGE=docker.io/library/debian:12
  • --build-arg GRUB_REF=grub-2.12 (a git tag or git commit hash)
  • --build-arg ARCH=x86_64-linux-gnu to configure grub build, host and target architectures

Run custom grub container

podman run -it --name grub --volume "$(pwd)":/opt/app --workdir /opt/app grub:2.12
docker run -it --name grub --volume "$(pwd)":/opt/app --workdir /opt/app localhost/grub:2.12

Add ENV_GRUB_MODULES_DIR=/usr/local/lib/grub/x86_64-efi/ to your env, source it and you can build with the grub version in this container now. Testing with qemu would need to be done on the host though. Same with generating new env files with configure, as the container won't be able to see the luks partition by default.

You could even use the container environment to checkout and build other grub versions (without necessarily rebuilding the container).

Testing changes to the grub.cfg template

To quickly test changes to the grub.cfg template, you can use the test-vm.env and the ./test-vm command. It will use the test-luks.qcow2 as the disk with a luks encrypted partition and chainloading a custom grub that prints success. The password is 1234. This avoids qemu having to access your real with long decryption time and potential issues with booting an already running system.

How the test-luks.qcow2 was created

The image was created with qemu-img create -f qcow2 test-luks.qcow2 10M. Then mounted as a disk in virt-manager, booting from a live-system iso to create a gpt and unformatted partition in gparted. The LUKS partition was also created while in the live system: cryptsetup luksFormat --pbkdf=pbkdf2 --pbkdf-force-iterations=10000 --luks2-keyslots-size=1M --luks2-metadata-size=2M

  • pbkdf2 is currently required for grub decrypt support. The newer argon2 in luks2 is not yet supported in grub.
  • a low iteraion count to allow faster decrypt for the test image - please use proper values for actual production use
  • size limits to fit into a relatively small image Created an ext4 partition in the luks volume.

The efi to chainload was created with grub-mkimage --config "test-success.cfg" --output test-success.efi --format x86_64-efi --prefix /boot/grub halt sleep echo. And inserted into the image with guestfish -a test-luks.qcow2; running

  • run
  • luks-open /dev/vda1 luks
  • mount /dev/mapper/luks /
  • mkdir-p /boot/grub/x86_64-efi
  • copy-in test-success.efi /
  • mv /test-success.efi /boot/grub/x86_64-efi/grub.efi

Troubleshooting

The grub modules defined in build are suitable for my setup. They may not be enough on your machine. E.g. if you have the encrypted /boot on a btrfs or zfs filesystem, add those modules to the grub-mkimage command in build as well.

ToDo

  • add an install command to mount esp, backup the old efi file, copy the new one, and set efibootmgr ?
  • detect needed filesystem modules automatically in configure and only load the necessary ones
  • secure boot?