GRUB2 encrypted boot unlock using a TPM
This post describes who to use Grub2 to unlock a LUKS encrypted /boot
partition using a keyfile
that sealed using a TPM. The original patches for this are from Hernan Gatta and are on the
grub2-devel mailing list.
These can be applied onto the current Grub2 master branch, although involves some manual merging.
Then this patched Grub2 can be used to boot a Fedora 36 virtual machine with encrypted boot partition
without manual interaction.
These instructions follow what Hernan Gatta described in the emails on the list, and apply them to a Fedora 36 system.
Install Fedora 36
Default Fedora 36 install. Automatic setup using virt-install:
cat << EOF > kickstart.cfg
graphical
keyboard --vckeymap=us --xlayouts='us'
lang en_US.UTF-8
url --url="https://download.fedoraproject.org/pub/fedora/linux/releases/36/Server/x86_64/os"
%packages
@^custom-environment
@guest-agents
@standard
%end
firstboot --enable
ignoredisk --only-use=vda
autopart --encrypted --passphrase=pw2233
clearpart --none --initlabel
timezone Europe/Berlin --utc
rootpw rootpw22
reboot
EOF
Then create the VM:
virt-install \
--name "enc-boot" \
--boot uefi \
--memory 2048 \
--vcpus 2 \
--disk size=8 \
--location "https://download.fedoraproject.org/pub/fedora/linux/releases/36/Server/x86_64/os" \
--os-variant fedora36 \
--initrd-inject "./kickstart.cfg" \
--extra-args="inst.ks=file://kickstart.cfg"
Use automatic partitioning, enable encryption. This results in a partition layout like this:
[root@fedora ~]# lsblk -o NAME,FSTYPE,SIZE,TYPE,MOUNTPOINTS
NAME FSTYPE SIZE TYPE MOUNTPOINTS
sr0 1024M rom
zram0 5.8G disk [SWAP]
vda 20G disk
├─vda1 vfat 600M part /boot/efi
├─vda2 ext4 1G part /boot
└─vda3 crypto_LUKS 18.4G part
└─luks-ac79e1eb-25a9-4b36-ade8-da53150718f3 btrfs 18.4G crypt /home
/
A standard ESP partition, unencrypted /boot, and /
and /home
on a LUKS
encrypted btrfs.
Disable SecureBoot
Our new grub binary is not signed and can not be booted on a SecureBoot enabled system unless further steps are taken, which not in the scope of this article. To be able to boot the patched grub later, let's just disable secure boot.
mokutil --disable-verification
Choose a temporary password. You will be asked to enter it (or parts of it) once when rebooting. Then reboot and follow the instructions during boot to disable SecureBoot. Then check if SecureBoot is really disabled:
[root@fedora ~]# mok-util --sb-state
SecureBoot enabled
SecureBoot validation is disabled in shim
Disable Boot Loader Spec
Only needed if BLS patches not used.
Fedora uses the Boot Loader Spec. This places some parts of the Grub2 config
in extra files on /boot
. This is not yet supported by upstream Grub, so
disable it for now:
sed -i s/GRUB_ENABLE_BLSCFG=true/GRUB_ENABLE_BLSCFG=false/ /etc/default/grub grub2-mkconfig -o /boot/grub2/grub.cfg rm -r /boot/loader
Build Grub with Patches
The following tools are needed to build Grub2 from source:
dnf install \
make \
binutils \
bison \
gcc \
gettext-devel \
flex \
git \
patch \
automake
Then pull the Grub2 sources including the patches for unlocking /boot
.
This branch also includes the patch from the Fedora version of Grub2
that adds support for Boot Loader Specification (BLS), which is used in Fedora 36.
These commands pull and build Grub2:
git clone https://github.com/osteffenrh/grub2
cd grub2
git checkout tpm-unlock-BLS
./bootstrap
mkdir build
./configure --with-platform=efi --prefix=$(realpath ./build)
make
make install
If you don't want BLS, use the tpm-unlock
branch instead.
Now make a grub.cfg
(to be baked into the grub image) with this content:
cd build
cat << EOF > grub.cfg
tpm2_key_protector_init -k (hd0,gpt1)/efi/fedora/sealed_key
cryptomount -k tpm2 (hd0,gpt2)
configfile (crypto0)/grub2/grub.cfg
EOF
./bin/grub-mkstandalone \
--modules="part_gpt cryptodisk luks luks2 pbkdf2 ext2" \
-o grub2-standalone.efi \
-O x86_64-efi \
"/boot/grub/grub.cfg=./grub.cfg"
There is our fresh grub binary:
# file grub2-standalone.efi
grub2-standalone.efi: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows
Replace fedora grub:
cd /boot/efi/EFI/fedora
mv grubx64.efi grubx64_fedora.efi
cp /root/grub2/build/grub2-standalone.efi grubx64.efi
Encrypt /boot
umount /boot/efi
cd boot
tar cf ../b.tar .
cd ..
umount /boot
wipefs --all /dev/vda2
cryptsetup luksFormat --type luks1 --pbkdf pbkdf2 --force-password /dev/vda2
Choose some password. Later unlocking will be automatic, but we need this for manual interaction.
Add a keyfile for automatic unlocking:
mkdir -p /etc/keys/
dd if=/dev/urandom of=/etc/keys/boot.key bs=1 count=32
chmod 400 /etc/keys/boot.key
cryptsetup luksAddKey /dev/vda2 /etc/keys/boot.key --pbkdf=pbkdf2 --hash=sha512
Enable automatic unlocking during Linux boot process:
echo "boot_crypt /dev/vda2 /etc/keys/boot.key luks,discard" >> /etc/crypttab
systemctl daemon-reload
Unlock now and add a file system:
systemctl start systemd-cryptsetup@boot_crypt
mkfs.ext4 /dev/mapper/boot_crypt
Replace the existing fstab entry for /boot
with
/dev/mapper/boot_crypt /boot ext4 defaults 1 2
Then:
systemctl daemon-reload
mount /boot
mount /boot/efi
Add the original content back in and regenerate the Grub2 config:
cd /boot
tar xf ../b.tar
rm ../b.tar
grub2-mkconfig -o /boot/grub2/grub.cfg
Now it looks like this:
[root@fedora boot]# lsblk -o NAME,FSTYPE,SIZE,TYPE,MOUNTPOINTS NAME FSTYPE SIZE TYPE MOUNTPOINTS sr0 1024M rom zram0 5.8G disk [SWAP] vda 20G disk ├─vda1 vfat 600M part ├─vda2 crypto_LUKS 1G part │ └─boot_crypt ext4 1008M crypt /boot └─vda3 crypto_LUKS 18.4G part └─luks-ac79e1eb-25a9-4b36-ade8-da53150718f3 btrfs 18.4G crypt /home /
cd /root/grub2/build/bin
./grub-protect --action=add --protector=tpm2 \
--tpm2-keyfile=/etc/keys/boot.key \
--tpm2-outfile=/boot/efi/EFI/fedora/sealed_key
Note: key sizes seem to be limited to 128 bytes max. I got error 0x1d5
from TPM otherwise.
See https://github.com/tpm2-software/tpm2-tools/issues/1621
[root@fedora bin]# stat /boot/efi/EFI/fedora/sealed_key
File: /boot/efi/EFI/fedora/sealed_key
Size: 240 Blocks: 8 IO Block: 4096 regular file
Device: 252,1 Inode: 7 Links: 1
Access: (0700/-rwx------) Uid: ( 0/ root) Gid: ( 0/ root)
Context: system_u:object_r:dosfs_t:s0
Access: 2022-06-02 02:00:00.000000000 +0200
Modify: 2022-06-02 10:53:42.000000000 +0200
Change: 2022-06-02 10:53:42.020000000 +0200
Birth: -
Ready to go! We could reboot the system now.
Grub will auto-unlock the /boot partition and load the grub.cfg
from Fedora.
The normal boot menu should appear.
You still have to enter the password for vda3
(aka /
) during Linux boot.
Unlock root partition
One way to automatically unlock /root
once Linux is running (during initrd phase) is systemd-cryptenroll.
Other options are clevis, or embedding a keyfile into initrd directly. Since /boot
is encrypted,
that is file.
Let's use cryptenroll:
dnf install tpm2-tools
systemd-cryptenroll --tpm2-device=auto /dev/vda3
Edit /etc/crypttab
and add tpm2-device=auto
to the options of the /dev/vda3
LUKS device.
Fedora uses UUIDs to identify partitions, so this will look different, but it is probably the first
line:
# cat /etc/crypttab
luks-ac79e1eb-25a9-4b36-ade8-da53150718f3 UUID=79a3878e-180e-4510-978d-4a74ebd633f2 none tpm2-device=auto,discard,luks
boot_crypt /dev/vda2 /etc/keys/boot.key luks,discard
Then run:
dracut --force
reboot
The system should reboot. Grub should automatically unlock /boot
, load the grub.cfg
from
there, and continue booting into Fedora. Then systemd should unlock /root
automatically and
you should end up at the login prompt. Tadaaaa!
ToDo and further steps
- Re-enable SecureBoot. For this we need to sign the
grub2.efi
binary and enroll matching keys in EFI. - Tie the unlocking to a sensible set of PCRs. Right now we did not specify any and just used the defaults.