On Thu, Aug 21, 2025 at 01:25:03PM +0530, Sudhakar Kuppusamy wrote:
> Building on the parsers and the ability to embed X.509 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.

This suggests that this patch should be split into two separate ones.

> Verification must be enabled by setting check_appended_signatures. If
> secure boot is enabled with enforced mode when the module is loaded,
> verification will be enabled and locked automatically. If verification
> is enabled, extract trusted keys from the GRUB ELF Note and store them in the 
> db.
>
> 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.
>
> Introducing the following GRUB commands to access db.
>
>  1. append_list_db:
>       Show the list of trusted certificates from the db list
>  2. append_add_db_cert:
>       Add the trusted certificate to the db list
>  3. append_rm_dbx_cert:
>       Remove the distrusted certificate from the db list
>  4. append_verify:
>       Verify the signed file using db list
>
> 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                  |  15 +
>  grub-core/commands/appendedsig/appendedsig.c | 793 +++++++++++++++++++
>  include/grub/err.h                           |   3 +-
>  include/grub/file.h                          |   2 +
>  4 files changed, 812 insertions(+), 1 deletion(-)
>  create mode 100644 grub-core/commands/appendedsig/appendedsig.c
>
> diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
> index b72f322b1..d91694de0 100644
> --- a/grub-core/Makefile.core.def
> +++ b/grub-core/Makefile.core.def
> @@ -980,6 +980,21 @@ module = {
>    cppflags = '$(CPPFLAGS_GCRY)';
>  };
>
> +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;
> +  enable = emu;
> +  enable = powerpc_ieee1275;
> +  cflags = '$(CFLAGS_GCRY) -Wno-redundant-decls';
> +  cppflags = '$(CPPFLAGS_GCRY) -I$(srcdir)/lib/libtasn1-grub';
> +  depends = crypto, gcry_rsa, gcry_sha256, gcry_sha512, mpi, asn1;
> +};
> +
>  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..2ddbb3a15
> --- /dev/null
> +++ b/grub-core/commands/appendedsig/appendedsig.c
> @@ -0,0 +1,793 @@
> +/*
> + *  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/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+");
> +
> +/* Public key type. */
> +#define GRUB_PKEY_ID_PKCS7 2
> +
> +/* Appended signature magic string. */
> +static const char magic[] = "~Module signature appended~\n";

This could be a constant... Two useful constants...

#define SIG_MAGIC       "~Module signature appended~\n"
#define SIG_MAGIC_SIZE  ((sizeof(SIG_MAGIC) - 1)

> +/*
> + * 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 [GRUB_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. */
> +};
> +
> +/* This represents a trusted certificates. */
> +struct grub_database
> +{
> +  struct x509_certificate *certs; /* Certificates. */
> +  grub_uint32_t cert_entries;     /* Number of certificates. */
> +};
> +
> +/* The db list is used to validate appended signatures. */
> +struct grub_database db = {.certs = NULL, .cert_entries = 0};
> +
> +/* Appended signature size. */
> +static grub_size_t append_sig_len = 0;
> +
> +/*
> + * Signature verification flag (check_sigs).
> + * check_sigs: false
> + *  - No signature verification. This is the default.
> + * check_sigs: true
> + *  - Enforce signature verification, and if signature verification fails,
> + *    post the errors and stop the boot.
> + */
> +static bool check_sigs = false;
> +
> +static void
> +register_appended_signatures_cmd (void);
> +static void
> +unregister_appended_signatures_cmd (void);
> +static void
> +free_db_list (void);
> +static void
> +build_static_db_list (void);
> +
> +static const char *
> +grub_env_read_sec (struct grub_env_var *var __attribute__ ((unused)),
> +                   const char *val __attribute__ ((unused)))
> +{
> +  if (check_sigs == true)
> +    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 signature verification is
> +   * (check_sigs is set to enforce) enabled and GRUB is locked down.
> +   */
> +  if (check_sigs == true && grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
> +    {
> +      ret = grub_strdup ("enforce");
> +      if (ret == NULL)
> +        grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not duplicate a string 
> enforce");
> +
> +      return ret;
> +    }
> +
> +  if ((*val == '1') || (*val == 'e'))
> +    check_sigs = true;
> +  else if ((*val == '0') || (*val == 'n'))
> +    check_sigs = false;
> +
> +  ret = grub_strdup (grub_env_read_sec (NULL, NULL));
> +  if (ret == NULL)
> +    grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not duplicate a string %s",
> +                grub_env_read_sec (NULL, NULL));
> +
> +  return ret;
> +}
> +
> +static bool
> +is_cert_match (const struct x509_certificate *distrusted_cert,
> +               const struct x509_certificate *db_cert)
> +{
> +  if (grub_memcmp (distrusted_cert->subject, db_cert->subject, 
> db_cert->subject_len) == 0
> +      && grub_memcmp (distrusted_cert->issuer, db_cert->issuer, 
> db_cert->issuer_len) == 0
> +      && grub_memcmp (distrusted_cert->serial, db_cert->serial, 
> db_cert->serial_len) == 0
> +      && grub_memcmp (distrusted_cert->mpis[0], db_cert->mpis[0], sizeof 
> (db_cert->mpis[0])) == 0
> +      && grub_memcmp (distrusted_cert->mpis[1], db_cert->mpis[1], sizeof 
> (db_cert->mpis[1])) == 0)
> +    return true;
> +
> +  return false;
> +}
> +
> +static grub_err_t
> +file_read_whole (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",

s/PRIuGRUB_UINT64_T/PRIuGRUB_OFFSET/

> +                       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
> +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);

s/grub_strlen (magic)/sizeof (magic) - 1/

> +
> +  if (bufsize < grub_strlen (magic))

s/grub_strlen (magic)/sizeof (magic) - 1/

Please be more consistent...

> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature 
> magic");
> +
> +  if (grub_strncmp ((const char *) appsigdata, magic, sizeof (magic) - 1))
> +    return grub_error (GRUB_ERR_BAD_SIGNATURE, "missing or invalid signature 
> magic");
> +
> +  remaining_len = bufsize - grub_strlen (magic);

Ditto...

Or even define a constant, as above, and use it everywhere...

> +  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 != GRUB_PKEY_ID_PKCS7)
> +    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;

Ditto...

> +  /* Rewind pointer and parse pkcs7 data. */
> +  appsigdata -= pkcs7_size;
> +
> +  return parse_pkcs7_signedData (appsigdata, pkcs7_size, &sig->pkcs7);
> +}
> +
> +/*
> + * Given a hash value 'hval', of hash specification 'hash', prepare
> + * the S-expressions (sexp) and perform the signature verification.
> + */
> +static grub_err_t
> +verify_signature (const gcry_mpi_t *pkmpi, const gcry_mpi_t hmpi,
> +                  const gcry_md_spec_t *hash, const grub_uint8_t *hval)
> +{
> +  gcry_sexp_t hsexp, pubkey, sig;
> +  grub_size_t errof;
> +
> +  if (_gcry_sexp_build(&hsexp, &errof, "(data (flags %s) (hash %s %b))", 
> "pkcs1",
> +                       hash->name, hash->mdlen, hval) != GPG_ERR_NO_ERROR)

Missing space after "_gcry_sexp_build" here and below...

> +    return GRUB_ERR_BAD_SIGNATURE;
> +
> +  if (_gcry_sexp_build(&pubkey, &errof, "(public-key (dsa (n %M) (e %M)))",
> +                       pkmpi[0], pkmpi[1]) != GPG_ERR_NO_ERROR)
> +    return GRUB_ERR_BAD_SIGNATURE;
> +
> +  if (_gcry_sexp_build(&sig, &errof, "(sig-val (rsa (s %M)))", hmpi) != 
> GPG_ERR_NO_ERROR)
> +    return GRUB_ERR_BAD_SIGNATURE;
> +
> +  _gcry_sexp_dump(sig);
> +  _gcry_sexp_dump(hsexp);
> +  _gcry_sexp_dump(pubkey);
> +
> +  if (grub_crypto_pk_rsa->verify (sig, hsexp, pubkey) != GPG_ERR_NO_ERROR)
> +    return GRUB_ERR_BAD_SIGNATURE;
> +
> +  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