From nobody Sun Nov 28 08:13:23 2021 X-Original-To: freebsd-hackers@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 664A018BC085 for ; Sun, 28 Nov 2021 08:13:55 +0000 (UTC) (envelope-from s.adaszewski@gmail.com) Received: from mail-ed1-x52e.google.com (mail-ed1-x52e.google.com [IPv6:2a00:1450:4864:20::52e]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (2048 bits) client-digest SHA256) (Client CN "smtp.gmail.com", Issuer "GTS CA 1D4" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4J21Tb2D2Xz4cTZ for ; Sun, 28 Nov 2021 08:13:55 +0000 (UTC) (envelope-from s.adaszewski@gmail.com) Received: by mail-ed1-x52e.google.com with SMTP id x6so58063968edr.5 for ; Sun, 28 Nov 2021 00:13:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :content-transfer-encoding; bh=wphHAr5Qdp2Kt4KWyqlhohEg78jy6VYS0CAxQPET7U8=; b=LEaejDNLJTW4qdBUjoW4iB6Y5W+JeibungtLU95ln5t6qfCcYfw7eCq+9bpjT4RMZa 4rmuAlj05kruOrewCjwPAptRrHaAMDmPoAOeMqY2AEgPBw0R56lVLPHRBSj+7Wr8uAn/ kMnf5mUGcdlZr0oB31eVDbu3t2GuH8EPpPxwsiFVJQk484639dR4NI9soEnaPAl7YrNO KeCZr3h62daiuY603ASYQjIebDqwVGEBDE6kej2NAvVSkib+z3jZ+suUaVN8ctlStCJd No1ft1GR2RPMo6DruIZ368SHlV4GvAIT8iQf+4OATJep67xp2Qhex3bjHWaK2stUXmqb noPA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:content-transfer-encoding; bh=wphHAr5Qdp2Kt4KWyqlhohEg78jy6VYS0CAxQPET7U8=; b=e1Q9GfNd4L3PetaZVNX9nAvqe+VE0RzUF9pGdk9AIGGO8ngnTGXk7yTDSp3ZCf2jhv BP3w6Xg9Soq/1FCfIgWjkUwavr0j0wCSD2YjNSZrqwUon+GbNzV57HjW7FJ/PtkBy4L1 y1tKpHAoQpE8AQu7WwU0tpyx01w8lV+ATzbI0CJpwH7jJToxRRK8peKaW+AFnt935j6R jJFFEDHpDyWhDh+kS+Zh/TSAxrmCGeo8gD72R2Trxg66bf1ACpYZrMIDxuaYnZY9KARx kmFj+dphlnM91/2FYJKRuKtC5eDaSff/uIgkrpFv3pM/g0P/O0W7m+9UrpGSiL/FxkPP cOOQ== X-Gm-Message-State: AOAM533j4feHPOmBMvyMSqesH5vykra1sWufvfd9dOP91h1r2faEv356 fB/6mv2YWxb7ng9PWp6iYJrSiyQAFV4TJ5fokapPaJV7 X-Google-Smtp-Source: ABdhPJxH/vhNwUoZpDA0sYi1k+JbVyao+jUnGBAW+NZRx+EIjiZA6Vjo1k1uIYfqvX5c91fZibo5W7yeklH73XNu5Kw= X-Received: by 2002:a50:d492:: with SMTP id s18mr64458786edi.145.1638087234250; Sun, 28 Nov 2021 00:13:54 -0800 (PST) List-Id: Technical discussions relating to FreeBSD List-Archive: https://lists.freebsd.org/archives/freebsd-hackers List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-freebsd-hackers@freebsd.org MIME-Version: 1.0 References: In-Reply-To: From: Stanislaw Adaszewski Date: Sun, 28 Nov 2021 09:13:23 +0100 Message-ID: Subject: Re: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase To: FreeBSD Hackers Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Rspamd-Queue-Id: 4J21Tb2D2Xz4cTZ X-Spamd-Bar: ---- Authentication-Results: mx1.freebsd.org; none X-Spamd-Result: default: False [-4.00 / 15.00]; REPLY(-4.00)[]; TAGGED_FROM(0.00)[] X-ThisMailContainsUnwantedMimeParts: N Hi Ka Ho Ng, Thanks for the interesting remarks! > I believe putting that somewhere as container metadata for consumption by= geom_eli > would be more favorable for such usage. That should make it look more tai= lored given > encryption is a volume property rather. The system does not think in terms of GELI anymore when it considers a rootfs. For a ZFS-on-GELI rootfs it is passed from the bootloader at as zfs:zroot/ROOT/default: and that's it, no mention of GELI. See below - the drive could be substituted for a hostile one before the kernel gets to mounting the rootfs. A check for the /.passphrase_marker is necessary AFTER vfs_mountroot(). The root ZFS could be as well spanning multiple GELI devices, etc. It is simply on a higher level than GELI. > In a strict environment it might be better to simply drop the prompt and = directly > panic instead. This could be implemented as well but is currently not done as it would not be enough to ensure a trusted rootfs. Conversely, after ANY successful vfs_mountroot(), the rootfs is checked for the presence of the correct /.passphrase_marker and we panic if this is not the case. This makes for a real protection. You could imagine unplugging the boot dri= ve in the early stages of kernel runtime (before mounting the rootfs) and pluggin= g in another one with a ZFS pool (with the same name) on an unencrypted partition - AFAIK the kernel would pick it up without any complaint and boot straight into a hostile userland while keeping all the secrets available in memory. In fact, if you re-plugged the boot drive then, it would use the cached GELI keys and decrypt it straight away. > Despite tpm2_nvwrite is used instead of sealing. TPM non-volatile storage= is not > something we can spare as they are limited resources. Sealing does not re= quire > non-volatile storage in TPM module, while achieving what you have describ= ed. The TPM PC platform specification says: "1.The TPM SHALL provide a minimum = of 6962 bytes of NV Storage.". The passphrase is only using a dozen. That shou= ld be fine. Aren't sealed persistent objects on the TPM eating into the same memory anyway? Perhaps you could elaborate? In either case switching to sealed objects (if there really is a rationale) would be a minor change. Kind regards, -- Stanislaw On Sat, 27 Nov 2021 at 21:35, Stanislaw Adaszewski wrote: > > 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 in= it. > 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 encrypte= d > and therefore tampering-proof. More tests are necessary but also - VERIEX= EC > 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 do= ne > 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 wrote: > > > > > > > > On Sat, Nov 27, 2021, 7:36 AM Stanislaw Adaszewski 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" environmen= t, > >> 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'=3D'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 o= n 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 th= e 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 proces= s > >> 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 usu= ally done with a handler of some sort so the mountroot code can just call t= he generic handler. Can your code be restructured such that this is possibl= e? The reason I ask is that ZFS supports encryption too and it would be ni= ce to use that instead of geli. > > > >> The configuration of the bootloader for this procedure looks the follo= wing: > >> > >> 1) We set an efivar KernGeomEliPassphraseFromTpm2NvIndex > >> to contain the TPM2 NV Index we store our passphrase in, e.g. 0x100000= 1 > >> 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 passp= hrase > >> 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 follo= wing: > >> > >> 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...t= pm-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 a= lready. > > > > Thanks for sharing this. Despite some reservations expressed above, I t= hink this has potential to be quite cool. > > > > Warner > > > >> > >> Kind regards,