Unlocking of LUKS-encrypted volumes by using TPM 2.0

I have tried to install systemd-boot as you say and also as it says here:
https://en.opensuse.org/Systemd-boot#Modified_Packages

As of November 2022 the following packages require modifications to work with systemd-boot:

- systemd: hooks to add snapshot awareness to kernel-install
- dracut: avoid overwriting existing initrds, do not delete initrds on removal in kernel-install script
- suse-module-tools: split kernel scriptlets into separate subpackage so file triggers can be used
- snapper: fixes for plugin support
- transactional-update: call snapper plugins after transaction
- patterns-microos: don't require bootloader pattern (which is grub)
- combustion: don't require ignition grub integration

I have managed to install it without any problems but it is quite limited in MicroOS.

As discussed above, if I put the /boot partition separate and unencrypted, systemd-cryptenroll with TPM2.0 works for me but I lose the ability to restore snapshots. If I put it encrypted as subvolume inside the / partition of type btrfs, I must to use LUKS1. And if I use LUKS1, systemd-cryptenroll doesn’t work.

So, I have used the following partition scheme:

$ lsblk 
NAME      MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
nvme0n1   259:0    0   32G  0 disk  
├─nvme0n1p1
│         259:1    0  512M  0 part  /boot/efi
├─nvme0n1p2
│         259:2    0   18G  0 part  
│ └─cr_root
│         254:0    0   18G  0 crypt /boot/grub2/x86_64-efi
│                                   /boot/grub2/i386-pc
│                                   /.snapshots
│                                   /boot/writable
│                                   /opt
│                                   /srv
│                                   /usr/local
│                                   /var
│                                   /root
│                                   /
└─nvme0n1p3
          259:3    0 13,5G  0 part  
  └─cr_home
          254:1    0 13,5G  0 crypt /home

I’ve tried Clevis and can’t get it to work. However, due to the current limitations of systemd-boot in MicroOS, it’s not worth the effort. I don’t know what will come first, that GRUB2 will have support to unlock LUKS volumes, or that Systemd-boot will be fully supported in MicroOS. There is no timeline of development dates.

You can use LUKS2 with pbkdf2. grub2 does not support Argon (yet).

It’s true, my fault, and I can’t continue testing because I have exceeded the TPM failed attempt limit:

$ sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/nvme0n1p2
🔐 Please enter current passphrase for disk /dev/nvme0n1p2: (press TAB for no ec********
WARNING:esys:src/tss2-esys/api/Esys_Create.c:398:Esys_Create_Finish() Received TPM Error 
ERROR:esys:src/tss2-esys/api/Esys_Create.c:134:Esys_Create() Esys Finish ErrorCode (0x00000921) 
Failed to generate HMAC key in TPM: tpm:warn(2.0): authorizations for objects subject to DA protection are not allowed at this time because the TPM is in DA lockout mode

I was partially wrong.

It is supported by grub2 in Tumbleweed and is even fully implemented in ALP:
https://documentation.suse.com/alp/all/single-html/alp/index.html#alp-post-deploy-full-disk-encryption-tpm

The corresponding grub2 patches are in Tumbleweed:

File 0012-protectors-Add-TPM2-Key-Protector.patch of Package grub2 - openSUSE Build Service

File 0014-util-grub-protect-Add-new-tool.patch of Package grub2 - openSUSE Build Service

As far as I understand, if you enable TPM in YaST Bootloader settings, on EFI it automatically configures grub2 to use TPM, i.e. measure boot and (try to) unseal encryption key using TPM. I am not sure whether YaST also creates sealed key or of there is any automation to do it. Look at shim-install command. It also shows how to configure which PCRs are used to unseal the key. The sealed key itself will be stored on ESP. You just need to run grub2-protect once you booted with TPM2-enabled grub2 (so PCRs have the correct content).

I will certainly test it, but not sure if I will have time in the next two weeks.

P.S. Thank you, this was rather interesting question. I certainly learned quite a lot.

1 Like

But necessary shim support is in Factory but not yet in Tumbleweed (and so not yet in MicroOS).

1 Like

OK, all that is really needed is shim-install and it can be copied from Factory. With it the following works on MicroOS.

  1. Add the following to /etc/default/grub:
GRUB_TPM2_PCR_LIST=0,2,4,7
GRUB_TPM2_SEALED_KEY=root.key
  1. Make sure TPM is enabled (either via YaST or adding TRUSTED_BOOT="yes" to /etc/sysconfig/bootloader
  2. Update bootloader with transactional-update bootloader
  3. Reboot (to get measurements)
  4. Add key file to LUKS slot using standard procedure; for LUKS2 it is
dd if=/dev/urandom of=key bs=1 count=32
cryptsetup luksAddKey /dev/sdb1 key --pbkdf=pbkdf2 --hash=sha512

Should be working with LUKS1 as well.

  1. Seal key for grub
grub2-protect --action=add --protector=tpm2 --tpm2-keyfile=key --tpm2-outfile=/boot/efi/EFI/opensuse/root.key --tpm2-pcrs=0,2,4,7

The name of oufile must match GRUB_TPM2_SEALED_KEY and the PCR list must match GRUB_TPM2_PCR_LIST

  1. Reboot. You should not be asked for root passphrase anymore. For any additional filesystem you can use LUKS2 with systemd-cryptenroll.

I was not successful in using PCR 8 and 9. Unlocking happens very early and my best guess is that grub2-protect is using current PCR values which are different from what grub2 sees at the time of unlocking (grub2 continues to update PCR 8 and 9 after that). In any case, unlocking happens before grub2 knows the kernel command line. So you cannot prevent intruder from booting with init=/bin/sh with unlocked root unless you also protect grub.cfg from modification. This is different from systemd-cryptenroll where unlocking happens after kernel is booted so full measurements are available. I suppose one could combine both, but for that grub2 should not pass LUKS key to initrd as it does now. Would be valid change request.

Given that you cannot really measure integrity of grub configuration and kernel command line the value of this patch is questionable.

1 Like

I was just testing with Tumbleweed and with MicroOS.
I don’t understand what you mean by copying shim-install from Factory. It looks like it’s already installed. I run in Tumbleweed:

$ sudo zypper install shim
'shim' is already installed
$ sudo shim-install
copying /usr/share/efi/x86_64/grub.efi to /boot/efi/EFI/opensuse/grub.efi
·
·
·

In MicroOS:

$ sudo transactional-update run shim-install 
Checking for newer version.
transactional-update 4.1.0 started
Options: run shim-install
Separate /var detected.
2022-12-24 00:00:47 tukit 4.1.0 started
2022-12-24 00:00:47 Options: -c4 open 
2022-12-24 00:00:47 Using snapshot 4 as base for new snapshot 5.
2022-12-24 00:00:47 Syncing /etc of previous snapshot 3 as base into new snapshot "/.snapshots/5/snapshot"
ID: 5
2022-12-24 00:00:47 Transaction completed.
2022-12-24 00:00:47 tukit 4.1.0 started
2022-12-24 00:00:47 Options: call 5 shim-install 
2022-12-24 00:00:47 Executing `shim-install`:
copying /usr/share/efi/x86_64/grub.efi to /boot/efi/EFI/opensuse/grub.efi
Installing for x86_64-efi platform.
Installation finished. No error reported.
BootCurrent: 0004
Timeout: 0 seconds
BootOrder: 0000,0001,0002,0003
Boot0000* UiApp
Boot0001* UEFI VBOX CD-ROM VB2-01700376 
Boot0002* UEFI ORCL-VBOX-NVME-VER12 VB1234-56789 1
Boot0003* EFI Internal Shell
BootCurrent: 0004
Timeout: 0 seconds
BootOrder: 0004,0000,0001,0002,0003
Boot0000* UiApp
Boot0001* UEFI VBOX CD-ROM VB2-01700376 
Boot0002* UEFI ORCL-VBOX-NVME-VER12 VB1234-56789 1
Boot0003* EFI Internal Shell
Boot0004* opensuse-secureboot
2022-12-24 00:00:48 Application returned with exit status 0.
2022-12-24 00:00:48 Transaction completed.
2022-12-24 00:00:48 tukit 4.1.0 started
2022-12-24 00:00:48 Options: close 5 
2022-12-24 00:00:48 New default snapshot is #5 (/.snapshots/5/snapshot).
2022-12-24 00:00:48 Transaction completed.

Please reboot your machine to activate the changes and avoid data loss.
New default snapshot is #5 (/.snapshots/5/snapshot).
transactional-update finished

I’m sure I’m missing that because It’s not working for me, it continues asking for password.

The steps I have followed are:
1 - Fresh installation of MicroOS without /home partition, only /boot/efi and /
2 - I’ve checked Secure Boot and Trusted Boot state:

$ cat /etc/sysconfig/bootloader  | grep "_BOOT"
SECURE_BOOT="yes"
TRUSTED_BOOT="yes"

3 - I’ve installed TPM2.0 support (I don’t know if it was necessary):

$ sudo transactional-update pkg install tpm2.0-tools && systemctl reboot
$ systemd-cryptenroll --tpm2-device=list
PATH        DEVICE      DRIVER 
/dev/tpmrm0 MSFT0101:00 tpm_tis

4 - I have modified /etc/default/grub:

$ sudo vim /etc/default/grub
GRUB_TPM2_PCR_LIST=0,2,4,7
GRUB_TPM2_SEALED_KEY=sealed_rootfs_key

5 - I’ve updated the bootloader with:

$ sudo transactional-update bootloader && systemctl reboot

6 - I’ve created the keyfile in a encrypted partition:

$ sudo dd if=/dev/urandom of=/home/boot_keyfile bs=1 count=32
$ sudo chmod 0400 /home/boot_keyfile

7 - I’ve added the keyfile to a slot:

$ sudo cryptsetup luksAddKey /dev/nvme0n1p2 /home/boot_keyfile --pbkdf=pbkdf2 --hash=sha512
$ sudo cryptsetup luksDump /dev/nvme0n1p2

8 - I’ve sealed the key for grub and I get a warning:

$ sudo grub2-protect --action=add --protector=tpm2 --tpm2-keyfile=/home/boot_keyfile --tpm2-outfile=/boot/efi/EFI/opensuse/sealed_rootfs_key --tpm2-pcrs=0,2,4,7
Warning: Could not determine GRUB drive for sealed key file.

9 - I reboot and system ask for password before grub.

I’m surprised you don’t use the tpm2_key_protector_init command as the link you posted says.

What you say about security worries me, because if this method is not secure, it doesn’t do its main work. Do you think that protecting grub.cfg from modification would be safe? How is this done? Adjusting permissions or otherwise?

Copy file shim-install from Show openSUSE:Factory / shim - openSUSE Build Service. You need to lo in to download files (same user/password as here).

That is done by shim-install.

This file is on encrypted partition so you need to be root to modify it.

You can password protect editing of boot entries, I believe YaST supports it (should be check box somewhere).

1 Like

Thank you very much for your explanations but I still don’t understand why you download shim-install from Factory and what do you do with that file afterwards. It’s already included in my installations, that is a different version than default one? at what point do you run the shim-install command? Do you use it via transactional-update? I mean:

$ sudo transactional-update run shim-install

Because shim-install in Tumbleweed/MicroOS is too old and does not support TPM unlock.

I replace /usr/sbin/shim-install from MicroOS/Tumbleweed.

Of course. I already wrote that necessary support is not in Tumbleweed yet.

It is run every time bootloader is reconfigured, e.g. with transactional-update bootloader

I have tried several times yesterday and today and it doesn’t work for me.
The system wouldn’t allow me to copy the shim-install version from Factory to the read-only folder /usr/sbin/ (not even using sudo transactional update cp …), so I used a command to modify the contents of the file :

sudo transactional-update run vim /usr/sbin/shim-install

I’ve checked after reboot that the changes are persistent with:

cat /usr/sbin/shim-install | grep "tpm2_key_protector_init"

When starting the operating system it shows me: Failed to unseal sealed key (TPM2_Unseal failed with TSS/TPM error 2461).

The order of commands that I have carried out has been (in addition to the ones that I carried out yesterday):

sudo transactional-update bootloader && systemctl reboot
sudo transactional-update run shim-install && systemctl reboot
sudo grub2-protect --action=add --protector=tpm2 --tpm2-keyfile=/home/boot_keyfile --tpm2-outfile=/boot/efi/EFI/opensuse/sealed_rootfs_key --tpm2-pcrs=0,2,4,7 && systemctl reboot

¿Can you post the output of the following command?

sudo cat /boot/efi/EFI/opensuse/grub.cfg

This is mine:

set btrfs_relative_path="yes"
tpm_record_pcrs 0-9
tpm2_key_protector_init -b sha256 -p 0,2,4,7 -k $prefix/sealed_rootfs_key
if ! cryptomount -u 27023ef8fa664f74834d58fb19d813b7 -k tpm2; then
    cryptomount -u 27023ef8fa664f74834d58fb19d813b7
fi
search --fs-uuid --set=root 2bebad20-6a41-4ede-ae96-30dc7b8fcc69
set prefix=(${root})/boot/grub2
source "${prefix}/grub.cfg"

I think the path $prefix/sealed_rootfs_key is wrong because:

prefix=(${root})/boot/grub2

an my file is in:

--tpm2-outfile=/boot/efi/EFI/opensuse/sealed_rootfs_key

I think it should point to the decrypted partition and therefore it should be:

(hd0,gpt1)/EFI/opensuse/sealed_rootfs_key

I’ve tried to modify the file but nothing happens. I don’t know why it is generated wrong.

Partition scheme of this virtual machine is:

$ lsblk -fm
NAME        FSTYPE      FSVER LABEL       UUID                                   MOUNTPOINTS
nvme0n1
├─nvme0n1p1 vfat        FAT16 EFI         60FD-3E23                              /boot/efi
└─nvme0n1p2 crypto_LUKS 2     ROOTFS_luks 27023ef8-fa66-4f74-834d-58fb19d813b7
  └─cr_root btrfs             ROOTFS      2bebad20-6a41-4ede-ae96-30dc7b8fcc69   (VARIOUS)

The content is correct (I did not cross-check that file name is correct as well and that PCRs match). I get error 2461 when the content of PCRs does not match whatever value was used to seal the key. grub stores PCRs in EFI variable GrubPcrSnapshot. Verify that the content of PCRs you use is the same.

dd if=/sys/firmware/efi/efivars/GrubPcrSnapshot-7ce323f2-b841-4d30-a0e9-5474a76c9a3f bs=1 skip=4
tpm2_pcrread sha256

PCR #4 doesn’t match:

$ dd if=/sys/firmware/efi/efivars/GrubPcrSnapshot-7ce323f2-b841-4d30-a0e9-5474a76c9a3f bs=1 skip=4 | grep '00 sha256\|02 sha256\|04 sha256\|07 sha256'
00 sha256 f6e3278cd99be7e7cf7e2f0b97b64b487091cf8bbf698e6a29ec3e6e99c11d86
02 sha256 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969
04 sha256 3bf0cdf3b54d56a1d70afbbc4384ae039cf83c1f9f6000a1c9ebcb27416375ba
07 sha256 46091803aa093d7fd3efa4cb24c682db860aabce6e6c4a634e01a2732d21a2fe
750+0 records in
750+0 records out
750 bytes copied, 7,13879 s, 0,1 kB/s

$ sudo tpm2_pcrread sha256 | grep '0 :\|2 :\|4 :\|7 :'
    0 : 0xF6E3278CD99BE7E7CF7E2F0B97B64B487091CF8BBF698E6A29EC3E6E99C11D86
    2 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
    4 : 0x9331C7969AD739D172B7CC284627D6BCC5F5BFE6FF9E1D0140895712F709781E
    7 : 0x46091803AA093D7FD3EFA4CB24C682DB860AABCE6E6C4A634E01A2732D21A2FE

The TCG PC Client Specific Platform Firmware Profile Specification defines the registers in use:
PCR4. Use: Boot Manager Code and Boot Attempts. Notes: Measures the boot manager and the devices that the firmware tried to boot from.

I’ve updated the sealed file and I get the warning:

$ sudo grub2-protect --action=add --protector=tpm2 --tpm2-keyfile=/home/boot_keyfile --tpm2-outfile=/boot/efi/EFI/opensuse/sealed_rootfs_key --tpm2-pcrs=0,2,4,7
Warning: Could not determine GRUB drive for sealed key file.

I reboot and I get the same as the previous screenshot. Error 2461.
I check the hashes again and they have not changed:

$ dd if=/sys/firmware/efi/efivars/GrubPcrSnapshot-7ce323f2-b841-4d30-a0e9-5474a76c9a3f bs=1 skip=4 | grep '04 sha256'
04 sha256 3bf0cdf3b54d56a1d70afbbc4384ae039cf83c1f9f6000a1c9ebcb27416375ba

$ sudo tpm2_pcrread sha256 | grep '4 :'
    4 : 0x9331C7969AD739D172B7CC284627D6BCC5F5BFE6FF9E1D0140895712F709781E

I have also tried to run again:

sudo transactional-update bootloader && systemctl reboot
sudo transactional-update run shim-install && systemctl reboot

…and the hashes don’t change.
I don’t know if PCR #4 hash is not updating in efivars file, or if it’s updating wrong.

Run tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements and upload full output to https://susepaste.org/ (or here if this forum allows uploading of this file).

Here it is:
https://susepaste.org/48205962

Yes, after grub is already loaded there is event extending PCR 4:

- EventNum: 135
  PCRIndex: 9
  EventType: EV_IPL
...
  Event:
    String: |-
      /@/.snapshots/2/snapshot/boot/vmlinuz-6.0.8-8-default

This is grub loading kernel and measuring it into PCR 9

- EventNum: 136
  PCRIndex: 4
  EventType: EV_EFI_BOOT_SERVICES_APPLICATION

And immediately after that something extends PCR 4. I think it is shim which verifies loaded kernel (it certainly has code to produce log event with correct type).

Except I do not see it on Tumbleweed, I see it on ALP which has different shim. So I am not sure what does it it in your case.

ALP has different implementation - it computes PCR values using event log up to the point where cryptomount is called. So it works even if PCR values are changed later. But Tumbleweed does not include these packages (fde-tools and dependencies).

1 Like

Thanks for your help.
I have installed fde-tools and its dependency pcr-oracle just in case, but everything stays the same.
In the end I have removed the PCR 4 and it works. I wanted to use it, but for now I’ll leave it out until I find a solution.
I guess with kernels updates, grub2, initrd rebuilds, etc… PCR hashes will change, what commands from the list below should I use to update the unlocking of luks volumes?

sudo transactional-update bootloader
sudo transactional-update run shim-install
sudo grub2-protect --action=add --protector=tpm2 --tpm2-keyfile=/home/boot_keyfile --tpm2-outfile=/boot/efi/EFI/opensuse/sealed_rootfs_key --tpm2-pcrs=0,2,7

A couple of weeks ago I was asking:

Would it be possible to create a script that reads the used PCR registers at startup and, if those registers have been modified due to an update, it does the re-enroll when the equipment is turning off?

A possible solution could be a script that runs when the OS schedules a reboot, disables all but a few PCRs that don’t change, does the reboot, and once powered on, checks if the PCRs being used are all the ones desired or are missing, if they are missing then it run the appropriate commands again to restore them.
It’s the best idea I’ve thought of but it looks a bit dirty. I don’t know how Windows does it.

It is not enough to install it. You need to use fdectl to seal the key: fdectl tpm-enable (from memory, check source, it is all shell script).

1 Like