I probably need to note that TPM2 unlocking is dangerous and difficult to do correctly. Not impossible, but difficult (and I’ve yet to see it done correctly in any online guide with NixOS). For instance with this guide, an attacker could decrypt the hard drive if they stole the laptop. If the attacker took out the hard drive, backed up its (ciphertext) contents, and then replaced the root partition with a malicious one for which they know the passphrase, the system would happily boot the unmodified ESP, which would then boot the malicious root partition, and they’d be running an OS that they control. Then they can plug in their ciphertext backup and use the TPM2 to decrypt it because secure boot was still honored and therefore PCRS 0,1,2,3,7 are all still valid.
And there are a variety of different ways to attack at this angle that I’ve explored and demonstrated. The way to defend against this is with something like systemd-pcrphase
or systemd-pcrlock
, where you guarantee that the TPM2 state changes before handing control over to potentially compromised code. But lanzaboote isn’t set up for those specifically yet, so I use a slightly different mechanism in my system.
I also bind my disk to PCR 15:sha256=00000...
(meaning PCR 15 must have no measurements in order to decrypt), and add crypttabExtraOpts = [ "tpm2-measure-pcr=yes" ];
(meaning that it will measure the decrypted LUKS volume key into PCR 15). So at first, PCR 15 is zero, and stage 1 will decrypt a disk. The volume key is then measured into PCR 15, so now I know that my real disk cannot be decrypted anymore. Only after this point is control handed over to a possibly malicious stage 2. But you have to be absolutely sure that this PCR 15 measurement will definitely take place before handing over control. For most systems this is as simple as ensuring the root device is /dev/mapper/<name>
rather than /dev/disk/by-uuid/asdf
or anything else, because the mapper path imposes the necessary systemd ordering.
Of course it’d be nice to have proper stage 2 verification to avoid all of this, but that’s significantly more difficult, and it doesn’t help in the case where you want a vendor-signed OS, so these TPM2 state changes are still necessary in the grand scheme.
EDIT: Another one of my systems does this nonsense, which only auto-unlocks a zvol with SSH host keys and Tailscale state on it and requires a passphrase (as pin for TPM2) for the root fs. That way I can log in remotely during stage 1 using Tailscale+SSH, know that secure boot was honored because the system was able to decrypt those keys, and manually enter a TPM2 pin that should only work for my actual root fs.