On Fri, Jun 28, 2024 at 04:19:01PM +0800, Gary Lin via Grub-devel wrote: > From: Hernan Gatta <hega...@linux.microsoft.com> > > To utilize the key protectors framework, there must be a way to protect > full-disk encryption keys in the first place. The grub-protect tool > includes support for the TPM2 key protector but other protectors that > require setup ahead of time can be supported in the future. > > For the TPM2 key protector, the intended flow is for a user to have a > LUKS 1 or LUKS 2-protected fully-encrypted disk. The user then creates a > new LUKS key file, say by reading /dev/urandom into a file, and creates > a new LUKS key slot for this key. Then, the user invokes the grub-protect > tool to seal this key file to a set of PCRs using the system's TPM 2.0. > The resulting sealed key file is stored in an unencrypted partition such > as the EFI System Partition (ESP) so that GRUB may read it. The user also > has to ensure the cryptomount command is included in GRUB's boot script > and that it carries the requisite key protector (-P) parameter. > > Sample usage: > > $ dd if=/dev/urandom of=luks-key bs=1 count=32 > $ sudo cryptsetup luksAddKey /dev/sdb1 luks-key --pbkdf=pbkdf2 --hash=sha512 > > To seal the key with TPM 2.0 Key File (recommended): > > $ sudo grub-protect --action=add \ > --protector=tpm2 \ > --tpm2-pcrs=0,2,4,7,9 \ > --tpm2key \ > --tpm2-keyfile=luks-key \ > --tpm2-outfile=/boot/efi/boot/grub2/sealed.tpm > > Or, to seal the key with the raw sealed key: > > $ sudo grub-protect --action=add \ > --protector=tpm2 \ > --tpm2-pcrs=0,2,4,7,9 \ > --tpm2-keyfile=luks-key \ > --tpm2-outfile=/boot/efi/boot/grub2/sealed.key > > Then, in the boot script, for TPM 2.0 Key File: > > tpm2_key_protector_init --tpm2key=(hd0,gpt1)/boot/grub2/sealed.tpm > cryptomount -u <SDB1_UUID> -P tpm2 > > Or, for the raw sealed key: > > tpm2_key_protector_init --keyfile=(hd0,gpt1)/boot/grub2/sealed.key > --pcrs=0,2,4,7,9 > cryptomount -u <SDB1_UUID> -P tpm2 > > The benefit of using TPM 2.0 Key File is that the PCR set is already > written in the key file, so there is no need to specify PCRs when > invoking tpm2_key_protector_init.
I think most if not all of this commit message should go to the GRUB documentation too. > Cc: Stefan Berger <stef...@linux.ibm.com> > Signed-off-by: Hernan Gatta <hega...@linux.microsoft.com> > Signed-off-by: Gary Lin <g...@suse.com> > --- > .gitignore | 2 + > Makefile.util.def | 26 + > configure.ac | 30 + > docs/man/grub-protect.h2m | 4 + > util/grub-protect.c | 1423 +++++++++++++++++++++++++++++++++++++ > 5 files changed, 1485 insertions(+) > create mode 100644 docs/man/grub-protect.h2m > create mode 100644 util/grub-protect.c > > diff --git a/.gitignore b/.gitignore > index 4c1f91db8..2105d87c8 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -169,6 +169,8 @@ widthspec.bin > /grub-ofpathname.exe > /grub-probe > /grub-probe.exe > +/grub-protect > +/grub-protect.exe > /grub-reboot > /grub-render-label > /grub-render-label.exe > diff --git a/Makefile.util.def b/Makefile.util.def > index fb82f59a0..074c0aff7 100644 > --- a/Makefile.util.def > +++ b/Makefile.util.def > @@ -208,6 +208,32 @@ program = { > ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; > }; > > +program = { > + name = grub-protect; > + mansection = 1; > + > + common = grub-core/kern/emu/argp_common.c; > + common = grub-core/osdep/init.c; > + common = grub-core/lib/tss2/buffer.c; > + common = grub-core/lib/tss2/tss2_mu.c; > + common = grub-core/lib/tss2/tpm2_cmd.c; > + common = grub-core/commands/tpm2_key_protector/args.c; > + common = grub-core/commands/tpm2_key_protector/tpm2key_asn1_tab.c; > + common = util/grub-protect.c; > + common = util/probe.c; > + > + cflags = '-I$(srcdir)/grub-core/lib/tss2 > -I$(srcdir)/grub-core/commands/tpm2_key_protector'; > + > + ldadd = libgrubmods.a; > + ldadd = libgrubgcry.a; > + ldadd = libgrubkern.a; > + ldadd = grub-core/lib/gnulib/libgnu.a; > + ldadd = '$(LIBTASN1)'; > + ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) > $(LIBGEOM)'; > + > + condition = COND_GRUB_PROTECT; > +}; > + > program = { > name = grub-mkrelpath; > mansection = 1; > diff --git a/configure.ac b/configure.ac > index d4a14bf93..12681c19c 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -76,6 +76,7 @@ grub_TRANSFORM([grub-mkpasswd-pbkdf2]) > grub_TRANSFORM([grub-mkrelpath]) > grub_TRANSFORM([grub-mkrescue]) > grub_TRANSFORM([grub-probe]) > +grub_TRANSFORM([grub-protect]) > grub_TRANSFORM([grub-reboot]) > grub_TRANSFORM([grub-script-check]) > grub_TRANSFORM([grub-set-default]) > @@ -2057,6 +2058,29 @@ fi > AC_SUBST([LIBZFS]) > AC_SUBST([LIBNVPAIR]) > > +AC_ARG_ENABLE([grub-protect], > + [AS_HELP_STRING([--enable-grub-protect], > + [build and install the `grub-protect' utility > (default=guessed)])]) > +if test x"$enable_grub_protect" = xno ; then > + grub_protect_excuse="explicitly disabled" > +fi > + > +LIBTASN1= > +if test x"$grub_protect_excuse" = x ; then > + AC_CHECK_LIB([tasn1], [asn1_write_value], [LIBTASN1="-ltasn1"], > [grub_protect_excuse="need libtasn1 library"]) > +fi > +AC_SUBST([LIBTASN1]) > + > +if test x"$enable_grub_protect" = xyes && test x"$grub_protect_excuse" != x > ; then > + AC_MSG_ERROR([grub-protect was explicitly requested but can't be compiled > ($grub_protect_excuse)]) > +fi > +if test x"$grub_protect_excuse" = x ; then > +enable_grub_protect=yes > +else > +enable_grub_protect=no > +fi > +AC_SUBST([enable_grub_protect]) > + > LIBS="" > > AC_SUBST([FONT_SOURCE]) > @@ -2173,6 +2197,7 @@ AM_CONDITIONAL([COND_GRUB_EMU_SDL], [test > x$enable_grub_emu_sdl = xyes]) > AM_CONDITIONAL([COND_GRUB_EMU_PCI], [test x$enable_grub_emu_pci = xyes]) > AM_CONDITIONAL([COND_GRUB_MKFONT], [test x$enable_grub_mkfont = xyes]) > AM_CONDITIONAL([COND_GRUB_MOUNT], [test x$enable_grub_mount = xyes]) > +AM_CONDITIONAL([COND_GRUB_PROTECT], [test x$enable_grub_protect = xyes]) > AM_CONDITIONAL([COND_HAVE_FONT_SOURCE], [test x$FONT_SOURCE != x]) > if test x$FONT_SOURCE != x ; then > HAVE_FONT_SOURCE=1 > @@ -2300,6 +2325,11 @@ echo grub-mount: Yes > else > echo grub-mount: No "($grub_mount_excuse)" > fi > +if [ x"$grub_protect_excuse" = x ]; then > +echo grub-protect: Yes > +else > +echo grub-protect: No "($grub_protect_excuse)" > +fi > if [ x"$starfield_excuse" = x ]; then > echo starfield theme: Yes > echo With DejaVuSans font from $DJVU_FONT_SOURCE > diff --git a/docs/man/grub-protect.h2m b/docs/man/grub-protect.h2m > new file mode 100644 > index 000000000..3bfa8b645 > --- /dev/null > +++ b/docs/man/grub-protect.h2m > @@ -0,0 +1,4 @@ > +[NAME] > +grub-protect \- protect a disk key with a key protector > +[DESCRIPTION] > +grub-protect helps to pretect a disk encryption key with a specified key > protector. Please expand this too... > diff --git a/util/grub-protect.c b/util/grub-protect.c > new file mode 100644 > index 000000000..62b764b41 > --- /dev/null > +++ b/util/grub-protect.c > @@ -0,0 +1,1423 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * Copyright (C) 2024 Free Software Foundation, Inc. > + * Copyright (C) 2023 SUSE LLC > + * Copyright (C) 2022 Microsoft Corporation > + * > + * GRUB is free software: you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation, either version 3 of the License, or > + * (at your option) any later version. > + * > + * GRUB is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. > + */ > + > +#include <config.h> > + > +#include <errno.h> > +#include <fcntl.h> > +#include <libtasn1.h> > +#include <stdio.h> > +#include <string.h> > +#include <unistd.h> > + > +#include <grub/emu/hostdisk.h> > +#include <grub/emu/misc.h> > + > +#include <grub/util/misc.h> > + > +#include <tss2_buffer.h> > +#include <tss2_mu.h> > +#include <tcg2.h> > +#include <tpm2_args.h> > +#include <tpm2.h> > + > +#pragma GCC diagnostic ignored "-Wmissing-prototypes" > +#pragma GCC diagnostic ignored "-Wmissing-declarations" > +#include <argp.h> > +#pragma GCC diagnostic error "-Wmissing-prototypes" > +#pragma GCC diagnostic error "-Wmissing-declarations" I can see this in other utils sources but it would be nice to know it is still needed, and why, or not... > +#include "progname.h" > + > +/* Unprintable option keys for argp */ > +typedef enum grub_protect_opt > +{ > + /* General */ > + GRUB_PROTECT_OPT_ACTION = 'a', > + GRUB_PROTECT_OPT_PROTECTOR = 'p', > + /* TPM2 */ > + GRUB_PROTECT_OPT_TPM2_DEVICE = 0x100, > + GRUB_PROTECT_OPT_TPM2_PCRS, > + GRUB_PROTECT_OPT_TPM2_ASYMMETRIC, > + GRUB_PROTECT_OPT_TPM2_BANK, > + GRUB_PROTECT_OPT_TPM2_SRK, > + GRUB_PROTECT_OPT_TPM2_KEYFILE, > + GRUB_PROTECT_OPT_TPM2_OUTFILE, > + GRUB_PROTECT_OPT_TPM2_EVICT, > + GRUB_PROTECT_OPT_TPM2_TPM2KEY > +} grub_protect_opt; > + > +/* Option flags to keep track of specified arguments */ > +typedef enum grub_protect_arg > +{ > + /* General */ > + GRUB_PROTECT_ARG_ACTION = 1 << 0, > + GRUB_PROTECT_ARG_PROTECTOR = 1 << 1, > + /* TPM2 */ > + GRUB_PROTECT_ARG_TPM2_DEVICE = 1 << 2, > + GRUB_PROTECT_ARG_TPM2_PCRS = 1 << 3, > + GRUB_PROTECT_ARG_TPM2_ASYMMETRIC = 1 << 4, > + GRUB_PROTECT_ARG_TPM2_BANK = 1 << 5, > + GRUB_PROTECT_ARG_TPM2_SRK = 1 << 6, > + GRUB_PROTECT_ARG_TPM2_KEYFILE = 1 << 7, > + GRUB_PROTECT_ARG_TPM2_OUTFILE = 1 << 8, > + GRUB_PROTECT_ARG_TPM2_EVICT = 1 << 9, > + GRUB_PROTECT_ARG_TPM2_TPM2KEY = 1 << 10 > +} grub_protect_arg_t; > + > +typedef enum grub_protect_protector > +{ > + GRUB_PROTECT_TYPE_ERROR, > + GRUB_PROTECT_TYPE_TPM2 > +} grub_protect_protector_t; > + > +typedef enum grub_protect_action > +{ > + GRUB_PROTECT_ACTION_ERROR, > + GRUB_PROTECT_ACTION_ADD, > + GRUB_PROTECT_ACTION_REMOVE > +} grub_protect_action_t; > + > +struct grub_protect_args > +{ > + grub_protect_arg_t args; > + grub_protect_action_t action; > + grub_protect_protector_t protector; > + > + const char *tpm2_device; > + grub_uint8_t tpm2_pcrs[TPM_MAX_PCRS]; > + grub_uint8_t tpm2_pcr_count; > + grub_srk_type_t srk_type; > + TPM_ALG_ID tpm2_bank; > + TPM_HANDLE tpm2_srk; > + const char *tpm2_keyfile; > + const char *tpm2_outfile; > + int tpm2_evict; > + int tpm2_tpm2key; > +}; > + > +static struct argp_option grub_protect_options[] = > + { > + /* Top-level options */ > + { > + .name = "action", > + .key = 'a', > + .arg = "add|remove", > + .flags = 0, > + .doc = > + N_("Add or remove a key protector to or from a key."), > + .group = 0 > + }, > + { > + .name = "protector", > + .key = 'p', > + .arg = "tpm2", > + .flags = 0, > + .doc = > + N_("Key protector to use (only tpm2 is currently supported)."), > + .group = 0 > + }, > + /* TPM2 key protector options */ > + { > + .name = "tpm2-device", > + .key = GRUB_PROTECT_OPT_TPM2_DEVICE, > + .arg = "FILE", > + .flags = 0, > + .doc = > + N_("Path to the TPM2 device. (default: /dev/tpm0)"), > + .group = 0 > + }, > + { > + .name = "tpm2-pcrs", > + .key = GRUB_PROTECT_OPT_TPM2_PCRS, > + .arg = "0[,1]...", > + .flags = 0, > + .doc = > + N_("Comma-separated list of PCRs used to authorize key release " > + "e.g., '7,11'. Please be aware that PCR 0~7 are used by the " > + "firmware and the measurement result may change after a " > + "firmware update (for baremetal systems) or a package " > + "(OVMF/SeaBIOS/SLOF) update in the VM host. This may lead to" > + "the failure of key unsealing. (default: 7)"), I would add this to the GRUB docs too. > + .group = 0 > + }, > + { > + .name = "tpm2-bank", > + .key = GRUB_PROTECT_OPT_TPM2_BANK, > + .arg = "ALG", > + .flags = 0, > + .doc = > + N_("Bank of PCRs used to authorize key release: " > + "SHA1, SHA256, SHA384, or SHA512. (default: SHA256)"), > + .group = 0 > + }, > + { > + .name = "tpm2-keyfile", > + .key = GRUB_PROTECT_OPT_TPM2_KEYFILE, > + .arg = "FILE", > + .flags = 0, > + .doc = > + N_("Path to a file that contains the cleartext key to protect."), > + .group = 0 > + }, > + { > + .name = "tpm2-outfile", > + .key = GRUB_PROTECT_OPT_TPM2_OUTFILE, > + .arg = "FILE", > + .flags = 0, > + .doc = > + N_("Path to the file that will contain the key after sealing (must be " > + "accessible to GRUB during boot)."), > + .group = 0 > + }, > + { > + .name = "tpm2-srk", > + .key = GRUB_PROTECT_OPT_TPM2_SRK, > + .arg = "NUM", > + .flags = 0, > + .doc = > + N_("The SRK handle if the SRK is to be made persistent."), > + .group = 0 > + }, > + { > + .name = "tpm2-asymmetric", > + .key = GRUB_PROTECT_OPT_TPM2_ASYMMETRIC, > + .arg = "TYPE", > + .flags = 0, > + .doc = > + N_("The type of SRK: RSA (RSA2048) and ECC (ECC_NIST_P256)." > + "(default: ECC)"), > + .group = 0 > + }, > + { > + .name = "tpm2-evict", > + .key = GRUB_PROTECT_OPT_TPM2_EVICT, > + .arg = NULL, > + .flags = 0, > + .doc = > + N_("Evict a previously persisted SRK from the TPM, if any."), > + .group = 0 > + }, > + { > + .name = "tpm2key", > + .key = GRUB_PROTECT_OPT_TPM2_TPM2KEY, > + .arg = NULL, > + .flags = 0, > + .doc = > + N_("Use TPM 2.0 Key File format instead of the raw format."), > + .group = 0 > + }, > + /* End of list */ > + { 0, 0, 0, 0, 0, 0 } > + }; > + > +static int grub_protector_tpm2_fd = -1; I would prefer if it is not global... > +static grub_err_t > +grub_protect_read_file (const char *filepath, void **buffer, > + size_t *buffer_size) > +{ > + grub_err_t err; > + FILE *f; > + long len; > + void *buf; > + > + f = fopen (filepath, "rb"); > + if (f == NULL) > + return GRUB_ERR_FILE_NOT_FOUND; > + > + if (fseek (f, 0, SEEK_END)) > + { > + err = GRUB_ERR_FILE_READ_ERROR; > + goto exit1; > + } > + > + len = ftell (f); > + if (len <= 0) > + { > + err = GRUB_ERR_FILE_READ_ERROR; > + goto exit1; > + } > + > + rewind (f); > + > + buf = grub_malloc (len); > + if (buf == NULL) > + { > + err = GRUB_ERR_OUT_OF_MEMORY; > + goto exit1; > + } > + > + if (fread (buf, len, 1, f) != 1) > + { > + err = GRUB_ERR_FILE_READ_ERROR; > + goto exit2; > + } > + > + *buffer = buf; > + *buffer_size = len; > + > + buf = NULL; > + err = GRUB_ERR_NONE; > + > +exit2: > + grub_free (buf); > + > +exit1: Missing spaces before labels. > + fclose (f); > + > + return err; > +} > + > +static grub_err_t > +grub_protect_write_file (const char *filepath, void *buffer, size_t > buffer_size) > +{ > + grub_err_t err; > + FILE *f; > + > + f = fopen (filepath, "wb"); > + if (f == NULL) > + return GRUB_ERR_FILE_NOT_FOUND; > + > + if (fwrite (buffer, buffer_size, 1, f) != 1) > + { > + err = GRUB_ERR_WRITE_ERROR; > + goto exit1; > + } > + > + err = GRUB_ERR_NONE; > + > +exit1: Ditto and below please... > + fclose (f); > + > + return err; > +} [...] > +static grub_err_t > +grub_protect_tpm2_get_policy_digest (struct grub_protect_args *args, > + TPM2B_DIGEST *digest) > +{ > + TPM_RC rc; > + TPML_PCR_SELECTION pcr_sel = { > + .count = 1, > + .pcrSelections = { > + { > + .hash = args->tpm2_bank, > + .sizeOfSelect = 3, > + .pcrSelect = { 0 } > + }, > + } > + }; > + TPML_PCR_SELECTION pcr_sel_out = { 0 }; > + TPML_DIGEST pcr_values = { 0 }; > + TPM2B_DIGEST pcr_digest = { 0 }; > + grub_size_t pcr_digest_len; > + TPM2B_MAX_BUFFER pcr_concat = { 0 }; > + grub_size_t pcr_concat_len; > + grub_uint8_t *pcr_cursor; > + TPM2B_NONCE nonce = { 0 }; > + TPM2B_ENCRYPTED_SECRET salt = { 0 }; > + TPMT_SYM_DEF symmetric = { 0 }; > + TPMI_SH_AUTH_SESSION session = 0; > + TPM2B_DIGEST policy_digest = { 0 }; > + grub_uint8_t i; > + grub_err_t err; > + > + /* PCR Read */ > + for (i = 0; i < args->tpm2_pcr_count; i++) > + TPMS_PCR_SELECTION_SelectPCR (&pcr_sel.pcrSelections[0], > args->tpm2_pcrs[i]); > + > + rc = TPM2_PCR_Read (NULL, &pcr_sel, NULL, &pcr_sel_out, &pcr_values, NULL); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, _("Failed to read PCRs (TPM2_PCR_Read: 0x%x).\n"), > rc); > + return GRUB_ERR_BAD_DEVICE; > + } > + > + if ((pcr_sel_out.count != pcr_sel.count) || > + (pcr_sel.pcrSelections[0].sizeOfSelect != > + pcr_sel_out.pcrSelections[0].sizeOfSelect)) > + { > + fprintf (stderr, _("Could not read all the specified PCRs.\n")); > + return GRUB_ERR_BAD_DEVICE; > + } > + > + /* Compute PCR Digest */ > + switch (args->tpm2_bank) > + { > + case TPM_ALG_SHA1: > + pcr_digest_len = TPM_SHA1_DIGEST_SIZE; > + break; > + case TPM_ALG_SHA256: > + pcr_digest_len = TPM_SHA256_DIGEST_SIZE; > + break; > + case TPM_ALG_SHA384: > + pcr_digest_len = TPM_SHA384_DIGEST_SIZE; > + break; > + case TPM_ALG_SHA512: > + pcr_digest_len = TPM_SHA512_DIGEST_SIZE; > + break; > + default: > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + pcr_concat_len = pcr_digest_len * args->tpm2_pcr_count; > + if (pcr_concat_len > TPM_MAX_DIGEST_BUFFER) > + { > + fprintf (stderr, _("PCR concatenation buffer not enough.\n")); > + return GRUB_ERR_OUT_OF_RANGE; > + } > + > + pcr_cursor = pcr_concat.buffer; > + for (i = 0; i < args->tpm2_pcr_count; i++) > + { > + if (pcr_values.digests[i].size != pcr_digest_len) > + { > + fprintf (stderr, > + _("Bad PCR value size: expected %" PRIuGRUB_SIZE " bytes but > got %u bytes.\n"), You cannot use PRIuGRUB_SIZE within _() macro. > + pcr_digest_len, pcr_values.digests[i].size); > + return GRUB_ERR_BAD_ARGUMENT; > + } Daniel _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel