From nobody Mon Nov 29 16:12:35 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 B6BC518CCDDC for ; Mon, 29 Nov 2021 16:13:15 +0000 (UTC) (envelope-from s.adaszewski@gmail.com) Received: from mail-ed1-x536.google.com (mail-ed1-x536.google.com [IPv6:2a00:1450:4864:20::536]) (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 4J2r4B4Ykpz3Kf4 for ; Mon, 29 Nov 2021 16:13:14 +0000 (UTC) (envelope-from s.adaszewski@gmail.com) Received: by mail-ed1-x536.google.com with SMTP id w1so74362004edc.6 for ; Mon, 29 Nov 2021 08:13:14 -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 :cc:content-transfer-encoding; bh=FFavrgx+cnUY8Hu9SfkouOD7h58pnDt7Euzb2KK2vgk=; b=P0/uB5HVHOIqSglGbqPRih+ADGp525CxnlbKYXdI14s7uCiTyRePISEfHi0ZjP48w3 JLKJB1O8IAmbutLAylNh/6iwR0oAD245GRKtKZ7KpKqdNV4XJtC7iZadBYcBIR6JlJsG XgQXD04SFq8P2h8FSihaoEkgxJTCIRGXwj1iKEsf4NJyl968nrnIXJC5eNbLv/mRJuqp GPYtSmkzHE5dqClhBM+MQ2rj/5fpySkom9cdbjMJRG5Nlj2ZLf4VE9xqyixikNqqaVk7 Y4zgRGN4NpRCY+gY494O3n7BAj/smI50qJdWn1QbkoVWIsJ2nGsNLho56D4VS3BApkEI LCYQ== 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:cc:content-transfer-encoding; bh=FFavrgx+cnUY8Hu9SfkouOD7h58pnDt7Euzb2KK2vgk=; b=t3UBIwHg/8QCeaXmWWT50iL1GkdDV4KYiNZajoS/66VGctEanrH8NO8KOc9NlI6DaL wHO3FSpk5C3tytCOIaJYRAUUv1PjhN5mboet9aMyVWKq+4FE6BTib8nzQIDQfzKOSI24 EPDQf+ZdUvGUbcA8bCMiQE4XD2JwPcNL7K4oFbd/lY94xLxdPhg+rcY65A5zyRDDX5yN z7Ybio4ehjXa+ZfnSl9CtTiXmp5lnFzMnt8U7vuFXnw+70GA3kryDmUZ7eC44rtko9ir QsnAfeJ/cxauNylKwD9ggOylTjf98s4SVWaX4/ZummI54dG9f0lXvwzutmnw403FV9SS waiA== X-Gm-Message-State: AOAM532d8ymQTPE6JMkwmrUXDE3OpopTM8a9+WSiWtqRWp0nv/GruDKT GH/FvkNhXMY4RHLFF23kAnmoTG0UN5qqHBKlBukIs3pZ X-Google-Smtp-Source: ABdhPJxtEtK4RuvTfXIFTxN3Gw4C7o5pehCTn+h2q+bxxuuqSDl9ZgHOyDZX5nI581XmubU9oHENg75KOEQT4GL4CZk= X-Received: by 2002:a17:906:d54d:: with SMTP id cr13mr17625597ejc.409.1638202392111; Mon, 29 Nov 2021 08:13:12 -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: Mon, 29 Nov 2021 17:12:35 +0100 Message-ID: Subject: Re: TPM2 Support in bootloader / kernel in order to retrieve GELI passphrase To: Warner Losh Cc: FreeBSD Hackers Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Rspamd-Queue-Id: 4J2r4B4Ykpz3Kf4 X-Spamd-Bar: / Authentication-Results: mx1.freebsd.org; dkim=pass header.d=gmail.com header.s=20210112 header.b="P0/uB5HV"; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (mx1.freebsd.org: domain of sadaszewski@gmail.com designates 2a00:1450:4864:20::536 as permitted sender) smtp.mailfrom=sadaszewski@gmail.com X-Spamd-Result: default: False [0.65 / 15.00]; ARC_NA(0.00)[]; R_DKIM_ALLOW(-0.20)[gmail.com:s=20210112]; FROM_HAS_DN(0.00)[]; DWL_DNSWL_NONE(0.00)[gmail.com:dkim]; R_SPF_ALLOW(-0.20)[+ip6:2a00:1450:4000::/36:c]; FREEMAIL_FROM(0.00)[gmail.com]; MIME_GOOD(-0.10)[text/plain]; PREVIOUSLY_DELIVERED(0.00)[freebsd-hackers@freebsd.org]; NEURAL_SPAM_MEDIUM(0.22)[0.225]; NEURAL_SPAM_SHORT(0.42)[0.421]; TO_MATCH_ENVRCPT_SOME(0.00)[]; TO_DN_ALL(0.00)[]; DKIM_TRACE(0.00)[gmail.com:+]; RCPT_COUNT_TWO(0.00)[2]; DMARC_POLICY_ALLOW(-0.50)[gmail.com,none]; RCVD_IN_DNSWL_NONE(0.00)[2a00:1450:4864:20::536:from]; MID_RHS_MATCH_FROMTLD(0.00)[]; NEURAL_SPAM_LONG(1.00)[1.000]; FROM_EQ_ENVFROM(0.00)[]; MIME_TRACE(0.00)[0:+]; FREEMAIL_ENVFROM(0.00)[gmail.com]; ASN(0.00)[asn:15169, ipnet:2a00:1450::/32, country:US]; TAGGED_FROM(0.00)[]; RCVD_TLS_ALL(0.00)[]; RCVD_COUNT_TWO(0.00)[2] X-ThisMailContainsUnwantedMimeParts: N Made a handful of other changes: 1) Prevent interaction (exit) if autoboot fails (stand/common/interp.c) 2) Wipe env vars and GELI keys before panic()'ing the kernel in case the passphrase marker does not match - probably does not really help much but I guess the idea to narrow the time window any secrets are in memory a good rule of thumb, 3) Call Tpm2NvReadLock() right after retrieving the passphrase from TPM in the bootloader. Therefore, it cannot be read again from the TPM until th= e next restart. Thus, it remains unavailable for example in the booted OS. On Sun, 28 Nov 2021 at 12:19, Stanislaw Adaszewski wrote: > > I have made further modifications: > > 1) As per suggestion - use SYSINIT() and EVENTHANDLER_REGISTER() > in a separate file (tpm2cpm.c) rather than hardcoding in init_main.c. > > 2) Make the functionality configurable / optional both for the kernel > (option TPM2_PASSPHRASE) and the bootloader > (WITH_LOADER_TPM2_PASSPHRASE). The one for the bootloader > is enabled by default and the one for the kernel is enabled by default > in the GENERIC amd64 kernel. > > Best 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 = init. > > I would certainly expect that no process drops out to interactive promp= ts > > 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 encryp= ted > > and therefore tampering-proof. More tests are necessary but also - VERI= EXEC > > 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 E= FI > > 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-cher= ry-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 preven= t > > >> any interaction and continue the boot process of the "safe" environm= ent, > > >> 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 u= p > > >> 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 poi= nt 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 th= is 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 th= e > > >> 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 proc= ess > > >> 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 u= sually 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 possi= ble? 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 fol= lowing: > > >> > > >> 1) We set an efivar KernGeomEliPassphraseFromTpm2NvIndex > > >> to contain the TPM2 NV Index we store our passphrase in, e.g. 0x1000= 001 > > >> 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 pas= sphrase > > >> and behave in the modified way described above > > >> 3b) Otherwise, it behaves as the vanilla version and will ask for GE= LI > > >> passphrases if necessary > > >> > > >> The configuration of the TPM and the passphrase marker looks the fol= lowing: > > >> > > >> 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 ma= in 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,