On Thu, Oct 24, 2024 at 05:11:00PM +0200, Daniel Kiper wrote:
> On Mon, Oct 21, 2024 at 04:07:03PM +0800, Gary Lin 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/efi/grub/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/efi/grub/sealed.key
> >
> > Then, in the boot script, for TPM 2.0 Key File:
> >
> > tpm2_key_protector_init --tpm2key=(hd0,gpt1)/efi/grub/sealed.tpm
> > cryptomount -u <SDB1_UUID> -P tpm2
> >
> > Or, for the raw sealed key:
> >
> > tpm2_key_protector_init --keyfile=(hd0,gpt1)/efi/grub/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
> 
> [...]
> 
> > +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 exit;
> > +  }
> > +
> > +  err = GRUB_ERR_NONE;
> > +
> > + exit:
> > +  fclose (f);
> > +
> > +  return err;
> > +}
> > +
> > +grub_err_t
> > +grub_tcg2_get_max_output_size (grub_size_t *size)
> > +{
> > +  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)
> > +{
> > +  static const grub_size_t header_size = sizeof (grub_uint16_t) +
> > +                                    (2 * sizeof(grub_uint32_t));
> 
> AIUI this is somehow related to the TPM structs defined elsewhere. So,
> I think these structs should have defined header as a separate struct
> (probably preferred). Then you could use, e.g., sizeof (tpm_header) and be
> done. Or you can define a constant next to TPM structs as you do here.
> Of course it would be nice if the constant has relevant name and/or
> a comment...
> 
I'll define a TPM command header in tss2_structs.h like this:

struct TPM_COMMAND_HEADER
{
  grub_uint16_t tag;
  grub_uint32_t command_size;
  TPM_CC_t command_code;
};
typedef struct TPM_COMMAND_HEADER TPM_COMMAND_HEADER_t;

This would make the code more comprehensive.


> > +  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_export_tpm2key (const protect_args_t *args,
> > +                        tpm2_sealed_key_t *sealed_key)
> > +{
> > +  const char *sealed_key_oid = "2.23.133.10.1.5";
> 
> Please name this OID in a comment or define properly named constant.
> 
The OID is from the TPM 2.0 Key File spec. I'll add a '#define' for it
and a proper comment.

> > +  asn1_node asn1_def = NULL;
> > +  asn1_node tpm2key = NULL;
> > +  grub_uint32_t parent;
> > +  grub_uint32_t cmd_code;
> > +  struct grub_tpm2_buffer pol_buf;
> > +  TPML_PCR_SELECTION_t pcr_sel = {
> > +    .count = 1,
> > +    .pcrSelections = {
> > +      {
> > +   .hash = args->tpm2_bank,
> > +   .sizeOfSelect = 3,
> > +   .pcrSelect = {0}
> > +      },
> > +    }
> > +  };
> > +  struct grub_tpm2_buffer pub_buf;
> > +  struct grub_tpm2_buffer priv_buf;
> > +  void *der_buf = NULL;
> > +  int der_buf_size = 0;
> > +  int i;
> > +  int ret;
> > +  grub_err_t err;
> > +
> > +  for (i = 0; i < args->tpm2_pcr_count; i++)
> > +    TPMS_PCR_SELECTION_SelectPCR (&pcr_sel.pcrSelections[0], 
> > args->tpm2_pcrs[i]);
> > +
> > +  /*
> > +   * Prepare the parameters for TPM_CC_PolicyPCR:
> > +   * empty pcrDigest and the user selected PCRs
> > +   */
> > +  grub_tpm2_buffer_init (&pol_buf);
> > +  grub_tpm2_buffer_pack_u16 (&pol_buf, 0);
> > +  grub_Tss2_MU_TPML_PCR_SELECTION_Marshal (&pol_buf, &pcr_sel);
> > +
> > +  grub_tpm2_buffer_init (&pub_buf);
> > +  grub_Tss2_MU_TPM2B_PUBLIC_Marshal (&pub_buf, &sealed_key->public);
> > +  grub_tpm2_buffer_init (&priv_buf);
> > +  grub_Tss2_MU_TPM2B_Marshal (&priv_buf, sealed_key->private.size,
> > +                         sealed_key->private.buffer);
> > +  if (pub_buf.error != 0 || priv_buf.error != 0)
> > +    return GRUB_ERR_BAD_ARGUMENT;
> > +
> > +  ret = asn1_array2tree (tpm2key_asn1_tab, &asn1_def, NULL);
> > +  if (ret != ASN1_SUCCESS)
> > +    return GRUB_ERR_BAD_ARGUMENT;
> > +
> > +  ret = asn1_create_element (asn1_def, "TPM2KEY.TPMKey" , &tpm2key);
> > +  if (ret != ASN1_SUCCESS)
> > +    return GRUB_ERR_BAD_ARGUMENT;
> > +
> > +  /* Set 'type' to "sealed key" */
> > +  ret = asn1_write_value (tpm2key, "type", sealed_key_oid, 1);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'type': 0x%u\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Set 'emptyAuth' to TRUE */
> > +  ret = asn1_write_value (tpm2key, "emptyAuth", "TRUE", 1);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'emptyAuth': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Set 'policy' */
> > +  ret = asn1_write_value (tpm2key, "policy", "NEW", 1);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'policy': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +  cmd_code = grub_cpu_to_be32 (TPM_CC_PolicyPCR);
> > +  ret = asn1_write_value (tpm2key, "policy.?LAST.CommandCode", &cmd_code,
> > +                     sizeof (cmd_code));
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'policy CommandCode': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +  ret = asn1_write_value (tpm2key, "policy.?LAST.CommandPolicy", 
> > &pol_buf.data,
> > +                     pol_buf.size);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'policy CommandPolicy': 0x%x\n", 
> > ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Remove 'secret' */
> > +  ret = asn1_write_value (tpm2key, "secret", NULL, 0);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to remove 'secret': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Remove 'authPolicy' */
> > +  ret = asn1_write_value (tpm2key, "authPolicy", NULL, 0);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to remove 'authPolicy': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Remove 'description' */
> > +  ret = asn1_write_value (tpm2key, "description", NULL, 0);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to remove 'description': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /*
> > +   *  Use the SRK handle as the parent handle if specified
> > +   *  Otherwise, Use TPM_RH_OWNER as the default parent handle
> > +  */
> > +  if (args->tpm2_srk != 0)
> > +    parent = grub_cpu_to_be32 (args->tpm2_srk);
> > +  else
> > +    parent = grub_cpu_to_be32 (TPM_RH_OWNER);
> > +  ret = asn1_write_value (tpm2key, "parent", &parent, sizeof (parent));
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'parent': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /*
> > +   * Set 'rsaParent' to TRUE if the RSA SRK is specified and the SRK
> > +   * handle is not persistent. Otherwise, remove 'rsaParent'.
> > +   */
> > +  if (args->tpm2_srk == 0 && args->srk_type.type == TPM_ALG_RSA)
> > +    ret = asn1_write_value (tpm2key, "rsaParent", "TRUE", 1);
> > +  else
> > +    ret = asn1_write_value (tpm2key, "rsaParent", NULL, 0);
> > +
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'rsaParent': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Set the pubkey */
> > +  ret = asn1_write_value (tpm2key, "pubkey", pub_buf.data, pub_buf.size);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'pubkey': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Set the privkey */
> > +  ret = asn1_write_value (tpm2key, "privkey", priv_buf.data, 
> > priv_buf.size);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "Failed to set 'privkey': 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  /* Create the DER binary */
> > +  der_buf_size = 0;
> > +  ret = asn1_der_coding (tpm2key, "", NULL, &der_buf_size, NULL);
> > +  if (ret != ASN1_MEM_ERROR)
> > +    {
> > +      fprintf (stderr, "Failed to get DER size: 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  der_buf = grub_malloc (der_buf_size);
> > +  if (der_buf == NULL)
> > +    {
> > +      fprintf (stderr, "Failed to allocate memory for DER encoding\n");
> > +      err = GRUB_ERR_OUT_OF_MEMORY;
> > +      goto error;
> > +    }
> > +
> > +  ret = asn1_der_coding (tpm2key, "", der_buf, &der_buf_size, NULL);
> > +  if (ret != ASN1_SUCCESS)
> > +    {
> > +      fprintf (stderr, "DER coding error: 0x%x\n", ret);
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto error;
> > +    }
> > +
> > +  err = protect_write_file (args->tpm2_outfile, der_buf, der_buf_size);
> > +  if (err != GRUB_ERR_NONE)
> > +    fprintf (stderr, "Could not write tpm2key file (Error: %u).\n", errno);
> > +
> > + error:
> > +  grub_free (der_buf);
> > +
> > +  if (tpm2key)
> > +    asn1_delete_structure (&tpm2key);
> > +
> > +  return err;
> > +}
> 
> [...]
> 
> > +static grub_err_t
> > +protect_tpm2_args_verify (protect_args_t *args)
> > +{
> > +  switch (args->action)
> > +    {
> > +    case PROTECT_ACTION_ADD:
> > +      if (args->args & PROTECT_ARG_TPM2_EVICT)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-evict is invalid when --action is 
> > 'add'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->tpm2_keyfile == NULL)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-keyfile must be specified.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->tpm2_outfile == NULL)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-outfile must be specified.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->tpm2_device == NULL)
> > +   args->tpm2_device = "/dev/tpm0";
> 
> When you move this "if" to the beginning of function or its end then you
> can do this check once and drop one below...
> 
Got it.

> > +      if (args->tpm2_pcr_count == 0)
> > +   {
> > +     args->tpm2_pcrs[0] = 7;
> > +     args->tpm2_pcr_count = 1;
> > +   }
> > +
> > +      if (args->srk_type.type == TPM_ALG_ERROR)
> > +   {
> > +     args->srk_type.type = TPM_ALG_ECC;
> > +     args->srk_type.detail.ecc_curve = TPM_ECC_NIST_P256;
> > +   }
> > +
> > +      if (args->tpm2_bank == TPM_ALG_ERROR)
> > +   args->tpm2_bank = TPM_ALG_SHA256;
> > +
> > +      break;
> > +
> > +    case PROTECT_ACTION_REMOVE:
> > +      if (args->args & PROTECT_ARG_TPM2_ASYMMETRIC)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-asymmetric is invalid when --action is 
> > 'remove'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->args & PROTECT_ARG_TPM2_BANK)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-bank is invalid when --action is 
> > 'remove'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->args & PROTECT_ARG_TPM2_KEYFILE)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-keyfile is invalid when --action is 
> > 'remove'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->args & PROTECT_ARG_TPM2_OUTFILE)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-outfile is invalid when --action is 
> > 'remove'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->args & PROTECT_ARG_TPM2_PCRS)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-pcrs is invalid when --action is 
> > 'remove'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->tpm2_srk == 0)
> > +   {
> > +     fprintf (stderr, N_("--tpm2-srk is not specified when --action is 
> > 'remove'.\n"));
> > +     return GRUB_ERR_BAD_ARGUMENT;
> > +   }
> > +
> > +      if (args->tpm2_device == NULL)
> > +   args->tpm2_device = "/dev/tpm0";
> 
> ... I mean from here...
> 
> > +      break;
> > +
> > +    default:
> > +      fprintf (stderr, N_("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
> > +protect_args_verify (protect_args_t *args)
> > +{
> > +  if (args->action == PROTECT_ACTION_ERROR)
> > +    {
> > +      fprintf (stderr, N_("--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 key protector supported by this tool. */
> 
> Wrong coding style for the comment...
> 
Will fix it in the next version.

> > +  if (args->protector != PROTECT_TYPE_TPM2)
> > +    {
> > +      fprintf (stderr, N_("--protector is mandatory and only 'tpm2' is 
> > currently supported.\n"));
> > +      return GRUB_ERR_BAD_ARGUMENT;
> > +    }
> > +
> > +  switch (args->protector)
> > +    {
> > +    case PROTECT_TYPE_TPM2:
> > +      return protect_tpm2_args_verify (args);
> > +    default:
> > +      return GRUB_ERR_BAD_ARGUMENT;
> > +    }
> > +
> > +  return GRUB_ERR_NONE;
> > +}
> 
> [...]
> 
> > +int
> > +main (int argc, char *argv[])
> > +{
> > +  grub_err_t err;
> 
> s/grub_err_t/int/
> 
> > +  protect_args_t args = {0};
> > +
> > +  if (argp_parse (&protect_argp, argc, argv, 0, 0, &args) != 0)
> > +    {
> > +      fprintf (stderr, N_("Could not parse arguments.\n"));
> > +      return GRUB_ERR_BAD_ARGUMENT;
> 
> You expose GRUB internals to the user space and mix types. There is no
> guarantee GRUB_ERR_BAD_ARGUMENT value will not change in the future.
> So, I think you should return EXIT_FAILURE here.
> 
Will fix it in the next version.

> > +    }
> > +
> > +  protect_init (&argc, &argv);
> > +
> > +  err = protect_args_verify (&args);
> 
> Ditto... The EXIT_SUCCESS and EXIT_FAILURE are your friends...
> 
> > +  if (err != GRUB_ERR_NONE)
> > +    goto exit;
> > +
> > +  err = protect_dispatch (&args);
> 
> Ditto...
> 
> I did not check other patches but if you do the same thing elsewhere
> please fix it.
> 
Will check other patches.

> > +  if (err != GRUB_ERR_NONE)
> > +    goto exit;
> > +
> > + exit:
> > +  protect_fini ();
> > +

> > +  return err;
I would like to handle 'err' like this:

  if (err != GRUB_ERR_NONE)
    return EXIT_FAILURE;

  return EXIT_SUCCESS;


Gary Lin

> > +}
> 
> Daniel

_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to