Difficulty: ★★★★★
Note
This tutorial is almost complete, it lacks only good formatting and one tiny addition which is not necessary but brings a bit of polish to the end result. Formatting, I’ll do it in a couple of days. Sorry for that. I just feel that if I postpone publishing this again then I forget about it for weeks or even months (again) - that has already happened a couple of times
By its nature this post is a response to the question once asked by @linux-aarhus and several other people (@Arisa @muvvenby). Hope you folks will find it useful.
PS: all commands were checked by me before posting. If you find anything being weird / unclear, do not hesitate to post and correct me as my bash / shell in general skills leave much to be desired.
Preface: this tutorial is made with several assumptions in mind, which are listed below.
- Your PC has TPM2 device, which is
/dev/tpmrm0
. - You have already installed your system on GPT-partitioned drive using UEFI mode.
- You have full disk encryption (FDE) already in place, meaning that your root (with /boot directory - no separate unencrypted boot partition is necessary), home and swap partitions are LVM2 volumes located inside LUKSv2 container (
/dev/sda2
), and the only unencrypted partition is EFI partition (/dev/sda1
). - Your EFI partition ($esp) size is 300 Mb or larger.
- The name of encrypted partition is
cryptlvm
. Adjust respective commands according to the name of yours. - You have AUR enabled and yay installed, because some of tools are not available in official repo.
The explanation of how to do the above is out of scope of this tutorial, please refer to Arch Wiki’s “LVM on LUKS” page for details. However it’s totally possible to use whatever layout you want, e.g. BTRFS subvolumes inside LUKSv2 container or swapfile instead of partition, it’s just the specific scenario I’m about to describe.
Before you begin tinkering with system files, install all required tools:
yay -S sbsigntools efitools tpm2-tools tpm2-tss-git tpm2-totp-git plymouth-git sbupdate-git shim-signed
Some tools have to be git versions as you can see above due to a slow pace at which they get updated in the repository, while Linux kernel and systemd elolve quite fast.
Secure Boot:
The first step is to enable Secure Boot support. Normally it should be disabled because if using its default settings it prevents Manjaro from booting, but as it is required for ensuring that only allowed code is executed, you need to enable it as follows:
mkdir -p -v /etc/efikeys
chmod -v 700 /etc/efikeys
cd /etc/efikeys
The following saves default Secure Boot keys just in case, but we’re not going to change / remove them anyway so you can skip this step:
efi-readvar -v PK -o default_PK.esl
efi-readvar -v KEK -o default_KEK.esl
efi-readvar -v db -o default_db.esl
efi-readvar -v dbx -o default_dbx.esl
Now it’s time to generate machine-specific key which will be used to authenticate your Manjaro kernels and bootloader(s):
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$(hostname) platform key/" -keyout db.key -out db.crt -days 3650 -nodes -sha256
openssl x509 -outform DER -in db.crt -out db.cer
Now you need to replace grub with systemd-boot to make whole setup easier and more straightforward. With extra effort you can keep Grub and use it as your bootloader but that again is out of scope of this how-to since it would overcomplicate things dramatically. Frankly speaking, systemd-boot isn’t necessary too, but it makes booting different kernels and efi tools easier than just UEFI boot menu.
First, remove grub:
pacman -R grub
The following will change the mountpoint of your EFI partition to sane and logical location, which is, most importantly, perfectly supported by systemd-boot.
umount /boot/efi
sed -i 's|/efi/boot|/efi|' /etc/fstab
mkdir -p /efi
mount -a
Now let’s copy our Secure Boot certificate on $ESP to let MokManager utility enroll it on the next boot:
cp db.cer /efi/MOK.cer
Mkinitcpio and cryptsetup preparation:
Time to prepare initrd part of your boot image. You need to decide which path you prefer: udev-based initrd or systemd-based one. If you have no idea, choose the first (Manjaro/Arch default). Hereinafter everything hallmarked with (a) should be considered referring to udev-based initrd setup, and everything with a foregoing (b) literal – referring to systemd-based setup.
Replace HOOKS array in your /etc/mkinitcpio.conf
and save old ones commented in the end of the file just in case:
OLD_HOOKS=$(cat /etc/mkinitcpio.conf | grep "^HOOKS=(")
a)
sed -i 's|'"${OLD_HOOKS}"'|HOOKS=(base udev keyboard consolefont autodetect plymouth modconf block plymouth-tpm2-totp tpm2 plymouth-encrypt lvm2 resume filesystems)|' /etc/mkinitcpio.conf && echo '#previous_'$OLD_HOOKS |tee -a /etc/mkinitcpio.conf
OR
b)
sed -i 's|'"${OLD_HOOKS}"'|HOOKS=(base systemd keyboard sd-vconsole autodetect sd-plymouth modconf block sd-plymouth-tpm2-totp sd-encrypt lvm2 filesystems)|' /etc/mkinitcpio.conf && echo '#previous_'$OLD_HOOKS |tee -a /etc/mkinitcpio.conf
Do the following to add entry about your LUKS partition to crypttab (discard
is optional):
echo cryptlvm $(blkid -s UUID -o value /dev/sda2) - tpm2-device=/dev/tpmrm0,tpm2-pcrs=0+7+14,nofail,discard,x-initrd.attach | tee -a /etc/crypttab.initramfs
Also for better (de)compression speed/efficiency ratio consider setting COMPRESSION="zstd"
in your /etc/mkinitcpio.conf
if not set already. Note though that this works only starting from linux 5.10.
At this stage you might want to check your cryptsetup status:
cryptsetup luksDump /dev/sda2
Normally you should have 1 or 2 keyslots already occupied, the first one is your passphrase, the other is probably /crypto_keyfile.bin
, and no tokens in place (yet). I suggest nuking your slot 1 if it’s occupied by crypto_keyfile
at the moment (since it’s not safe to use crypto_keyfile in configuration we’re building):
cryptsetup luksKillSlot /dev/sda2 1
Double-check, of course, I doubt you want to destroy a slot with actual passphrase.
Configuring sbupdate and kernel command line
Sbupdate is a tool that generates signed and thus trusted kernel images used to boot your system. Issue
cat /proc/cmdline > /etc/cmdline
GRUB_LEFTOVER=$(cat /etc/cmdline | grep "BOOT_IMAGE/" | cut -d' ' -f1)
a)
sed -i 's|'"{GRUB_LEFTOVER}"'|splash tpmkey=/dev/sda1:/keyfile:0x81000001 tpmpcr=sha256:0,7,14|' /etc/cmdline
OR
b)
sed -i 's|'"{GRUB_LEFTOVER}"'|splash|' /etc/cmdline
CMDLINE=$(cat /etc/cmdline)
echo >>/etc/sbupdate.conf "
KEY_DIR="/etc/efikeys"
ESP_DIR="/efi"
OUT_DIR="EFI/Linux"
SPLASH="/sys/firmware/acpi/bgrt/image"
BACKUP=0
EXTRA_SIGN=('/efi/EFI/systemd/grubx64.efi')
CMDLINE_DEFAULT='$CMDLINE'"
Setting up systemd-boot, cleaning old configuration:
The following will install systemd-boot and create a new boot entry for shim efi utility which will be used as a “bridge” between UEFI and Linux bootloader:
OLD_MANJARO="$(efibootmgr | grep "manjaro" | cut -d' ' -f1 | cut -c 5-8)"
efibootgmr -b "${OLD_MANJARO}" -B
bootctl install && systemctl enable systemd-boot-update.service
cp /usr/share/shim-signed/*64* /efi/EFI/systemd/
cp /efi/EFI/systemd/systemd-bootx64.efi /efi/EFI/systemd/grubx64.efi
efibootgmr -c -d /dev/sda -p 1 -L "Manjaro" -l /EFI/systemd/shimx64.efi -v
mkinitcpio -P && sbupdate
BOOTORDER="$(efibootmgr | grep "^BootOrder: " | cut -d' ' -f2)"
efibootmgr --bootorder "${BOOTORDER}"
Open /efi/loader/loader.conf
and add there default @saved
. This will make systemd-boot select your previously booted kernel by default, which is handy isn’t it? Another useful change is setting timeout
to 1 or 2 - most likely that’s long enough period to see bootloader’s menu and stop countdown if needed.
By entering the last command above you’ll have a new boot order where “Manjaro” (SB signed, actually shimx64.efi
file) will be the first option, and “Linux Boot Manager” (no signature, actually systemd-bootx64.efi
file) will be the 2nd one, auto-updateable efi binary which you might want from time to time to sign with sbsigntools or just manually copy it with resulting name of “grubx64.efi” and run sbupdate
afterwards.
Okay, you are close to the final step now, but before that reboot, enter BIOS settings, set Secure Boot ON there, exit saving setting, and the next thing you see will be MokManager’s blue screen (of death), where you should enroll your Secure Boot MOK certificate located on your $ESP.
When you finish enrolling, your PC will reboot and the next thing you will see will be systemd-boot menu. Choose any boot entry and enter your unlock passphrase.
Configuring TPM2 module and tools:
a)
Let’s install luks-tpm2 tool and respective hook for mkinitcpio:
yay -S luks-tpm2 mkinitcpio-tpm2-encrypt
Then move luks-tpm2 alpm hook in order to avoid its triggering on kernel / bootloader update. Your TPM2 setup will rely on BIOS firmware, Secure Boot status and your MOK certificates check instead.
mv /usr/share/libalpm/hooks/luks-tpm2.hook /usr/share/libalpm/hooks/luks-tpm2.hook.bak
Time to make your TPM module aware of your persistence!
tpm2_createprimary -c primary.ctx
tpm2_evictcontrol -c primary.ctx
Now look at the output of the second command and write down the index of the persistent handle, it will be used later. I just assume it’s 0x81000001
.
Edit /etc/default/luks-tpm2
:
echo >>/etc/default/luks-tpm2 "
SEALED_KEY_PUBLIC="/efi/keyfile.pub"
SEALED_KEY_PRIVATE="/efi/keyfile.priv"
TPM2TOOLS_TCTI="device:/dev/tpmrm0"
ROOT_DEVICE="/dev/sda2"
PARENT_HANDLE="0x81000001"
PCRS="sha256:0,7,14"
UNSEAL_PCRS="sha256:0,7,14"
And add a TPM-sealed key to LUKS header:
luks-tpm2 -p /efi/keyfile -H 0x81000001 /dev/sda2 init
mkinitcpio -P && sbupdate
OR
b)
This is much less complicated, but only works properly since systemd v.250:
systemd-cryptenroll /dev/sda2 --tpm2-device=/dev/tpmrm0 --tpm2-pcrs=0+7+14
Reboot and enjoy. That’s it.
TOTP boot indication:
tpm2-totp
and its hooks which were added on mkinitcpio step are responsible for showing a 6-digit code during boot time if all checks are successful. It doesn’t unlock your partition, but just indicates that PCRs are the same, and I suggest using the following way:
tpm2-totp -p 0,5,7,14 -b SHA256 -P - init
, this will hang waiting for your input. Enter some password, press Ctrl+D twice. Install Google Authenticator on your phone, scan the QR code on your screen, done. Enter tpm2-totp show
. The digits on your phone and in the terminal should be the same. Now every time you boot your PC you will see identical digits on both devices. And if you don’t see any digits, that means that there was a change to GPT / Partition table, because you initialized tpm2-totp with not only 0 (UEFI/BIOS code), 7 (Secure boot state) and 14 (MOK) PCRs as we did for unlocking LUKS partition, but with PCR 5 as well, which changes when partition table is changed. Immediately comes to mind that it can happen when booting with some USB stick plugged in. At least that’s how I use it, you are welcome to man systemd-cryptenroll
and read about PCRs if you want to dive deeper into details.
Final provisions:
- From now on you have auto-unlocking of your encrypted drive using TPM module based on verifying of UEFI configuration + Secure Boot state + MOK list during the boot sequence. Kernel and cmdline checks are not used because Manjaro has oob support for variety of kernels, some may have unique cmdlines, so you probably do not want to enter your password every time you decide to boot another kernel. Relying on the above and having all executables signed should be enough.
- Note though: we left systemd-bootx64.efi on $esp unsigned. When you decide to update it, do not copy it to grubx64.efi if unsure whether it was maliciously tampered with, use the following 2 commands:
bootctl update
sudo sbsign --key /etc/efikeys/db.key --cert /etc/efikeys/db.crt --output /efi/EFI/systemd/grubx64.efi /efi/EFI/systemd/systemd-bootx64.efi
.
Then usebootctl
command to view current bootloader info, for example, note how grubx64.efi hasn’t been updated yet on my system whilesystemd-boot-update.service
has already done it for systemd-bootx64.efi:
Available Boot Loaders on ESP:
ESP: /efi (/dev/disk/by-partuuid/8a18a9ff-7d73-4fcf-ad75-32a832ab609e)
File: └─/EFI/systemd/fbx64.efi
File: └─/EFI/systemd/grubx64.efi (systemd-boot 250.3-2-manjaro)
File: └─/EFI/systemd/mmx64.efi
File: └─/EFI/systemd/shimx64.efi
File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 250.3-3-manjaro)
File: └─/EFI/BOOT/BOOTX64.EFI
-
The rationale behind using sha256 is that it is more secure than sha1 but you are free to use any supported protocol especially if there are some limitations implied by your BIOS, which is a quite common thing for many laptops.
-
Now every time you have some changes to PCRs on which your check is based, the auto-unlock will fail and you will have to enter your passphrase. This could be caused by not only malicious agent (Smith), but also your own actions like disabling Secure Boot, enrolling some other MOK certificate or deleting the current one, updating/downgrading BIOS. If you know it is you who triggered that, then provided that it was MOK or BIOS change (not disabling of Secure Boot), on completion of boot procedure you should do the following:
a)luks-tpm2 reset
b)systemd-cryptenroll /dev/sda2 --wipe-slot=tpm2
systemd-cryptenroll /dev/sda2 --tpm2-device=/dev/tpmrm0 --tpm2-pcrs=0+7+14
And on the next boot you should not see any password prompt (due to the auto-unlocking functionality working again). -
Updating tpm2-totp functionality is simple:
tpm2-totp -p 0,5,7,14 -b SHA256 -P - reseal
, then enter your totp password, press Ctrl+D twice and it’s done.
Sources:
man sed
man bootctl
man systemd-cryptenroll
Arch Wiki for systemd-boot
Arch Wiki for TPM
Arch Wiki for Secure Boot
Luks-tpm2 GitHub page