On Tue, Jun 10, 2025 at 09:20:47PM +0530, Sudhakar wrote:
> From: Daniel Axtens <d...@axtens.net>
>
> Building on the parsers and the ability to embed x509 certificates, as
> well as the existing gcrypt functionality, add a module for verifying
> appended signatures.
>
> This includes a verifier that requires that Linux kernels and grub modules
> have appended signatures, and commands to manage the list of trusted
> certificates for verification.
>
> Verification must be enabled by setting check_appended_signatures. If
> GRUB is locked down when the module is loaded, verification will be
> enabled and locked automatically.
>
> As with the PGP verifier, it is not a complete secure-boot solution:
> other mechanisms, such as a password or lockdown, must be used to ensure
> that a user cannot drop to the grub shell and disable verification.
>
> Signed-off-by: Daniel Axtens <d...@axtens.net>
> Signed-off-by: Sudhakar Kuppusamy <sudha...@linux.ibm.com>
> Reviewed-by: Stefan Berger <stef...@linux.ibm.com>
> Reviewed-by: Avnish Chouhan <avn...@linux.ibm.com>
> ---
>  grub-core/Makefile.core.def                  |  14 +
>  grub-core/commands/appendedsig/appendedsig.c | 624 +++++++++++++++++++
>  include/grub/file.h                          |   2 +
>  3 files changed, 640 insertions(+)
>  create mode 100644 grub-core/commands/appendedsig/appendedsig.c
>
> diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
> index f19071e22..232e97a37 100644
> --- a/grub-core/Makefile.core.def
> +++ b/grub-core/Makefile.core.def
> @@ -979,6 +979,20 @@ module = {
>    cppflags = '-I$(srcdir)/lib/posix_wrap';
>  };
>
> +module = {
> +  name = appendedsig;
> +  common = commands/appendedsig/appendedsig.c;
> +  common = commands/appendedsig/x509.c;
> +  common = commands/appendedsig/pkcs7.c;
> +  common = commands/appendedsig/asn1util.c;
> +  common = commands/appendedsig/gnutls_asn1_tab.c;
> +  common = commands/appendedsig/pkix_asn1_tab.c;
> +
> +  cflags = '$(CFLAGS_POSIX)';
> +  cppflags = '-I$(srcdir)/lib/posix_wrap -I$(srcdir)/lib/libtasn1-grub';
> +  depends = asn1, crypto, gcry_rsa, mpi, pkcs1_v15;
> +};
> +
>  module = {
>    name = hdparm;
>    common = commands/hdparm.c;
> diff --git a/grub-core/commands/appendedsig/appendedsig.c 
> b/grub-core/commands/appendedsig/appendedsig.c
> new file mode 100644
> index 000000000..5fc8e452b
> --- /dev/null
> +++ b/grub-core/commands/appendedsig/appendedsig.c
> @@ -0,0 +1,624 @@
> +/*
> + *  GRUB  --  GRand Unified Bootloader
> + *  Copyright (C) 2020, 2021, 2022 Free Software Foundation, Inc.
> + *  Copyright (C) 2020, 2021, 2022, 2025 IBM 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 <grub/types.h>
> +#include <grub/misc.h>
> +#include <grub/mm.h>
> +#include <grub/err.h>
> +#include <grub/dl.h>
> +#include <grub/file.h>
> +#include <grub/command.h>
> +#include <grub/crypto.h>
> +#include <grub/pkcs1_v15.h>
> +#include <grub/i18n.h>
> +#include <grub/gcrypt/gcrypt.h>
> +#include <grub/kernel.h>
> +#include <grub/extcmd.h>
> +#include <grub/verify.h>
> +#include <libtasn1.h>
> +#include <grub/env.h>
> +#include <grub/lockdown.h>
> +
> +#include "appendedsig.h"
> +
> +GRUB_MOD_LICENSE ("GPLv3+");
> +
> +static const char magic[] = "~Module signature appended~\n";
> +
> +/*
> + * This structure is extracted from scripts/sign-file.c in the linux kernel
> + * source. It was licensed as LGPLv2.1+, which is GPLv3+ compatible.
> + */
> +struct module_signature
> +{
> +  grub_uint8_t algo;       /* Public-key crypto algorithm [0] */
> +  grub_uint8_t hash;       /* Digest algorithm [0] */
> +  grub_uint8_t id_type;    /* Key identifier type [PKEY_ID_PKCS7] */
> +  grub_uint8_t signer_len; /* Length of signer's name [0] */
> +  grub_uint8_t key_id_len; /* Length of key identifier [0] */
> +  grub_uint8_t __pad[3];
> +  grub_uint32_t sig_len;   /* Length of signature data */
> +} GRUB_PACKED;
> +
> +/* This represents an entire, parsed, appended signature */
> +struct grub_appended_signature
> +{
> +  grub_size_t signature_len;            /* Length of PKCS#7 data + metadata 
> + magic */
> +  struct module_signature sig_metadata; /* Module signature metadata */
> +  struct pkcs7_signedData pkcs7;        /* Parsed PKCS#7 data */
> +};
> +
> +/* Trusted certificates for verifying appended signatures */
> +struct x509_certificate *grub_trusted_key;
> +
> +/*
> + * Force gcry_rsa to be a module dependency.
> + *
> + * If we use grub_crypto_pk_rsa, then then the gcry_rsa module won't be built
> + * in if you add 'appendedsig' to grub-install --modules. You would need to
> + * add 'gcry_rsa' too. That's confusing and seems suboptimal, especially when
> + * we only support RSA.
> + *
> + * Dynamic loading also causes some concerns. We can't load gcry_rsa from the
> + * the filesystem after we install the verifier - we won't be able to verify
> + * it without having it already present. We also shouldn't load it before we
> + * install the verifier, because that would mean it wouldn't be verified - an
> + * attacker could insert any code they wanted into the module.
> + *
> + * So instead, reference the internal symbol from gcry_rsa. That creates a
> + * direct dependency on gcry_rsa, so it will be built in when this module
> + * is built in. Being built in (assuming the core image is itself signed!)
> + * also resolves our concerns about loading from the filesystem.
> + */
> +extern gcry_pk_spec_t _gcry_pubkey_spec_rsa;

This is not nice. Please take a look at the commit 6744840b1 (build:
Track explicit module dependencies in Makefile.core.def) how it should
be done properly.

> +static enum
> +{
> +  CHECK_SIGS_NO = 0,
> +  CHECK_SIGS_ENFORCE = 1,
> +  CHECK_SIGS_FORCED = 2
> +} CHECK_SIGS = CHECK_SIGS_NO;

s/CHECK_SIGS = CHECK_SIGS_NO/check_sigs = CHECK_SIGS_NO/

> +static const char *
> +grub_env_read_sec (struct grub_env_var *var __attribute__ ((unused)),
> +                   const char *val __attribute__ ((unused)))
> +{
> +  if (CHECK_SIGS == CHECK_SIGS_FORCED)
> +    return "forced";
> +  else if (CHECK_SIGS == CHECK_SIGS_ENFORCE)
> +    return "enforce";
> +
> +  return "no";
> +}
> +
> +static char *
> +grub_env_write_sec (struct grub_env_var *var __attribute__ ((unused)), const 
> char *val)
> +{
> +  char *ret;
> +
> +  /* Do not allow the value to be changed if set to forced */
> +  if (CHECK_SIGS == CHECK_SIGS_FORCED)
> +    {
> +      ret = grub_strdup ("forced");
> +      if (ret == NULL)
> +        grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not duplicate a string 
> forced");
> +
> +      return ret;
> +    }
> +
> +  if ((*val == '2') || (*val == 'f'))
> +    CHECK_SIGS = CHECK_SIGS_FORCED;
> +  else if ((*val == '1') || (*val == 'e'))
> +    CHECK_SIGS = CHECK_SIGS_ENFORCE;
> +  else if ((*val == '0') || (*val == 'n'))
> +    CHECK_SIGS = CHECK_SIGS_NO;
> +
> +  ret = grub_strdup (grub_env_read_sec (NULL, NULL));
> +  if (ret == NULL)
> +    grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("could not duplicate a string 
> %s"),
> +                grub_env_read_sec (NULL, NULL));
> +
> +  return ret;
> +}
> +
> +static grub_err_t
> +file_read_all (grub_file_t file, grub_uint8_t **buf, grub_size_t *len)
> +{
> +  grub_off_t full_file_size;
> +  grub_size_t file_size, total_read_size = 0;
> +  grub_ssize_t read_size;
> +
> +  full_file_size = grub_file_size (file);
> +  if (full_file_size == GRUB_FILE_SIZE_UNKNOWN)
> +    return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                       "cannot read a file of unknown size into a buffer");
> +
> +  if (full_file_size > GRUB_SIZE_MAX)
> +    return grub_error (GRUB_ERR_OUT_OF_RANGE,
> +                       "file is too large to read: %" PRIuGRUB_UINT64_T " 
> bytes",
> +                       full_file_size);
> +
> +  file_size = (grub_size_t) full_file_size;
> +  *buf = grub_malloc (file_size);
> +  if (*buf == NULL)
> +    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
> +                       "could not allocate file data buffer size %" 
> PRIuGRUB_SIZE,
> +                       file_size);
> +
> +  while (total_read_size < file_size)
> +    {
> +      read_size = grub_file_read (file, *buf + total_read_size, file_size - 
> total_read_size);
> +      if (read_size < 0)
> +        {
> +          grub_free (*buf);
> +          return grub_errno;
> +        }
> +      else if (read_size == 0)
> +        {
> +          grub_free (*buf);
> +          return grub_error (GRUB_ERR_IO,
> +                             "could not read full file size "
> +                             "(%" PRIuGRUB_SIZE "), only %" PRIuGRUB_SIZE " 
> bytes read",
> +                             file_size, total_read_size);
> +        }
> +
> +      total_read_size += read_size;
> +    }
> +
> +  *len = file_size;
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +static grub_err_t
> +read_cert_from_file (grub_file_t f, struct x509_certificate *certificate)
> +{
> +  grub_err_t err;
> +  grub_uint8_t *buf;
> +  grub_size_t file_size;
> +
> +  err = file_read_all (f, &buf, &file_size);
> +  if (err != GRUB_ERR_NONE)
> +    return err;
> +
> +  err = parse_x509_certificate (buf, file_size, certificate);
> +  grub_free (buf);
> +
> +  return err;
> +}
> +
> +static grub_err_t
> +extract_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize,
> +                            struct grub_appended_signature *sig)
> +{
> +  grub_size_t pkcs7_size;
> +  grub_size_t remaining_len;
> +  const grub_uint8_t *appsigdata = buf + bufsize - grub_strlen (magic);
> +
> +  if (bufsize < grub_strlen (magic))
> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature 
> magic");
> +
> +  if (grub_memcmp (appsigdata, (grub_uint8_t *) magic, grub_strlen (magic)))

if (grub_strncmp (appsigdata, magic, sizeof (magic) - 1))

You compare/expect strings, not binary data...

> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "missing or invalid signature 
> magic");
> +
> +  remaining_len = bufsize - grub_strlen (magic);
> +
> +  if (remaining_len < sizeof (struct module_signature))
> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature 
> metadata");
> +
> +  appsigdata -= sizeof (struct module_signature);
> +  /* extract the metadata */
> +  grub_memcpy (&(sig->sig_metadata), appsigdata, sizeof (struct 
> module_signature));
> +  remaining_len -= sizeof (struct module_signature);
> +
> +  if (sig->sig_metadata.id_type != 2)

What id_type 2 means? Please define constant or add a comment before "if".
I think (meaningful) constant would be better...

> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "wrong signature type");
> +
> +  pkcs7_size = grub_be_to_cpu32 (sig->sig_metadata.sig_len);
> +
> +  if (pkcs7_size > remaining_len)
> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for PKCS#7 
> message");
> +
> +  grub_dprintf ("appendedsig", "sig len %" PRIuGRUB_SIZE "\n", pkcs7_size);
> +
> +  sig->signature_len = grub_strlen (magic) + sizeof (struct 
> module_signature) + pkcs7_size;
> +  /* rewind pointer and parse pkcs7 data */
> +  appsigdata -= pkcs7_size;
> +
> +  return parse_pkcs7_signedData (appsigdata, pkcs7_size, &sig->pkcs7);
> +}

[...]

> +static grub_err_t
> +grub_cmd_distrust (grub_command_t cmd __attribute__ ((unused)), int argc, 
> char **args)
> +{
> +  unsigned long cert_num, i;
> +  struct x509_certificate *cert, *prev;
> +
> +  if (argc != 1)
> +    return grub_error (GRUB_ERR_BAD_ARGUMENT, "one argument expected");
> +
> +  grub_errno = GRUB_ERR_NONE;
> +  cert_num = grub_strtoul (args[0], NULL, 10);
> +  if (grub_errno != GRUB_ERR_NONE)

Again, this is not reliable. Please fix this.

> +    return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                       "non-numeric or certificate number is invalid");
> +  else if (cert_num <= 0)

How can it be less than zero if it is "unsigned long"?

> +    return grub_error (GRUB_ERR_OUT_OF_RANGE,
> +                       "certificate number too small - numbers start at 1");
> +
> +  if (cert_num == 1)
> +    {
> +      cert = grub_trusted_key;
> +      grub_trusted_key = cert->next;
> +
> +      certificate_release (cert);
> +      grub_free (cert);
> +      return GRUB_ERR_NONE;
> +    }
> +  i = 2;

Where 2 comes from? It begs for a comment.

> +  prev = grub_trusted_key;
> +  cert = grub_trusted_key->next;
> +  while (cert)

Taking into account above probably "for" instead of while() would be
better here.

> +    {
> +      if (i == cert_num)
> +        {
> +          prev->next = cert->next;
> +          certificate_release (cert);
> +          grub_free (cert);
> +          return GRUB_ERR_NONE;
> +        }
> +      i++;
> +      prev = cert;
> +      cert = cert->next;
> +    }
> +
> +  return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                     N_("no certificate number %lu found - only %lu 
> certificates in the store"),
> +                     cert_num, i - 1);
> +}
> +
> +static grub_err_t
> +grub_cmd_trust (grub_command_t cmd __attribute__ ((unused)), int argc, char 
> **args)
> +{
> +  grub_file_t certf;
> +  struct x509_certificate *cert = NULL;
> +  grub_err_t err;
> +
> +  if (argc != 1)
> +    return grub_error (GRUB_ERR_BAD_ARGUMENT, "one argument expected");
> +
> +  certf = grub_file_open (args[0], GRUB_FILE_TYPE_CERTIFICATE_TRUST | 
> GRUB_FILE_TYPE_NO_DECOMPRESS);
> +  if (certf == NULL)
> +    return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("could not open %s file"), 
> args[0]);
> +
> +  cert = grub_zalloc (sizeof (struct x509_certificate));
> +  if (cert == NULL)
> +    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not allocate memory 
> for certificate");
> +
> +  err = read_cert_from_file (certf, cert);
> +  grub_file_close (certf);
> +  if (err != GRUB_ERR_NONE)
> +    {
> +      grub_free (cert);
> +      return err;
> +    }
> +
> +  grub_dprintf ("appendedsig", "loaded certificate with CN: %s\n", 
> cert->subject);
> +
> +  cert->next = grub_trusted_key;
> +  grub_trusted_key = cert;
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +static grub_err_t
> +grub_cmd_list (grub_command_t cmd __attribute__ ((unused)), int argc 
> __attribute__ ((unused)),
> +               char **args __attribute__ ((unused)))
> +{
> +  struct x509_certificate *cert;
> +  int cert_num = 1;
> +  grub_size_t i;
> +
> +  for (cert = grub_trusted_key; cert; cert = cert->next)
> +    {
> +      grub_printf ("Certificate %d:\n", cert_num);
> +      grub_printf ("\tSerial: ");
> +
> +      for (i = 0; i < cert->serial_len - 1; i++)
> +          grub_printf ("%02x:", cert->serial[i]);

Something is off with indention here...

> +      grub_printf ("%02x\n", cert->serial[cert->serial_len - 1]);
> +      grub_printf ("\tCN: %s\n\n", cert->subject);
> +      cert_num++;
> +    }
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +static grub_err_t
> +appendedsig_init (grub_file_t io __attribute__ ((unused)), enum 
> grub_file_type type,
> +                  void **context __attribute__ ((unused)), enum 
> grub_verify_flags *flags)
> +{
> +  if (CHECK_SIGS == CHECK_SIGS_NO)
> +    {
> +      *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION;
> +      return GRUB_ERR_NONE;
> +    }
> +
> +  switch (type & GRUB_FILE_TYPE_MASK)
> +    {
> +      case GRUB_FILE_TYPE_CERTIFICATE_TRUST:
> +        /*
> +         * This is a certificate to add to trusted keychain.
> +         *
> +         * This needs to be verified or blocked. Ideally we'd write an x509
> +         * verifier, but we lack the hubris required to take this on. 
> Instead,
> +         * require that it have an appended signature.
> +         */
> +      case GRUB_FILE_TYPE_LINUX_KERNEL:
> +      case GRUB_FILE_TYPE_GRUB_MODULE:
> +        /*
> +         * Appended signatures are only defined for ELF binaries.
> +         * Out of an abundance of caution, we only verify Linux kernels and
> +         * GRUB modules at this point.
> +         */
> +        *flags = GRUB_VERIFY_FLAGS_SINGLE_CHUNK;
> +        return GRUB_ERR_NONE;
> +
> +      case GRUB_FILE_TYPE_ACPI_TABLE:
> +      case GRUB_FILE_TYPE_DEVICE_TREE_IMAGE:
> +        /*
> +         * It is possible to use appended signature verification without
> +         * lockdown - like the PGP verifier. When combined with an embedded
> +         * config file in a signed grub binary, this could still be a 
> meaningful

s/grub/GRUB/

> +         * secure-boot chain - so long as it isn't subverted by something 
> like a
> +         * rouge ACPI table or DT image. Defer them explicitly.
> +         */
> +        *flags = GRUB_VERIFY_FLAGS_DEFER_AUTH;
> +        return GRUB_ERR_NONE;
> +
> +      default:
> +        *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION;
> +        return GRUB_ERR_NONE;
> +    }
> +}

Daniel

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

Reply via email to