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.
./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.
Aside from bash
, sudo
, df
, sed
, xz
and tar
, ./configure
checks for needed commands and files:
grub
for the commandsgrub-probe
andgrub-mkimage
gettext
for theenvsubst
commandqemu-system-x86
for theqemu-system-x86_64
command for testing purposesqemu-desktop
to get proper visual output
edk2-ovmf
for uefi firmware to test in qemu
# 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.
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.
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
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).
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.
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
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.
- add an
install
command to mount esp, backup the old efi file, copy the new one, and setefibootmgr
? - detect needed filesystem modules automatically in configure and only load the necessary ones
- secure boot?