On Mon, 24 Jan 2022 06:12:18 -0800 Hernan Gatta <[email protected]> wrote:
> From: Hernan Gatta <[email protected]> > > 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 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 ensures the cryptomount command is included in GRUB's > boot script and that it carries the requisite --protector (or -p) parameter. I think you mean '-k' instead of '-p' here. > > Sample usage: > > $ dd if=/dev/urandom of=key count=32 > $ sudo cryptsetup luksAddKey /dev/sdb1 key --pbkdf=pbkdf2 --hash=sha512 > > $ sudo grub-protect --action=add > --protector=tpm2 > --tpm2-keyfile=key > --tpm2-outfile=/boot/efi/boot/grub2/sealed_key Why not have a syntax that is similar to whats given to cryptomount? (eg. $ sudo grub-protect --action=add --protector=tpm2:keyfile=/boot/efi/boot/grub2/sealed_key ) This would allow you to re-use the parsing code from patch #3. Yes, its less UNIXy, but I think could be more intuitive and cuts down on code size. I'm not completely wedded to this idea either. > > Then, in the boot script (wrapped for display): > > cryptomount -u b20f95d0834842bc9197bd78b36732f8 > -p tpm2:keyfile=(hd0,gpt1)/boot/grub2/sealed_key Also, -k here. > > where the UUID corresponds to /dev/sdb1. > > Signed-off-by: Hernan Gatta <[email protected]> > --- > .gitignore | 1 + > Makefile.util.def | 16 + > configure.ac | 1 + > util/grub-protect.c | 1344 > +++++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 1362 insertions(+) > create mode 100644 util/grub-protect.c > > diff --git a/.gitignore b/.gitignore > index f6a1bd0..852327d 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -167,6 +167,7 @@ widthspec.bin > /grub-ofpathname.exe > /grub-probe > /grub-probe.exe > +/grub-protect > /grub-reboot > /grub-render-label > /grub-render-label.exe > diff --git a/Makefile.util.def b/Makefile.util.def > index 39b53b3..05f9f09 100644 > --- a/Makefile.util.def > +++ b/Makefile.util.def > @@ -52,6 +52,9 @@ library = { > common = grub-core/partmap/msdos.c; > common = grub-core/fs/proc.c; > common = grub-core/fs/archelp.c; > + common = grub-core/tpm2/buffer.c; > + common = grub-core/tpm2/mu.c; > + common = grub-core/tpm2/tpm2.c; > }; > > library = { > @@ -207,6 +210,19 @@ program = { > }; > > program = { > + name = grub-protect; > + common = grub-core/osdep/init.c; > + common = util/grub-protect.c; > + common = util/probe.c; > + > + ldadd = libgrubmods.a; > + ldadd = libgrubgcry.a; > + ldadd = libgrubkern.a; > + ldadd = grub-core/lib/gnulib/libgnu.a; > + ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) > $(LIBGEOM)'; > +}; > + > +program = { > name = grub-mkrelpath; > mansection = 1; > > diff --git a/configure.ac b/configure.ac > index 4f649ed..3a4dc5a 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -71,6 +71,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]) > diff --git a/util/grub-protect.c b/util/grub-protect.c > new file mode 100644 > index 0000000..4e4bbd7 > --- /dev/null > +++ b/util/grub-protect.c > @@ -0,0 +1,1344 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * 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 <stdio.h> > +#include <string.h> > +#include <unistd.h> > + > +#include <grub/crypto.h> > +#include <grub/emu/getroot.h> > +#include <grub/emu/hostdisk.h> > +#include <grub/emu/misc.h> > +#include <grub/tpm2/buffer.h> > +#include <grub/tpm2/mu.h> > +#include <grub/tpm2/tcg2.h> > +#include <grub/tpm2/tpm2.h> > +#include <grub/util/misc.h> > + > +#include "progname.h" > + > +typedef enum grub_protect_arg > +{ > + GRUB_PROTECT_ARG_ACTION = 1 << 0, > + GRUB_PROTECT_ARG_PROTECTOR = 1 << 1, > + 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_PERSIST = 1 << 9, > + GRUB_PROTECT_ARG_TPM2_EVICT = 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; > + TPM_ALG_ID tpm2_asymmetric; > + TPM_ALG_ID tpm2_bank; > + TPM_HANDLE tpm2_srk; > + const char *tpm2_keyfile; > + const char *tpm2_outfile; > + int tpm2_persist; > + int tpm2_evict; > +}; > + > +static int tpm2_fd = -1; > + > +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) > + 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) > + { > + err = GRUB_ERR_FILE_READ_ERROR; > + goto exit1; > + } > + > + rewind (f); > + > + buf = grub_malloc (len); > + if (!buf) > + { > + 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 > +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) > + 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: > + fclose (f); > + > + return err; > +} > + > +static grub_err_t > +grub_protect_get_grub_drive_for_file (const char *filepath, char **drive) > +{ > + grub_err_t err = GRUB_ERR_IO; > + char *disk; > + char **devices; > + char *grub_dev; > + char *grub_path; > + char *efi_drive; > + char *partition; > + char *grub_drive; > + grub_device_t dev; > + grub_size_t grub_drive_len; > + int n; > + > + grub_path = grub_canonicalize_file_name (filepath); > + if (!grub_path) > + goto exit1; > + > + devices = grub_guess_root_devices (grub_path); > + if (!devices || !devices[0]) > + goto exit2; > + > + disk = devices[0]; > + > + grub_util_pull_device (disk); > + > + grub_dev = grub_util_get_grub_dev (disk); > + if (!grub_dev) > + goto exit3; > + > + dev = grub_device_open (grub_dev); > + if (!dev) > + goto exit4; > + > + efi_drive = grub_util_guess_efi_drive (disk); > + if (!efi_drive) > + goto exit5; > + > + partition = grub_partition_get_name (dev->disk->partition); > + if (!partition) > + goto exit6; > + > + grub_drive_len = grub_strlen (efi_drive) + grub_strlen (partition) + 3; > + grub_drive = grub_malloc (grub_drive_len + 1); > + if (!grub_drive) > + goto exit7; > + > + n = grub_snprintf (grub_drive, grub_drive_len + 1, "(%s,%s)", efi_drive, > + partition); > + if (n != grub_drive_len) > + goto exit8; > + > + *drive = grub_drive; > + grub_drive = NULL; > + err = GRUB_ERR_NONE; > + > +exit8: > + grub_free (grub_drive); > + > +exit7: > + grub_free (partition); > + > +exit6: > + grub_free (efi_drive); > + > +exit5: > + grub_device_close (dev); > + > +exit4: > + grub_free (grub_dev); > + > +exit3: > + grub_free (devices); > + > +exit2: > + grub_free (grub_path); > + > +exit1: > + return err; > +} > + > +grub_err_t > +grub_tcg2_get_max_output_size (grub_size_t *size) > +{ > + if (!size) > + 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) > +{ > + static const grub_size_t header_size = sizeof (grub_uint16_t) + > + (2 * sizeof(grub_uint32_t)); > + > + if (write (tpm2_fd, input, input_size) != input_size) > + return GRUB_ERR_BAD_DEVICE; > + > + if (read (tpm2_fd, output, output_size) < header_size) > + return GRUB_ERR_BAD_DEVICE; > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_tpm2_open_device (const char *dev_node) > +{ > + if (tpm2_fd != -1) > + return GRUB_ERR_NONE; > + > + tpm2_fd = open (dev_node, O_RDWR); > + if (tpm2_fd == -1) > + { > + fprintf (stderr, _("Could not open TPM device (Error: %u).\n"), errno); > + return GRUB_ERR_FILE_NOT_FOUND; > + } > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_tpm2_close_device (void) > +{ > + int err; > + > + if (tpm2_fd == -1) > + return GRUB_ERR_NONE; > + > + err = close (tpm2_fd); > + if (err) > + { > + fprintf (stderr, _("Could not close TPM device (Error: %u).\n"), errno); > + return GRUB_ERR_IO; > + } > + > + tpm2_fd = -1; > + return GRUB_ERR_NONE; > +} > + > +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 }; > + grub_uint8_t *pcr_digest; > + grub_size_t pcr_digest_len; > + grub_uint8_t *pcr_concat; > + grub_size_t pcr_concat_len; > + grub_uint8_t *pcr_cursor; > + const gcry_md_spec_t *hash_spec; > + TPM2B_NONCE nonce = { 0 }; > + TPM2B_ENCRYPTED_SECRET salt = { 0 }; > + TPMT_SYM_DEF symmetric = { 0 }; > + TPMI_SH_AUTH_SESSION session = 0; > + TPM2B_DIGEST pcr_digest_in = { > + .size = TPM_SHA256_DIGEST_SIZE, > + .buffer = { 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++) > + pcr_sel > + .pcrSelections[0] > + .pcrSelect[TPM2_PCR_TO_SELECT(args->tpm2_pcrs[i])] > + |= TPM2_PCR_TO_BIT(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 (TPM error: 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; > + hash_spec = GRUB_MD_SHA1; > + break; > + case TPM_ALG_SHA256: > + pcr_digest_len = TPM_SHA256_DIGEST_SIZE; > + hash_spec = GRUB_MD_SHA256; > + break; > + default: > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + pcr_digest = grub_malloc (pcr_digest_len); > + if (!pcr_digest) > + { > + fprintf (stderr, _("Failed to allocate PCR digest buffer.\n")); > + return GRUB_ERR_OUT_OF_MEMORY; > + } > + > + pcr_concat_len = pcr_digest_len * args->tpm2_pcr_count; > + pcr_concat = grub_malloc (pcr_concat_len); > + if (!pcr_concat) > + { > + err = GRUB_ERR_OUT_OF_MEMORY; > + fprintf (stderr, _("Failed to allocate PCR concatenation buffer.\n")); > + goto exit1; > + } > + > + pcr_cursor = pcr_concat; > + 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 %lu bytes but got %u > bytes.\n"), > + pcr_digest_len, pcr_values.digests[i].size); > + goto exit2; > + } > + > + grub_memcpy (pcr_cursor, pcr_values.digests[i].buffer, pcr_digest_len); > + pcr_cursor += pcr_digest_len; > + } > + > + grub_crypto_hash (hash_spec, pcr_digest, pcr_concat, pcr_concat_len); > + > + /* Start Trial Session */ > + nonce.size = TPM_SHA256_DIGEST_SIZE; > + symmetric.algorithm = TPM_ALG_NULL; > + > + rc = TPM2_StartAuthSession (TPM_RH_NULL, TPM_RH_NULL, 0, &nonce, &salt, > + TPM_SE_TRIAL, &symmetric, TPM_ALG_SHA256, > + &session, NULL, 0); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, > + _("Failed to start trial policy session (TPM error: > 0x%x).\n"), > + rc); > + err = GRUB_ERR_BAD_DEVICE; > + goto exit2; > + } > + > + /* PCR Policy */ > + memcpy (pcr_digest_in.buffer, pcr_digest, TPM_SHA256_DIGEST_SIZE); > + > + rc = TPM2_PolicyPCR (session, NULL, &pcr_digest_in, &pcr_sel, NULL); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, _("Failed to submit PCR policy (TPM error: 0x%x).\n"), > + rc); > + err = GRUB_ERR_BAD_DEVICE; > + goto exit3; > + } > + > + /* Retrieve Policy Digest */ > + rc = TPM2_PolicyGetDigest (session, NULL, &policy_digest, NULL); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, _("Failed to get policy digest (TPM error: 0x%x).\n"), > + rc); > + err = GRUB_ERR_BAD_DEVICE; > + goto exit3; > + } > + > + /* Epilogue */ > + *digest = policy_digest; > + err = GRUB_ERR_NONE; > + > +exit3: > + TPM2_FlushContext (session); > + > +exit2: > + grub_free (pcr_concat); > + > +exit1: > + grub_free (pcr_digest); > + > + return err; > +} > + > +static grub_err_t > +grub_protect_tpm2_get_srk (struct grub_protect_args *args, TPM_HANDLE *srk) > +{ > + TPM_RC rc; > + TPM2B_PUBLIC public; > + TPMS_AUTH_COMMAND authCommand = { 0 }; > + TPM2B_SENSITIVE_CREATE inSensitive = { 0 }; > + TPM2B_PUBLIC inPublic = { 0 }; > + TPM2B_DATA outsideInfo = { 0 }; > + TPML_PCR_SELECTION creationPcr = { 0 }; > + TPM2B_PUBLIC outPublic = { 0 }; > + TPM2B_CREATION_DATA creationData = { 0 }; > + TPM2B_DIGEST creationHash = { 0 }; > + TPMT_TK_CREATION creationTicket = { 0 }; > + TPM2B_NAME srkName = { 0 }; > + TPM_HANDLE srkHandle; > + > + /* Find SRK */ > + rc = TPM2_ReadPublic (args->tpm2_srk, NULL, &public); > + if (rc == TPM_RC_SUCCESS) > + { > + if (args->tpm2_persist) > + fprintf (stderr, > + _("Warning: --tpm2-persist was specified but the SRK > already exists on the TPM. Continuing anyway...\n")); > + > + *srk = TPM2_SRK_HANDLE; > + return GRUB_ERR_NONE; > + } > + > + /* The handle exists but its public area could not be read. */ > + if ((rc & ~TPM_RC_N_MASK) != TPM_RC_HANDLE) > + { > + fprintf (stderr, > + _("The SRK exists on the TPM but its public area cannot be > read (TPM error: 0x%x).\n"), > + rc); > + return GRUB_ERR_BAD_DEVICE; > + } > + > + /* Create SRK */ > + authCommand.sessionHandle = TPM_RS_PW; > + inPublic.publicArea.type = args->tpm2_asymmetric; > + inPublic.publicArea.nameAlg = TPM_ALG_SHA256; > + inPublic.publicArea.objectAttributes.restricted = 1; > + inPublic.publicArea.objectAttributes.userWithAuth = 1; > + inPublic.publicArea.objectAttributes.decrypt = 1; > + inPublic.publicArea.objectAttributes.fixedTPM = 1; > + inPublic.publicArea.objectAttributes.fixedParent = 1; > + inPublic.publicArea.objectAttributes.sensitiveDataOrigin = 1; > + inPublic.publicArea.objectAttributes.noDA = 1; > + > + switch (args->tpm2_asymmetric) > + { > + case TPM_ALG_RSA: > + inPublic.publicArea.parameters.rsaDetail.symmetric.algorithm = > TPM_ALG_AES; > + inPublic.publicArea.parameters.rsaDetail.symmetric.keyBits.aes = 128; > + inPublic.publicArea.parameters.rsaDetail.symmetric.mode.aes = > TPM_ALG_CFB; > + inPublic.publicArea.parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL; > + inPublic.publicArea.parameters.rsaDetail.keyBits = 2048; > + inPublic.publicArea.parameters.rsaDetail.exponent = 0; > + break; > + > + case TPM_ALG_ECC: > + inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = > TPM_ALG_AES; > + inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128; > + inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = > TPM_ALG_CFB; > + inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL; > + inPublic.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256; > + inPublic.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; > + break; > + > + default: > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + rc = TPM2_CreatePrimary (TPM_RH_OWNER, &authCommand, &inSensitive, > &inPublic, > + &outsideInfo, &creationPcr, &srkHandle, > &outPublic, > + &creationData, &creationHash, &creationTicket, > + &srkName, NULL); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, _("Failed to create SRK (TPM error: 0x%x).\n"), rc); > + return GRUB_ERR_BAD_DEVICE; > + } > + > + /* Persist SRK */ > + if (args->tpm2_persist) > + { > + rc = TPM2_EvictControl (TPM_RH_OWNER, srkHandle, args->tpm2_srk, > + &authCommand, NULL); > + if (rc == TPM_RC_SUCCESS) > + { > + TPM2_FlushContext (srkHandle); > + srkHandle = args->tpm2_srk; > + } > + else > + fprintf (stderr, > + _("Warning: Failed to persist SRK (TPM error: 0x%x\n). > Continuing anyway...\n"), > + rc); > + } > + > + /* Epilogue */ > + *srk = srkHandle; > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_tpm2_seal (TPM2B_DIGEST *policyDigest, TPM_HANDLE srk, > + grub_uint8_t *clearText, grub_size_t clearTextLength, > + TPM2_SEALED_KEY *sealed_key) > +{ > + TPM_RC rc; > + TPMS_AUTH_COMMAND authCommand = { 0 }; > + TPM2B_SENSITIVE_CREATE inSensitive = { 0 }; > + TPM2B_PUBLIC inPublic = { 0 }; > + TPM2B_DATA outsideInfo = { 0 }; > + TPML_PCR_SELECTION pcr_sel = { 0 }; > + TPM2B_PRIVATE outPrivate = { 0 }; > + TPM2B_PUBLIC outPublic = { 0 }; > + > + /* Seal Data */ > + authCommand.sessionHandle = TPM_RS_PW; > + > + inSensitive.sensitive.data.size = clearTextLength; > + memcpy(inSensitive.sensitive.data.buffer, clearText, clearTextLength); > + > + inPublic.publicArea.type = TPM_ALG_KEYEDHASH; > + inPublic.publicArea.nameAlg = TPM_ALG_SHA256; > + inPublic.publicArea.parameters.keyedHashDetail.scheme.scheme = > TPM_ALG_NULL; > + inPublic.publicArea.authPolicy = *policyDigest; > + > + rc = TPM2_Create (srk, &authCommand, &inSensitive, &inPublic, &outsideInfo, > + &pcr_sel, &outPrivate, &outPublic, NULL, NULL, NULL, > NULL); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, _("Failed to seal key (TPM error: 0x%x).\n"), rc); > + return GRUB_ERR_BAD_DEVICE; > + } > + > + /* Epilogue */ > + sealed_key->public = outPublic; > + sealed_key->private = outPrivate; > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_tpm2_export_sealed_key (const char *filepath, > + TPM2_SEALED_KEY *sealed_key) > +{ > + grub_err_t err; > + struct grub_tpm2_buffer buf; > + > + grub_tpm2_buffer_init (&buf); > + grub_tpm2_mu_TPM2B_PUBLIC_Marshal (&buf, &sealed_key->public); > + grub_tpm2_mu_TPM2B_Marshal (&buf, sealed_key->private.size, > + sealed_key->private.buffer); > + if (buf.error) > + return GRUB_ERR_BAD_ARGUMENT; > + > + err = grub_protect_write_file (filepath, buf.data, buf.size); > + if (err) > + fprintf (stderr, _("Could not write sealed key file (Error: %u).\n"), > + errno); > + > + return err; > +} > + > +static grub_err_t > +grub_protect_tpm2_add (struct grub_protect_args *args) > +{ > + grub_err_t err; > + grub_uint8_t *key; > + grub_size_t key_size; > + TPM_HANDLE srk; > + TPM2B_DIGEST policy_digest; > + TPM2_SEALED_KEY sealed_key; > + char *grub_drive = NULL; > + > + grub_protect_get_grub_drive_for_file (args->tpm2_outfile, &grub_drive); > + > + err = grub_protect_tpm2_open_device (args->tpm2_device); > + if (err) > + return err; > + > + err = grub_protect_read_file (args->tpm2_keyfile, (void **)&key, > &key_size); > + if (err) > + goto exit1; > + > + if (key_size > TPM_MAX_SYM_DATA) > + { > + fprintf (stderr, > + _("Input key is too long, maximum allowed size is %u bytes.\n"), > + TPM_MAX_SYM_DATA); > + return GRUB_ERR_OUT_OF_RANGE; > + } > + > + err = grub_protect_tpm2_get_srk (args, &srk); > + if (err) > + goto exit2; > + > + err = grub_protect_tpm2_get_policy_digest (args, &policy_digest); > + if (err) > + goto exit3; > + > + err = grub_protect_tpm2_seal (&policy_digest, srk, key, key_size, > + &sealed_key); > + if (err) > + goto exit3; > + > + err = grub_protect_tpm2_export_sealed_key (args->tpm2_outfile, > &sealed_key); > + if (err) > + goto exit3; > + > + if (grub_drive) > + { > + printf (_("GRUB drive for the sealed key file: %s\n"), grub_drive); > + grub_free (grub_drive); > + } > + else > + { > + fprintf (stderr, > + _("Warning: Could not determine GRUB drive for sealed key > file.\n")); > + err = GRUB_ERR_NONE; > + } > + > +exit3: > + TPM2_FlushContext (srk); > + > +exit2: > + grub_free (key); > + > +exit1: > + grub_protect_tpm2_close_device (); > + > + return err; > +} > + > +static grub_err_t > +grub_protect_tpm2_remove (struct grub_protect_args *args) > +{ > + TPM_RC rc; > + TPM2B_PUBLIC public; > + TPMS_AUTH_COMMAND authCommand = { 0 }; > + grub_err_t err; > + > + if (!args->tpm2_evict) > + { > + printf (_("--tpm2-evict not specified, nothing to do.\n")); > + return GRUB_ERR_NONE; > + } > + > + err = grub_protect_tpm2_open_device (args->tpm2_device); > + if (err) > + return err; > + > + /* Find SRK */ > + rc = TPM2_ReadPublic (args->tpm2_srk, NULL, &public); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, _("SRK with handle 0x%x not found.\n"), > args->tpm2_srk); > + err = GRUB_ERR_BAD_ARGUMENT; > + goto exit1; > + } > + > + /* Evict SRK */ > + authCommand.sessionHandle = TPM_RS_PW; > + > + rc = TPM2_EvictControl (TPM_RH_OWNER, args->tpm2_srk, args->tpm2_srk, > + &authCommand, NULL); > + if (rc != TPM_RC_SUCCESS) > + { > + fprintf (stderr, > + _("Failed to evict SRK with handle 0x%x (TPM Error: > 0x%x).\n"), > + args->tpm2_srk, rc); > + err = GRUB_ERR_BAD_DEVICE; > + goto exit2; > + } > + > + err = GRUB_ERR_NONE; > + > +exit2: > + TPM2_FlushContext (args->tpm2_srk); > + > +exit1: > + grub_protect_tpm2_close_device (); > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_tpm2_run (struct grub_protect_args *args) > +{ > + switch (args->action) > + { > + case GRUB_PROTECT_ACTION_ADD: > + return grub_protect_tpm2_add (args); > + > + case GRUB_PROTECT_ACTION_REMOVE: > + return grub_protect_tpm2_remove (args); > + > + default: > + return GRUB_ERR_BAD_ARGUMENT; > + } > +} > + > +static grub_err_t > +grub_protect_tpm2_args_verify (struct grub_protect_args *args) > +{ I'm wondering if it wouldn't be a good idea to merge this function with grub_tpm2_protector_check_args from patch #3. This is probably only manageable if the protector argument looks like the one from grub as well, so they can share error messages. > + switch (args->action) > + { > + case GRUB_PROTECT_ACTION_ADD: > + if (args->args & GRUB_PROTECT_ARG_TPM2_EVICT) > + { > + fprintf (stderr, > + _("--tpm2-evict is invalid when --action is 'add'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!args->tpm2_keyfile) > + { > + fprintf (stderr, _("--tpm2-keyfile must be specified.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!args->tpm2_outfile) > + { > + fprintf (stderr, _("--tpm2-outfile must be specified.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!args->tpm2_device) > + args->tpm2_device = "/dev/tpm0"; Seems like this... > + > + if (!args->tpm2_pcr_count) > + { > + args->tpm2_pcrs[0] = 7; > + args->tpm2_pcr_count = 1; > + } > + > + if (!args->tpm2_srk) > + args->tpm2_srk = TPM2_SRK_HANDLE; and maybe this, can be done outside the switch since they are common to both cases. > + > + if (!args->tpm2_asymmetric) > + args->tpm2_asymmetric = TPM_ALG_RSA; > + > + if (!args->tpm2_bank) > + args->tpm2_bank = TPM_ALG_SHA256; > + > + break; > + > + case GRUB_PROTECT_ACTION_REMOVE: > + if (args->args & GRUB_PROTECT_ARG_TPM2_ASYMMETRIC) > + { > + fprintf (stderr, > + _("--tpm2-asymmetric is invalid when --action is > 'remove'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (args->args & GRUB_PROTECT_ARG_TPM2_BANK) > + { > + fprintf (stderr, > + _("--tpm2-bank is invalid when --action is 'remove'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (args->args & GRUB_PROTECT_ARG_TPM2_KEYFILE) > + { > + fprintf (stderr, > + _("--tpm2-keyfile is invalid when --action is > 'remove'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (args->args & GRUB_PROTECT_ARG_TPM2_OUTFILE) > + { > + fprintf (stderr, > + _("--tpm2-outfile is invalid when --action is > 'remove'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (args->args & GRUB_PROTECT_ARG_TPM2_PCRS) > + { > + fprintf (stderr, > + _("--tpm2-pcrs is invalid when --action is 'remove'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (args->args & GRUB_PROTECT_ARG_TPM2_PERSIST) > + { > + fprintf (stderr, > + _("--tpm2-persist is invalid when --action is > 'remove'.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!args->tpm2_device) > + args->tpm2_device = "/dev/tpm0"; > + > + if (!args->tpm2_srk) > + args->tpm2_srk = TPM2_SRK_HANDLE; > + > + break; > + > + default: > + fprintf (stderr, > + _("The TPM2 key protector only supports the following > actions: add, remove.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protector_args_kvp_parse (char *kvp, const char **key, char **value) > +{ > + char divider; > + char *separator; > + grub_size_t len; > + > + if (!kvp) > + return GRUB_ERR_BAD_ARGUMENT; > + > + len = grub_strlen (kvp); > + if (!len || len < 2) > + return GRUB_ERR_BAD_ARGUMENT; > + > + if (kvp[0] == '-' && kvp[1] != '-') > + divider = ' '; > + else if (kvp[0] == '-' && kvp[1] == '-') > + divider = '='; > + else > + return GRUB_ERR_BAD_ARGUMENT; > + > + separator = grub_strchr (kvp, divider); > + if (!separator) > + goto exit; > + > + *separator = '\0'; > + separator += 1; > + > + if (separator - kvp >= len) > + { > + separator = NULL; > + goto exit; > + } > + > +exit: > + *key = kvp; > + *value = separator; > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_args_parse (int argc, char *argv[], > + struct grub_protect_args *args) > +{ > + grub_err_t err; > + char *current_arg; > + const char *key; > + char *value; > + int i; > + > + for (i = 1; i < argc; i++) > + { > + current_arg = argv[i]; > + > + err = grub_protector_args_kvp_parse (current_arg, &key, &value); > + if (err) > + return err; > + > + if (grub_strcmp (key, "--action") == 0 || > + grub_strcmp (key, "-a") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_ACTION) > + { > + fprintf (stderr, > + _("Argument %s was already specified.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (grub_strcmp (value, "add") == 0) > + args->action = GRUB_PROTECT_ACTION_ADD; > + else if (grub_strcmp (value, "remove") == 0) > + args->action = GRUB_PROTECT_ACTION_REMOVE; > + else > + { > + fprintf (stderr, _("'%s' is not a valid action.\n"), value); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->args |= GRUB_PROTECT_ARG_ACTION; > + } > + else if (grub_strcmp (key, "--protector") == 0 || > + grub_strcmp (key, "-p") == 0) This might be confusing because its -p here but -k to cryptomount. > + { > + if (args->args & GRUB_PROTECT_ARG_PROTECTOR) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (grub_strcmp (value, "tpm2") == 0) > + args->protector = GRUB_PROTECT_TYPE_TPM2; > + else > + { > + fprintf (stderr, _("'%s' is not a valid protector.\n"), value); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->args |= GRUB_PROTECT_ARG_PROTECTOR; > + } > + else if (grub_strcmp (key, "--tpm2-device") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_DEVICE) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->tpm2_device = value; > + args->args |= GRUB_PROTECT_ARG_TPM2_DEVICE; > + } > + else if (grub_strcmp (key, "--tpm2-pcrs") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_PCRS) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + grub_size_t len = grub_strlen (value); > + grub_size_t dist; > + > + long pcr; > + char *next_pcr; > + char *current_pcr = value; > + grub_uint8_t pcr_u8; > + grub_uint8_t j; > + grub_uint8_t k; > + > + for (j = 0;; j++) > + { > + next_pcr = grub_strchr (current_pcr, ','); > + if (next_pcr) > + *next_pcr = '\0'; > + > + grub_errno = GRUB_ERR_NONE; > + pcr = grub_strtoul (current_pcr, NULL, 0); > + if (grub_errno != GRUB_ERR_NONE) > + { > + fprintf (stderr, _("'%s' is not a valid PCR number.\n"), > + current_pcr); > + return grub_errno; > + } > + > + if (pcr > TPM_MAX_PCRS) > + { > + fprintf (stderr, > + _("%lu larger than the maximum number of > PCRs.\n"), > + pcr); > + return GRUB_ERR_OUT_OF_RANGE; > + } > + > + pcr_u8 = (grub_uint8_t)pcr; > + > + for (k = 0; k < args->tpm2_pcr_count; k++) > + { > + if (args->tpm2_pcrs[k] == pcr_u8) > + { > + fprintf (stderr, _("PCR %u was already specified.\n"), > + pcr_u8); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + } > + > + args->tpm2_pcrs[j] = pcr_u8; > + args->tpm2_pcr_count++; > + > + if (!next_pcr) > + break; > + > + current_pcr = next_pcr + 1; > + > + dist = (current_pcr - value); > + if (dist >= len) > + break; > + } > + > + args->args |= GRUB_PROTECT_ARG_TPM2_PCRS; > + } > + else if (grub_strcmp (key, "--tpm2-srk") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_SRK) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + long srk; > + > + grub_errno = GRUB_ERR_NONE; > + srk = grub_strtoul (value, NULL, 0); > + if (grub_errno != GRUB_ERR_NONE) > + { > + fprintf (stderr, _("'%s' is not a valid SRK handle.\n"), > value); > + return grub_errno; > + } > + > + if (srk > GRUB_UINT_MAX) > + { > + fprintf (stderr, > + _("%lu is too large to be a valid SRK handle.\n"), > srk); > + return GRUB_ERR_OUT_OF_RANGE; > + } > + > + args->tpm2_srk = (TPM_HANDLE)srk; > + args->args |= GRUB_PROTECT_ARG_TPM2_SRK; > + } > + else if (grub_strcmp (key, "--tpm2-asymmetric") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_ASYMMETRIC) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (grub_strcasecmp (value, "ECC") == 0) > + args->tpm2_asymmetric = TPM_ALG_ECC; > + else if (grub_strcasecmp (value, "RSA") == 0) > + args->tpm2_asymmetric = TPM_ALG_RSA; > + else > + { > + fprintf (stderr, _("'%s' is not a valid asymmetric > algorithm.\n"), > + value); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->args |= GRUB_PROTECT_ARG_TPM2_ASYMMETRIC; > + } > + else if (grub_strcmp (key, "--tpm2-bank") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_BANK) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (grub_strcasecmp (value, "SHA1") == 0) > + args->tpm2_bank = TPM_ALG_SHA1; > + else if (grub_strcasecmp (value, "SHA256") == 0) > + args->tpm2_bank = TPM_ALG_SHA256; > + else > + { > + fprintf (stderr, _("'%s' is not a valid PCR bank.\n"), value); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->args |= GRUB_PROTECT_ARG_TPM2_BANK; > + } > + else if (grub_strcmp (key, "--tpm2-keyfile") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_KEYFILE) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->tpm2_keyfile = value; > + args->args |= GRUB_PROTECT_ARG_TPM2_KEYFILE; > + } > + else if (grub_strcmp (key, "--tpm2-outfile") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_OUTFILE) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (!value) > + { > + fprintf (stderr, _("Argument %s requires a value.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->tpm2_outfile = value; > + args->args |= GRUB_PROTECT_ARG_TPM2_OUTFILE; > + } > + else if (grub_strcmp (key, "--tpm2-persist") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_PERSIST) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (value) > + { > + fprintf (stderr, _("Argument %s does not accept a value.\n"), > + key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->tpm2_persist = 1; > + args->args |= GRUB_PROTECT_ARG_TPM2_PERSIST; > + } > + else if (grub_strcmp (key, "--tpm2-evict") == 0) > + { > + if (args->args & GRUB_PROTECT_ARG_TPM2_EVICT) > + { > + fprintf (stderr, _("Argument %s was already specified.\n"), > key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + if (value) > + { > + fprintf (stderr, _("Argument %s does not accept a value.\n"), > + key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + args->tpm2_evict = 1; > + args->args |= GRUB_PROTECT_ARG_TPM2_EVICT; > + } > + else > + { > + fprintf (stderr, _("'%s' is not a valid argument.\n"), key); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + } > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_args_verify (struct grub_protect_args *args) > +{ > + if (args->action == GRUB_PROTECT_ACTION_ERROR) > + { > + fprintf (stderr, "--action is mandatory.\n"); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + /* At the moment, the only configurable key protector is the TPM2 one, so > it > + * is the only keu protector supported by this tool. */ > + if (args->protector != GRUB_PROTECT_TYPE_TPM2) > + { > + fprintf (stderr, > + _("--protector is mandatory and only 'tpm2' is currently > supported.\n")); > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + switch (args->protector) > + { > + case GRUB_PROTECT_TYPE_TPM2: > + return grub_protect_tpm2_args_verify (args); > + default: > + return GRUB_ERR_BAD_ARGUMENT; > + } > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_protect_dispatch (struct grub_protect_args *args) > +{ > + switch (args->protector) > + { > + case GRUB_PROTECT_TYPE_TPM2: > + return grub_protect_tpm2_run (args); > + default: > + return GRUB_ERR_BAD_ARGUMENT; > + } > +} > + > +static void > +grub_protect_init (int *argc, char **argv[]) > +{ > + grub_util_host_init (argc, argv); > + > + grub_util_biosdisk_init (NULL); > + > + grub_init_all (); > + grub_gcry_init_all (); > + > + grub_lvm_fini (); > + grub_mdraid09_fini (); > + grub_mdraid1x_fini (); > + grub_diskfilter_fini (); > + grub_diskfilter_init (); > + grub_mdraid09_init (); > + grub_mdraid1x_init (); > + grub_lvm_init (); > +} > + > +static void > +grub_protect_fini (void) > +{ > + grub_gcry_fini_all (); > + grub_fini_all (); > + grub_util_biosdisk_fini (); > +} > + > +int > +main (int argc, char *argv[]) > +{ > + grub_err_t err; > + struct grub_protect_args args = { 0 }; > + > + grub_protect_init (&argc, &argv); > + > + err = grub_protect_args_parse (argc, argv, &args); > + if (err) > + goto exit; > + > + err = grub_protect_args_verify (&args); > + if (err) > + goto exit; > + > + err = grub_protect_dispatch (&args); > + if (err) > + goto exit; > + > +exit: > + grub_protect_fini (); > + > + return err; > +} It would be nice to take arguments --help and -h and spit out something useful. Glenn _______________________________________________ Grub-devel mailing list [email protected] https://lists.gnu.org/mailman/listinfo/grub-devel
