Re: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase
- Reply: Stanislaw Adaszewski : "Re: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase"
- Reply: Stanislaw Adaszewski : "Re: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase"
- Reply: Graham Perrin : "OpenZFS: FreeBSD bootloader support for encrypted file systems (was: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase)"
- In reply to: Warner Losh : "Re: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase"
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 27 Nov 2021 20:35:32 UTC
Hi Warner, Thanks a lot for the quick reaction - that helps. Accordingly, I have taken several actions (below). If you have any more tips how to push this forward, please let me know. Like I don't know is there a person formally responsible for this kind of contributions? Let's say when you are happy with the general architecture of the solution and the quality of the code (it still requires some polishing) - is that enough to pull the changes into the codebase? How does that work? Thank you in advance! 1. I have rebased my changeset on top of the tip of the FreeBSD's main branch [1] 2. I have changed the /.passphrase_marker convention to hold (instead of the passphrase) a human-readable lower-case digest of SHA256(salt | passphrase) where salt is a new (optional) parameter which can be passed using another EFI variable: KernGeomEliPassphraseFromTpm2Salt. I think it is more for the peace of mind than anything else, as anyone having access to /.passphrase_marker would normally have to be the root user. Nevertheless it is perhaps nicer not to keep raw passphrases laying around in files. We still pass the passphrase to the kernel via a kernel variable kern.geom.eli.passphrase.from_tpm2.passphrase which is unset before the system becomes interactive. I could be easily passing the hash computed by the bootloader instead - what do you think? One thing to keep in mind is that another kernel variable - kern.geom.eli.passphrase has been passed around like this as well and it is being unset precisely in init_main.c. But even more importantly, one has to keep in mind that geli_export_key_buffer() passes all GELI keys to the kernel anyway, so access to the encrypted drives is already possible by that alone. Not to mention that the root user can simply read the driver's master key with a simple geli show. 3) As an explanation, also to @Ka Ho Ng, the /.passphrase_marker serves only as a tag to allow to reliably pair a boot filesystem to the keyphrase retrieved from the TPM. If we were to just retrieve the passphrase and pass it to any boot environment then one would simply boot another OS with another root password and could read all our secrets. The same goes for the root filesystem that is mounted in turn by the kernel. If one would for example remove the boot drive - that would cause the kernel to drop out to interactive mountfrom> prompt and then one could for example boot from another drive. That is unacceptable. Kernel is by default very "boot happy" - it tries really hard to boot SOMETHING rather than accept a strict specification of what is allowed to boot. 4) The code is fully functional and I have tested it quite a bit on a Zotac CI622 mini-PC with the latest BIOS update which enables the TPM functionality on the Intel 10110U process in there. If you have a TPM-capable BIOS and CPU, I would encourage you to try, like this you will understand better how it works and that the design is necessary like it is with the /.passphrase_marker. I am not 100% sure if there are no ways left to fool the system to boot or run some arbitrary code. Such attacks would generally consist of causing some kind of errors on the "trusted" UFS-on-GELI or ZFS-on-GELI systems and making the system drop out into some kind of interactive prompts. I hope that does not happen. For example if one removes the drive during init. I would certainly expect that no process drops out to interactive prompts on a system with a root password set bit this kind of scares is a tradeoff of not using full VERIEXEC. It is however very convenient to say - just trust everything on the XXX-on-GELI since this was encrypted and therefore tampering-proof. More tests are necessary but also - VERIEXEC can be enabled in addition to that to ensure that such weird scenarios do not happen. 5) @Ka Ho Ng what you said is taking place clearly, the code relies on a PCR policy of user's choice and uses that to read the passphrase. 6) Regarding ZFS encryption I am not sure if that is supported in the EFI bootloader - at first glance I would say that it isn't. With that said, the code can be used to further modify the loader to read any kind of values stored in the TPM and put them in kernel variables for later use in the boot process. Just a big WARNING, kenv seems to let even lusers to read the variables. So whatever one would do with them, it would have to be done quickly and the variables would need to be discarded before letting the lusers log in. [1] https://github.com/sadaszewski/freebsd-src/compare/main...main-cherry-pick-tpm-support-in-stand Kind regards, -- Stanislaw On Sat, 27 Nov 2021 at 18:00, Warner Losh <imp@bsdimp.com> wrote: > > > > On Sat, Nov 27, 2021, 7:36 AM Stanislaw Adaszewski <s.adaszewski@gmail.com> wrote: >> >> Dear All, >> >> Could you please guide me so that we can together integrate >> the following piece of work into the FreeBSD base system? >> Thank you for your time and consideration. > > > See below for some advice. > >> I have created the following bundle of work [1]. The referenced >> patch implements on top of releng/13.0, the support for TPM2 >> in the EFI bootloader and in the kernel in order to allow for >> storage and retrievel of a GELI passphrase in a TPM2 module, >> secured with a PCR policy. >> >> The way the bootloader behavior is modified is the following: >> >> 1) before calling efipart_inithandles() an attempt to retrieve the >> passphrase from a TPM2 module might be performed - >> how this is achieved is described later on. >> 2) if a passphrase is indeed retrieved, then after determining >> currdev, the currdev is checked for the presence of a >> /.passphrase_marker file which must contain the same passphrase >> as retrieved from the TPM. This is supposed to ensure that we >> do not end up booting an environment not on the device we just >> unlocked with the passphrase. >> 3a) If all is go, the autoboot_delay is set to -1 in order to prevent >> any interaction and continue the boot process of the "safe" environment, >> a 'kern.geom.eli.passphrase.from_tpm2.passphrase' variable is set >> to the passphrase from TPM in order for kernel use later, as well as a >> kern.geom.eli.passphrase.from_tpm2.was_retrieved'='1' variable. >> 3b) If the passphrase marker does not match, the bootloader cleans up >> GELI keys, the TPM passphrase and kern.geom.eli.passphrase and exits. > > > I worry about information disclosure having the pass phrase available on the running system with this setup. Can you explain why that design point was selected? Usually there is something signed with a private key that the public key can be used to verify instead of a direct comparison like this to prevent disclosure of key material. I've not looked at the code yet, so it may already do this... > >> The way the kernel behavior is modified is the following: >> >> 1) In init_main.c, after vfs_mountroot() a check is added >> 2a) If kern.geom.eli.passphrase.from_tpm2.was_retrieved is not >> set to 1, then we do nothing and continue the boot process >> 2b) If the was_retrieved variable is set to '1' then we check for the >> same passphrase marker as the bootloader, its content compared >> against the 'kern.geom.eli.passphrase.from_tpm2.passphrase' >> variable. >> 3a) If all is go, the passphrase variable is unset and the boot process >> continues, >> 3c) If the passphrase marker does not match, we panic. > > > I'm sure that main_init should not know about geom or geli. This is usually done with a handler of some sort so the mountroot code can just call the generic handler. Can your code be restructured such that this is possible? The reason I ask is that ZFS supports encryption too and it would be nice to use that instead of geli. > >> The configuration of the bootloader for this procedure looks the following: >> >> 1) We set an efivar KernGeomEliPassphraseFromTpm2NvIndex >> to contain the TPM2 NV Index we store our passphrase in, e.g. 0x1000001 >> 2) We set an efivar KernGeomEfiPassphraseFromTpm2PolicyPcr >> to contain the PCR policy used to secure the passphrase, e.g. >> sha256:0,2,4,7 >> 3a) If both are set, the bootloader will attempt to retrieve the passphrase >> and behave in the modified way described above >> 3b) Otherwise, it behaves as the vanilla version and will ask for GELI >> passphrases if necessary >> >> The configuration of the TPM and the passphrase marker looks the following: >> >> 1) echo -n "passphrase" >/.passphrase_marker >> 2) chmod 600 /.passphrase_marker >> 3) tpm2_createpolicy -L policy.digest --policy-pcr -l sha256:0,2,4,7 >> 4) tpm2_nvdefine -Q 0x1000001 -s `wc -c /.passphrase_marker` -L >> policy.digest -A "policyread|policywrite" >> 5) tpm2_nvwrite -Q 0x1000001 -i /.passphrase_marker -P pcr:sha256:0,2,4,7 >> >> [1] https://github.com/sadaszewski/freebsd-src/compare/releng/13.0...tpm-support-in-stand > > > This sounds cool. Any chance you can rebase this to the tip of the main branch? All code goes into FreeBSD that way and 13.0 is about a year old already. > > Thanks for sharing this. Despite some reservations expressed above, I think this has potential to be quite cool. > > Warner > >> >> Kind regards,