On Fri, Sep 06, 2024 at 05:11:16PM +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 \
Please replace tabs with spaces here... > --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 \ Ditto... > --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. > > 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 | 1394 +++++++++++++++++++++++++++++++++++++ > 5 files changed, 1456 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 458b8382b..ad1e7bea5 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]) > @@ -2068,6 +2069,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]) > @@ -2184,6 +2208,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 > @@ -2311,6 +2336,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..ecf1c9eab > --- /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 protect a disk encryption key with a specified key > protector. > diff --git a/util/grub-protect.c b/util/grub-protect.c > new file mode 100644 > index 000000000..fb4ea4079 > --- /dev/null > +++ b/util/grub-protect.c > @@ -0,0 +1,1394 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * Copyright (C) 2022 Microsoft Corporation > + * Copyright (C) 2023 SUSE LLC > + * Copyright (C) 2024 Free Software Foundation, Inc. > + * > + * 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" > + > +#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; Again, you can drop "grub_" prefixes for internal stuff... > +/* 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; Ditto and in other patches too... > +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_t tpm2_bank; > + TPM_HANDLE_t 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_("Set 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_("Set the 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_("Set a 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)"), > + .group = 0 > + }, > + { > + .name = "tpm2-bank", > + .key = GRUB_PROTECT_OPT_TPM2_BANK, > + .arg = "ALG", > + .flags = 0, > + .doc = > + N_("Set the 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_("Set the 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_("Set the 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_("Set 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_("Set 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 protector_tpm2_fd = -1; > + > +static grub_err_t > +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: > + fclose (f); > + > + return err; > +} > + > +static grub_err_t > +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; s/exit1/exit/ > + } > + > + err = GRUB_ERR_NONE; > + > + exit1: > + fclose (f); > + > + return err; > +} > + > +grub_err_t > +grub_tcg2_get_max_output_size (grub_size_t *size) This function seems unused. > +{ > + if (size == NULL) > + return GRUB_ERR_BAD_ARGUMENT; > + > + *size = GRUB_TPM2_BUFFER_CAPACITY; > + > + return GRUB_ERR_NONE; > +} > + > +grub_err_t > +grub_tcg2_submit_command (grub_size_t input_size, grub_uint8_t *input, > + grub_size_t output_size, grub_uint8_t *output) Ditto... > +{ > + static const grub_size_t header_size = sizeof (grub_uint16_t) + > + (2 * sizeof(grub_uint32_t)); > + > + if (write (protector_tpm2_fd, input, input_size) != input_size) > + return GRUB_ERR_BAD_DEVICE; > + > + if (read (protector_tpm2_fd, output, output_size) < header_size) > + return GRUB_ERR_BAD_DEVICE; > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +protect_tpm2_open_device (const char *dev_node) > +{ > + if (protector_tpm2_fd != -1) > + return GRUB_ERR_NONE; > + > + protector_tpm2_fd = open (dev_node, O_RDWR); > + if (protector_tpm2_fd == -1) > + { > + fprintf (stderr, "Could not open TPM device (%s).\n", strerror > (errno)); > + return GRUB_ERR_FILE_NOT_FOUND; > + } > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +protect_tpm2_close_device (void) > +{ > + int err; > + > + if (protector_tpm2_fd == -1) > + return GRUB_ERR_NONE; > + > + err = close (protector_tpm2_fd); > + if (err != GRUB_ERR_NONE) > + { > + fprintf (stderr, "Could not close TPM device (Error: %u).\n", errno); > + return GRUB_ERR_IO; > + } > + > + protector_tpm2_fd = -1; > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +protect_tpm2_get_policy_digest (struct grub_protect_args *args, > TPM2B_DIGEST_t *digest) > +{ > + TPM_RC_t rc; > + TPML_PCR_SELECTION_t pcr_sel = { > + .count = 1, > + .pcrSelections = { > + { > + .hash = args->tpm2_bank, > + .sizeOfSelect = 3, > + .pcrSelect = { 0 } > + }, > + } > + }; > + TPML_PCR_SELECTION_t pcr_sel_out = { 0 }; Redundant spaces around 0. > + TPML_DIGEST_t pcr_values = { 0 }; Ditto and below... > + TPM2B_DIGEST_t pcr_digest = { 0 }; > + grub_size_t pcr_digest_len; > + TPM2B_MAX_BUFFER_t pcr_concat = { 0 }; > + grub_size_t pcr_concat_len; > + grub_uint8_t *pcr_cursor; > + TPM2B_NONCE_t nonce = { 0 }; > + TPM2B_ENCRYPTED_SECRET_t salt = { 0 }; > + TPMT_SYM_DEF_t symmetric = { 0 }; > + TPMI_SH_AUTH_SESSION_t session = 0; > + TPM2B_DIGEST_t policy_digest = { 0 }; > + grub_uint8_t i; > + grub_err_t err; Daniel _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel