/*
 *  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 <grub/powerpc/ieee1275/platform_keystore.h>
#include <grub/efi/pks.h>

#include "appendedsig.h"

GRUB_MOD_LICENSE ("GPLv3+");

/* Public key type. */
#define PKEY_ID_PKCS7      2

/* Appended signature magic string and size. */
#define SIG_MAGIC          "~Module signature appended~\n"
#define SIG_MAGIC_SIZE     ((sizeof(SIG_MAGIC) - 1))

/* SHA256, SHA384 and SHA512 hash sizes. */
#define SHA256_HASH_SIZE   32
#define SHA384_HASH_SIZE   48
#define SHA512_HASH_SIZE   64

#define OPTION_BINARY_HASH 0
#define OPTION_CERT_HASH   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 [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;

#define SIG_METADATA_SIZE  (sizeof (struct module_signature))
#define APPENDED_SIG_SIZE(pkcs7_data_size) \
                           (pkcs7_data_size + SIG_MAGIC_SIZE + SIG_METADATA_SIZE)

/* This represents an entire, parsed, appended signature. */
struct appended_signature
{
  struct module_signature sig_metadata; /* Module signature metadata. */
  grub_pkcs7_data_t pkcs7;              /* Parsed PKCS#7 data. */
  grub_size_t signature_len;            /* Length of PKCS#7 data + metadata + magic. */
};
typedef struct appended_signature sb_appendedsig_t;

/* This represents a trusted certificates. */
struct sb_database
{
  grub_x509_cert_t *certs;    /* Certificates. */
  grub_uint32_t cert_entries; /* Number of certificates. */
  grub_uint8_t **hashes;      /* Certificate/binary hashes. */
  grub_size_t *hash_sizes;    /* Sizes of certificate/binary hashes. */
  grub_uint32_t hash_entries; /* Number of certificate/binary hashes. */
  bool is_db;                 /* Flag to indicate the db/dbx list. */
};
typedef struct sb_database sb_database_t;

/* The db list is used to validate appended signatures. */
static sb_database_t db = {.certs = NULL, .cert_entries = 0, .hashes = NULL,
                           .hash_sizes = NULL, .hash_entries = 0, .is_db = true};
/*
 * The dbx list is used to ensure that the distrusted certificates or GRUB
 * modules/kernel binaries are rejected during appended signatures/hashes
 * validation.
 */
static sb_database_t dbx = {.certs = NULL, .cert_entries = 0, .hashes = NULL,
                            .hash_sizes = NULL, .hash_entries = 0, .is_db = false};

/*
 * 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;

/*
 * append_key_mgmt: Key Management Modes
 * False: Static key management (use built-in Keys). This is default.
 * True: Dynamic key management (use Platform KeySotre).
 */
static bool append_key_mgmt = false;

/* Platform KeyStore db and dbx. */
static grub_pks_t *pks_keystore;

/* Appended signature size. */
static grub_size_t append_sig_len = 0;

static const struct grub_arg_option options[] =
{
  {"binary-hash", 'b', 0, N_("hash file of the binary."), 0, ARG_TYPE_PATHNAME},
  {"cert-hash", 'c', 1, N_("hash file of the certificate."), 0, ARG_TYPE_PATHNAME},
  {0, 0, 0, 0, 0, 0}
};

static grub_ssize_t
pseudo_read (struct grub_file *file, char *buf, grub_size_t len)
{
  grub_memcpy (buf, (grub_uint8_t *) file->data + file->offset, len);
  return len;
}

/* Filesystem descriptor. */
static struct grub_fs pseudo_fs = {
  .name = "pseudo",
  .fs_read = pseudo_read
};

/*
 * We cannot use hexdump() to display hash data because it is typically displayed
 * in hexadecimal format, along with an ASCII representation of the same data.
 *
 * Example: sha256 hash data
 * 00000000  52 b5 90 49 64 de 22 d7  4e 5f 4f b4 1b 51 9c 34  |R..Id.".N_O..Q.4|
 * 00000010  b1 96 21 7c 91 78 a5 0d  20 8c e9 5c 22 54 53 f7  |..!|.x.. ..\"TS.|
 *
 * An appended signature only required to display the hexadecimal of the hash data
 * by separating each byte with ":". So, we introduced a new method hexdump_colon
 * to display it.
 *
 * Example: Sha256 hash data
 *  52:b5:90:49:64:de:22:d7:4e:5f:4f:b4:1b:51:9c:34:
 *  b1:96:21:7c:91:78:a5:0d:20:8c:e9:5c:22:54:53:f7
 */
static void
hexdump_colon (const grub_uint8_t *data, const grub_size_t length)
{
  grub_size_t i, count = 0;

  for (i = 0; i < length - 1; i++)
    {
      grub_printf ("%02x:", data[i]);
      count++;
      if (count == 16)
        {
          grub_printf ("\n         ");
          count = 0;
        }
    }

  grub_printf ("%02x\n", data[i]);
}

static void
print_certificate (const grub_x509_cert_t *cert, const grub_uint32_t cert_num)
{
  grub_uint32_t i;

  grub_printf ("\nCertificate: %u\n", cert_num);
  grub_printf ("    Data:\n");
  grub_printf ("        Version: %u (0x%u)\n", cert->version + 1, cert->version);
  grub_printf ("        Serial Number:\n             ");

  for (i = 0; i < cert->serial_len - 1; i++)
    grub_printf ("%02x:", cert->serial[i]);

  grub_printf ("%02x\n", cert->serial[cert->serial_len - 1]);
  grub_printf ("        Issuer: %s\n", cert->issuer);
  grub_printf ("        Subject: %s\n", cert->subject);
  grub_printf ("        Subject Public Key Info:\n");
  grub_printf ("            Public Key Algorithm: rsaEncryption\n");
  grub_printf ("                RSA Public-Key: (%d bit)\n", cert->modulus_size);
  grub_printf ("    Fingerprint: sha256\n         ");
  hexdump_colon (&cert->fingerprint[GRUB_FINGERPRINT_SHA256][0],
                 grub_strlen ((char *) cert->fingerprint[GRUB_FINGERPRINT_SHA256]));
}

/*
 * GUID can be used to determine the hashing function and generate the hash using
 * determined hashing function.
 */
static grub_err_t
get_hash (const grub_packed_guid_t *guid, const grub_uint8_t *data, const grub_size_t data_size,
          grub_uint8_t *hash, grub_size_t *hash_size)
{
  gcry_md_spec_t *hash_func = NULL;

  if (guid == NULL)
    return grub_error (GRUB_ERR_OUT_OF_RANGE, "GUID is not available");

  if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
      grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0)
    hash_func = &_gcry_digest_spec_sha256;
  else if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
           grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0)
    hash_func = &_gcry_digest_spec_sha384;
  else if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
           grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0)
    hash_func = &_gcry_digest_spec_sha512;
  else
    return grub_error (GRUB_ERR_OUT_OF_RANGE, "unsupported GUID hash");

  grub_crypto_hash (hash_func, hash, data, data_size);
  *hash_size = hash_func->mdlen;

  return GRUB_ERR_NONE;
}

static grub_err_t
generate_cert_hash (const grub_size_t cert_hash_size, const grub_uint8_t *data,
                    const grub_size_t data_size, grub_uint8_t *hash, grub_size_t *hash_size)
{
  grub_packed_guid_t guid = { 0 };

  /* support SHA256, SHA384 and SHA512 for certificate hash */
  if (cert_hash_size == SHA256_HASH_SIZE)
    grub_memcpy (&guid, &GRUB_PKS_CERT_X509_SHA256_GUID, GRUB_PACKED_GUID_SIZE);
  else if (cert_hash_size == SHA384_HASH_SIZE)
    grub_memcpy (&guid, &GRUB_PKS_CERT_X509_SHA384_GUID, GRUB_PACKED_GUID_SIZE);
  else if (cert_hash_size == SHA512_HASH_SIZE)
    grub_memcpy (&guid, &GRUB_PKS_CERT_X509_SHA512_GUID, GRUB_PACKED_GUID_SIZE);
  else
    {
      grub_dprintf ("appendedsig", "unsupported hash type (%" PRIuGRUB_SIZE ") and "
                    "skipped\n", cert_hash_size);
      return GRUB_ERR_UNKNOWN_COMMAND;
    }

  return get_hash (&guid, data, data_size, hash, hash_size);
}

/* Check the hash presence in the db/dbx list. */
static bool
check_hash_presence (grub_uint8_t *const hash, const grub_size_t hash_size,
                     const sb_database_t *sb_database)
{
  grub_uint32_t i;

  for (i = 0; i < sb_database->hash_entries; i++)
    {
      if (sb_database->hashes[i] == NULL)
        continue;

      if (hash_size == sb_database->hash_sizes[i] &&
          grub_memcmp (sb_database->hashes[i], hash, hash_size) == 0)
        return true;
    }

  return false;
}

/* Add the certificate/binary hash into the db/dbx list. */
static grub_err_t
add_hash (grub_uint8_t *const data, const grub_size_t data_size, sb_database_t *sb_database)
{
  grub_uint8_t **hashes;
  grub_size_t *hash_sizes;

  if (data == NULL || data_size == 0)
    return grub_error (GRUB_ERR_OUT_OF_RANGE, "certificate/binary-hash data or size is not available");

  if (sb_database->is_db == true)
    {
      if (check_hash_presence (data, data_size, &dbx) == true)
        {
          grub_dprintf ("appendedsig",
                        "cannot add a hash (%02x%02x%02x%02x), as it is present in the dbx list\n",
                        data[0], data[1], data[2], data[3]);
          return GRUB_ERR_ACCESS_DENIED;
        }
    }

  if (check_hash_presence (data, data_size, sb_database) == true)
    {
      grub_dprintf ("appendedsig",
                    "cannot add a hash (%02x%02x%02x%02x), as it is present in the %s list\n",
                    data[0], data[1], data[2], data[3], ((sb_database->is_db == true) ? "db" : "dbx"));
      return GRUB_ERR_EXISTS;
    }

  hashes = grub_realloc (sb_database->hashes, sizeof (grub_uint8_t *) * (sb_database->hash_entries + 1));
  if (hashes == NULL)
    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");

  hash_sizes = grub_realloc (sb_database->hash_sizes, sizeof (grub_size_t) * (sb_database->hash_entries + 1));
  if (hash_sizes == NULL)
    {
      /* Allocated memory will be freed by free_db_list()/free_dbx_list(). */
      hashes[sb_database->hash_entries] = NULL;
      sb_database->hashes = hashes;
      sb_database->hash_entries++;

      return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
    }

  hashes[sb_database->hash_entries] = grub_malloc (data_size);
  if (hashes[sb_database->hash_entries] == NULL)
    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");

  grub_dprintf ("appendedsig",
                "added the hash %02x%02x%02x%02x... with size of %" PRIuGRUB_SIZE " to the %s list\n",
                data[0], data[1], data[2], data[3], data_size,
                ((sb_database->is_db == true) ? "db" : "dbx"));

  grub_memcpy (hashes[sb_database->hash_entries], data, data_size);
  hash_sizes[sb_database->hash_entries] = data_size;
  sb_database->hash_sizes = hash_sizes;
  sb_database->hashes = hashes;
  sb_database->hash_entries++;

  return GRUB_ERR_NONE;
}

static bool
is_hash (const grub_packed_guid_t *guid)
{
  /* GUID type of the binary hash. */
  if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
      grub_memcmp (guid, &GRUB_PKS_CERT_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
      grub_memcmp (guid, &GRUB_PKS_CERT_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0)
    return true;

  /* GUID type of the certificate hash. */
  if (grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
      grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
      grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0)
    return true;

  return false;
}

static bool
is_x509 (const grub_packed_guid_t *guid)
{
  if (grub_memcmp (guid, &GRUB_PKS_CERT_X509_GUID, GRUB_PACKED_GUID_SIZE) == 0)
    return true;

  return false;
}

static bool
is_cert_match (const grub_x509_cert_t *cert1, const grub_x509_cert_t *cert2)
{
  if (grub_memcmp (cert1->subject, cert2->subject, cert2->subject_len) == 0
      && grub_memcmp (cert1->issuer, cert2->issuer, cert2->issuer_len) == 0
      && grub_memcmp (cert1->serial, cert2->serial, cert2->serial_len) == 0
      && grub_memcmp (cert1->mpis[GRUB_RSA_PK_MODULUS], cert2->mpis[GRUB_RSA_PK_MODULUS],
                      sizeof (cert2->mpis[GRUB_RSA_PK_MODULUS])) == 0
      && grub_memcmp (cert1->mpis[GRUB_RSA_PK_EXPONENT], cert2->mpis[GRUB_RSA_PK_EXPONENT],
                      sizeof (cert2->mpis[GRUB_RSA_PK_EXPONENT])) == 0
      && grub_memcmp (cert1->fingerprint[GRUB_FINGERPRINT_SHA256],
                      cert2->fingerprint[GRUB_FINGERPRINT_SHA256],
                      grub_strlen ((char *) cert2->fingerprint[GRUB_FINGERPRINT_SHA256])) == 0)
    return true;

  return false;
}

/* Check the certificate hash presence in the dbx list. */
static bool
is_cert_hash_present_in_dbx (const grub_uint8_t *data, const grub_size_t data_size)
{
  grub_err_t rc;
  grub_uint32_t i;
  grub_size_t cert_hash_size = 0;
  grub_uint8_t cert_hash[GRUB_MAX_HASH_LEN] = { 0 };

  for (i = 0; i < dbx.hash_entries; i++)
    {
      if (dbx.hashes[i] == NULL)
        continue;

      rc = generate_cert_hash (dbx.hash_sizes[i], data, data_size, cert_hash, &cert_hash_size);
      if (rc != GRUB_ERR_NONE)
        continue;

      if (cert_hash_size == dbx.hash_sizes[i] &&
          grub_memcmp (dbx.hashes[i], cert_hash, cert_hash_size) == 0)
        return true;
    }

  return false;
}

/* Check the certificate presence in the db/dbx list. */
static bool
check_cert_presence (const grub_x509_cert_t *cert_in, const sb_database_t *sb_database)
{
  grub_x509_cert_t *cert;

  for (cert = sb_database->certs; cert != NULL; cert = cert->next)
    if (is_cert_match (cert, cert_in) == true)
      return true;

  return false;
}

/*
 * Add the certificate into the db list if it is not present in the dbx and db
 * list when is_db is true. Add the certificate into the dbx list when is_db is
 * false.
 */
static grub_err_t
add_certificate (const grub_uint8_t *data, const grub_size_t data_size,
                 sb_database_t *sb_database)
{
  grub_err_t rc;
  grub_x509_cert_t *cert;

  if (data == NULL || data_size == 0)
    return grub_error (GRUB_ERR_OUT_OF_RANGE, "certificate data or size is not available");

  cert = grub_zalloc (sizeof (grub_x509_cert_t));
  if (cert == NULL)
    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");

  rc = grub_x509_cert_parse (data, data_size, cert);
  if (rc != GRUB_ERR_NONE)
    {
      grub_dprintf ("appendedsig", "cannot add a certificate CN='%s' to the %s list\n",
                    cert->subject, (sb_database->is_db == true) ? "db" : "dbx");
      grub_free (cert);
      return rc;
    }

  /*
   * Only checks the certificate against dbx if is_db is true when dynamic key
   * management is enabled.
   */
  if (append_key_mgmt == true)
    {
      if (sb_database->is_db == true)
        {
          if (is_cert_hash_present_in_dbx (data, data_size) == true ||
              check_cert_presence (cert, &dbx) == true)
            {
              grub_dprintf ("appendedsig",
                            "cannot add a certificate CN='%s', as it is present in the dbx list",
                            cert->subject);
              rc = GRUB_ERR_ACCESS_DENIED;
              goto fail;
            }
        }
    }

  if (check_cert_presence (cert, sb_database) == true)
    {
      grub_dprintf ("appendedsig",
                    "cannot add a certificate CN='%s', as it is present in the %s list",
                    cert->subject, ((sb_database->is_db == true) ? "db" : "dbx"));
      rc = GRUB_ERR_EXISTS;
      goto fail;
    }

  grub_dprintf ("appendedsig", "added a certificate CN='%s' to the %s list\n",
                cert->subject, ((sb_database->is_db == true) ? "db" : "dbx"));

  cert->next = sb_database->certs;
  sb_database->certs = cert;
  sb_database->cert_entries++;

  return rc;

 fail:
  grub_x509_cert_release (cert);
  grub_free (cert);

  return rc;
}

static void
_remove_cert_from_db (const grub_x509_cert_t *cert)
{
  grub_uint32_t i = 1;
  grub_x509_cert_t *curr_cert, *prev_cert;

  for (curr_cert = prev_cert = db.certs; curr_cert != NULL; curr_cert = curr_cert->next, i++)
    {
      if (is_cert_match (curr_cert, cert) == true)
        {
          if (i == 1) /* Match with first certificate in the db list. */
            db.certs = curr_cert->next;
          else
            prev_cert->next = curr_cert->next;

          grub_dprintf ("appendedsig",
                        "removed distrusted certificate with CN: %s from the db list\n",
                        curr_cert->subject);
          curr_cert->next = NULL;
          grub_x509_cert_release (curr_cert);
          grub_free (curr_cert);
          break;
        }
      else
        prev_cert = curr_cert;
    }
}

static grub_err_t
remove_cert_from_db (const grub_uint8_t *data, const grub_size_t data_size)
{
  grub_err_t rc;
  grub_x509_cert_t *cert;

  if (data == NULL || data_size == 0)
    return grub_error (GRUB_ERR_OUT_OF_RANGE, "certificate data or size is not available");

  cert = grub_zalloc (sizeof (grub_x509_cert_t));
  if (cert == NULL)
    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");

  rc = grub_x509_cert_parse (data, data_size, cert);
  if (rc != GRUB_ERR_NONE)
    {
      grub_dprintf ("appendedsig", "cannot remove an invalid certificate from the db list\n");
      grub_free (cert);
      return rc;
    }

  /* Remove certificate from the db list. */
  _remove_cert_from_db (cert);

  return rc;
}

static bool
cert_fingerprint_match (const grub_uint8_t *hash_data, const grub_size_t hash_data_size,
                        const grub_x509_cert_t *cert)
{
  grub_int32_t type;

  if (hash_data_size == SHA256_HASH_SIZE)
    type = GRUB_FINGERPRINT_SHA256;
  else if (hash_data_size == SHA384_HASH_SIZE)
    type = GRUB_FINGERPRINT_SHA384;
  else if (hash_data_size == SHA512_HASH_SIZE)
    type = GRUB_FINGERPRINT_SHA512;
  else
    {
      grub_dprintf ("appendedsig", "unsupported fingerprint hash type "
                    "(%" PRIuGRUB_SIZE ") \n", hash_data_size);
      return false;
    }

  if (grub_memcmp (cert->fingerprint[type], hash_data, hash_data_size) == 0)
    return true;

  return false;
}

static void
remove_hash_from_db (const grub_uint8_t *hash_data, const grub_size_t hash_data_size,
                     const bool bin_hash)
{
  grub_uint32_t i;
  grub_x509_cert_t *cert;

  if (bin_hash == true)
    {
      for (i = 0; i < db.hash_entries; i++)
        {
          if (db.hashes[i] == NULL)
            continue;

          if (grub_memcmp (db.hashes[i], hash_data, hash_data_size) == 0)
            {
              grub_dprintf ("appendedsig", "removed distrusted hash %02x%02x%02x%02x.. from the db list\n",
                            db.hashes[i][0], db.hashes[i][1], db.hashes[i][2], db.hashes[i][3]);
              grub_free (db.hashes[i]);
              db.hashes[i] = NULL;
              db.hash_sizes[i] = 0;
              break;
            }
        }
    }
  else
    {
      for (cert = db.certs; cert != NULL; cert = cert->next)
        {
          if (cert_fingerprint_match (hash_data, hash_data_size, cert) == true)
            {
              _remove_cert_from_db (cert);
              break;
            }
        }
    }
}

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_OFFSET " 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
extract_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize,
                            sb_appendedsig_t *sig)
{
  grub_size_t appendedsig_pkcs7_size;
  grub_size_t signed_data_size = bufsize;
  const grub_uint8_t *signed_data = buf;

  if (signed_data_size < SIG_MAGIC_SIZE)
    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature magic");

  /* Fast-forwarding pointer and get signature magic string. */
  signed_data += signed_data_size - SIG_MAGIC_SIZE;
  if (grub_strncmp ((const char *) signed_data, SIG_MAGIC, SIG_MAGIC_SIZE))
    return grub_error (GRUB_ERR_BAD_SIGNATURE, "missing or invalid signature magic");

  signed_data_size -= SIG_MAGIC_SIZE;
  if (signed_data_size < SIG_METADATA_SIZE)
    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature metadata");

  /* Rewind pointer and extract signature metadata. */
  signed_data -= SIG_METADATA_SIZE;
  grub_memcpy (&(sig->sig_metadata), signed_data, SIG_METADATA_SIZE);

  if (sig->sig_metadata.id_type != PKEY_ID_PKCS7)
    return grub_error (GRUB_ERR_BAD_SIGNATURE, "wrong signature type");

  appendedsig_pkcs7_size = grub_be_to_cpu32 (sig->sig_metadata.sig_len);

  signed_data_size -= SIG_METADATA_SIZE;
  if (appendedsig_pkcs7_size > signed_data_size)
    return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for PKCS#7 message");

  grub_dprintf ("appendedsig", "sig len %" PRIuGRUB_SIZE "\n", appendedsig_pkcs7_size);

  /* Appended signature size. */
  sig->signature_len = APPENDED_SIG_SIZE (appendedsig_pkcs7_size);
  /* Rewind pointer and parse appended pkcs7 data. */
  signed_data -= appendedsig_pkcs7_size;

  return grub_pkcs7_data_parse (signed_data, appendedsig_pkcs7_size, &sig->pkcs7);
}

static grub_err_t
get_binary_hash (const grub_size_t binary_hash_size, const grub_uint8_t *data,
                 const grub_size_t data_size, grub_uint8_t *hash, grub_size_t *hash_size)
{
  grub_packed_guid_t guid = { 0 };

  /* support SHA256, SHA384 and SHA512 for binary hash */
  if (binary_hash_size == SHA256_HASH_SIZE)
    grub_memcpy (&guid, &GRUB_PKS_CERT_SHA256_GUID, GRUB_PACKED_GUID_SIZE);
  else if (binary_hash_size == SHA384_HASH_SIZE)
    grub_memcpy (&guid, &GRUB_PKS_CERT_SHA384_GUID, GRUB_PACKED_GUID_SIZE);
  else if (binary_hash_size == SHA512_HASH_SIZE)
    grub_memcpy (&guid, &GRUB_PKS_CERT_SHA512_GUID, GRUB_PACKED_GUID_SIZE);
  else
    {
      grub_dprintf ("appendedsig", "unsupported hash type (%" PRIuGRUB_SIZE ") and "
                    "skipped\n", binary_hash_size);
      return GRUB_ERR_UNKNOWN_COMMAND;
    }

  return get_hash (&guid, data, data_size, hash, hash_size);
}

/*
 * Verify binary hash against the db and dbx list.
 * The following errors can occur:
 *  - GRUB_ERR_BAD_SIGNATURE: indicates that the hash is in dbx list.
 *  - GRUB_ERR_EOF: the hash could not be found in the db and dbx list.
 *  - GRUB_ERR_NONE: the hash is found in db list.
 */
static grub_err_t
verify_binary_hash (const grub_uint8_t *data, const grub_size_t data_size)
{
  grub_err_t rc = GRUB_ERR_NONE;
  grub_uint32_t i;
  grub_size_t hash_size = 0;
  grub_uint8_t hash[GRUB_MAX_HASH_LEN] = { 0 };

  for (i = 0; i < dbx.hash_entries; i++)
    {
      if (dbx.hashes[i] == NULL)
        continue;

      rc = get_binary_hash (dbx.hash_sizes[i], data, data_size, hash, &hash_size);
      if (rc != GRUB_ERR_NONE)
        continue;

      if (hash_size == dbx.hash_sizes[i] &&
          grub_memcmp (dbx.hashes[i], hash, hash_size) == 0)
        {
          grub_dprintf ("appendedsig", "the hash (%02x%02x%02x%02x) is present in the dbx list\n",
                        hash[0], hash[1], hash[2], hash[3]);
          return GRUB_ERR_BAD_SIGNATURE;
        }
    }

  for (i = 0; i < db.hash_entries; i++)
    {
      if (db.hashes[i] == NULL)
        continue;

      rc = get_binary_hash (db.hash_sizes[i], data, data_size, hash, &hash_size);
      if (rc != GRUB_ERR_NONE)
        continue;

      if (hash_size == db.hash_sizes[i] &&
          grub_memcmp (db.hashes[i], hash, hash_size) == 0)
        {
          grub_dprintf ("appendedsig", "verified with a trusted hash (%02x%02x%02x%02x)\n",
                        hash[0], hash[1], hash[2], hash[3]);
          return GRUB_ERR_NONE;
        }
    }

  return GRUB_ERR_EOF;
}

/*
 * 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)
    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;
}

static grub_err_t
grub_verify_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize)
{
  grub_err_t err;
  grub_size_t datasize;
  void *context;
  grub_uint8_t *hash;
  grub_x509_cert_t *pk;
  sb_appendedsig_t sig;
  grub_pkcs7_signer_t *si;
  grub_int32_t i;

  if (!db.cert_entries && !db.hash_entries)
    return grub_error (GRUB_ERR_BAD_SIGNATURE, "no trusted keys to verify against");

  err = extract_appended_signature (buf, bufsize, &sig);
  if (err != GRUB_ERR_NONE)
    return err;

  append_sig_len = sig.signature_len;
  datasize = bufsize - sig.signature_len;

  /*
   * If signature verification is enabled with dynamic key management mode,
   * Verify binary hash against the db and dbx list.
   */
  if (append_key_mgmt == true)
    {
      err = verify_binary_hash (buf, datasize);
      if (err == GRUB_ERR_BAD_SIGNATURE)
        {
          grub_pkcs7_data_release (&sig.pkcs7);
          return grub_error (err,
                             "failed to verify the binary hash against a trusted binary hash");
        }
    }

  /* Verify signature using trusted keys from db list. */
  for (i = 0; i < sig.pkcs7.signer_count; i++)
    {
      si = &sig.pkcs7.signers[i];
      context = grub_zalloc (si->hash->contextsize);
      if (context == NULL)
        return grub_errno;

      si->hash->init (context, 0);
      si->hash->write (context, buf, datasize);
      si->hash->final (context);
      hash = si->hash->read (context);

      grub_dprintf ("appendedsig", "data size %" PRIuGRUB_SIZE ", signer %d hash %02x%02x%02x%02x...\n",
                    datasize, i, hash[0], hash[1], hash[2], hash[3]);

      for (pk = db.certs; pk != NULL; pk = pk->next)
        {
          err = verify_signature (pk->mpis, si->sig_mpi, si->hash, hash);
          if (err == GRUB_ERR_NONE)
            {
              grub_dprintf ("appendedsig", "verify signer %d with key '%s' succeeded\n",
                            i, pk->subject);
              break;
            }

          grub_dprintf ("appendedsig", "verify signer %d with key '%s' failed\n",
                        i, pk->subject);
        }

      grub_free (context);
      if (err == GRUB_ERR_NONE)
        break;
    }

  grub_pkcs7_data_release (&sig.pkcs7);

  if (err != GRUB_ERR_NONE)
    return grub_error (err, "failed to verify signature against a trusted key");

  return err;
}

static grub_err_t
grub_cmd_verify_signature (grub_command_t cmd __attribute__ ((unused)), int argc, char **args)
{
  grub_file_t signed_file;
  grub_err_t err;
  grub_uint8_t *signed_data = NULL;
  grub_size_t signed_data_size = 0;

  if (argc != 1)
    return grub_error (GRUB_ERR_BAD_ARGUMENT,
                       "a signed file is expected\nExample:\n\tappend_verify <SIGNED FILE>\n");

  if (!grub_strlen (args[0]))
    return grub_error (GRUB_ERR_BAD_FILENAME, "missing signed file");

  grub_dprintf ("appendedsig", "verifying %s\n", args[0]);

  signed_file = grub_file_open (args[0], GRUB_FILE_TYPE_VERIFY_SIGNATURE);
  if (signed_file == NULL)
    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "could not open %s file", args[0]);

  err = file_read_whole (signed_file, &signed_data, &signed_data_size);
  if (err == GRUB_ERR_NONE)
    {
      err = grub_verify_appended_signature (signed_data, signed_data_size);
      grub_free (signed_data);
    }

  grub_file_close (signed_file);

  return err;
}

/*
 * Checks the trusted certificate against dbx list if dynamic key management is
 * enabled. And add it to the db list if it is not already present.
 *
 * Note: When signature verification is enabled, this command only accepts the
 * trusted certificate that is signed with an appended signature.
 * The signature is verified by the appendedsig module. If verification succeeds,
 * the certificate is added to the db list. Otherwise, an error is posted and
 * the certificate is not added.
 * When signature verification is disabled, it accepts the trusted certificate
 * without an appended signature and add it to the db list.
 *
 * Also, note that the adding of the trusted certificate using this command does
 * not persist across reboots.
 */
static grub_err_t
grub_cmd_db_cert (grub_command_t cmd __attribute__ ((unused)), int argc, char **args)
{
  grub_err_t err;
  grub_file_t cert_file;
  grub_uint8_t *cert_data = NULL;
  grub_size_t cert_data_size = 0;

  if (argc != 1)
    return grub_error (GRUB_ERR_BAD_ARGUMENT,
                       "a trusted X.509 certificate file is expected in DER format\n"
                       "Example:\n\tappend_add_db_cert <X509_CERTIFICATE>\n");

  if (!grub_strlen (args[0]))
    return grub_error (GRUB_ERR_BAD_FILENAME, "missing trusted X.509 certificate file");

  cert_file = grub_file_open (args[0],
                              GRUB_FILE_TYPE_CERTIFICATE_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
  if (cert_file == NULL)
    return grub_error (GRUB_ERR_BAD_FILE_TYPE, "could not open %s file", args[0]);

  err = file_read_whole (cert_file, &cert_data, &cert_data_size);
  grub_file_close (cert_file);
  if (err != GRUB_ERR_NONE)
    return err;

  /*
   * If signature verification is enabled (check_sigs is set to true), obtain
   * the actual certificate size by subtracting the appended signature size from
   * the certificate size because the certificate has an appended signature, and
   * this actual certificate size is used to get the X.509 certificate.
   */
  if (check_sigs == true)
    cert_data_size -= append_sig_len;

  err = add_certificate (cert_data, cert_data_size, &db);
  grub_free (cert_data);

  return err;
}

/*
 * Remove the distrusted certificate from the db list if it is already present.
 * And add it to the dbx list if not present when dynamic key management is
 * enabled.
 *
 * Note: When signature verification is enabled, this command only accepts the
 * distrusted certificate that is signed with an appended signature.
 * The signature is verified by the appended sig module. If verification
 * succeeds, the certificate is removed from the db list. Otherwise, an error
 * is posted and the certificate is not removed.
 * When signature verification is disabled, it accepts the distrusted certificate
 * without an appended signature and removes it from the db list.
 *
 * Also, note that the removal of the distrusted certificate using this command
 * does not persist across reboots.
 */
static grub_err_t
grub_cmd_dbx_cert (grub_command_t cmd __attribute__ ((unused)), int argc, char **args)
{
  grub_err_t err;
  grub_file_t cert_file;
  grub_uint8_t *cert_data = NULL;
  grub_size_t cert_data_size = 0;

  if (argc != 1)
    return grub_error (GRUB_ERR_BAD_ARGUMENT,
                       "a distrusted X.509 certificate file is expected in DER format\n"
                       "Example:\n\tappend_add_dbx_cert <X509_CERTIFICATE>\n");

  if (!grub_strlen (args[0]))
    return grub_error (GRUB_ERR_BAD_FILENAME, "missing distrusted X.509 certificate file");

  cert_file = grub_file_open (args[0],
                              GRUB_FILE_TYPE_CERTIFICATE_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
  if (cert_file == NULL)
    return grub_error (GRUB_ERR_BAD_FILE_TYPE, "could not open %s file", args[0]);

  err = file_read_whole (cert_file, &cert_data, &cert_data_size);
  grub_file_close (cert_file);
  if (err != GRUB_ERR_NONE)
    return err;

  /*
   * If signature verification is enabled (check_sigs is set to true), obtain
   * the actual certificate size by subtracting the appended signature size from
   * the certificate size because the certificate has an appended signature, and
   * this actual certificate size is used to get the X.509 certificate.
   */
  if (check_sigs == true)
    cert_data_size -= append_sig_len;

  /* Remove distrusted certificate from the db list if present. */
  err = remove_cert_from_db (cert_data, cert_data_size);
  if (err != GRUB_ERR_NONE)
    {
      grub_free (cert_data);
      return err;
    }

  /* Only add the certificate to the dbx list if dynamic key management is enabled. */
  if (append_key_mgmt == true)
    err = add_certificate (cert_data, cert_data_size, &dbx);

  grub_free (cert_data);

  return err;
}

static grub_err_t
grub_cmd_list_db (grub_command_t cmd __attribute__ ((unused)), int argc __attribute__ ((unused)),
                  char **args __attribute__ ((unused)))
{
  struct x509_certificate *cert;
  grub_uint32_t i, cert_num = 1;

  for (cert = db.certs; cert != NULL; cert = cert->next, cert_num++)
    print_certificate (cert, cert_num);

  if (append_key_mgmt == false)
    return GRUB_ERR_NONE;

  for (i = 0; i < db.hash_entries; i++)
    {
      if (db.hashes[i] != NULL)
        {
          grub_printf ("\nBinary hash: %u\n", i + 1);
          grub_printf ("    Hash: sha%" PRIuGRUB_SIZE "\n         ", db.hash_sizes[i] * 8);
          hexdump_colon (db.hashes[i], db.hash_sizes[i]);
        }
    }

  return GRUB_ERR_NONE;
}

static grub_err_t
grub_cmd_list_dbx (grub_command_t cmd __attribute__((unused)),
                   int argc __attribute__((unused)), char **args __attribute__((unused)))
{
  struct x509_certificate *cert;
  grub_uint32_t i, cert_num = 1;

  if (append_key_mgmt == false)
    return grub_error (GRUB_ERR_ACCESS_DENIED,
                       "append_list_dbx command is unsupported in static key mode");

  for (cert = dbx.certs; cert != NULL; cert = cert->next, cert_num++)
    print_certificate (cert, cert_num);

  for (i = 0; i < dbx.hash_entries; i++)
    {
      if (dbx.hashes[i] != NULL)
        {
          grub_printf ("\nCertificate/Binary hash: %u\n", i + 1);
          grub_printf ("    Hash: sha%" PRIuGRUB_SIZE "\n         ", dbx.hash_sizes[i] * 8);
          hexdump_colon (dbx.hashes[i], dbx.hash_sizes[i]);
        }
    }

  return GRUB_ERR_NONE;
}

/*
 * Remove the trusted binary hash from the dbx list if present. And add them to
 * the db list if it is not already present.
 *
 * Note: When signature verification is enabled, this command only accepts the
 * binary hash file that is signed with an appended signature. The signature is
 * verified by the appendedsig module. If verification succeeds, the binary hash
 * is added to the db list. Otherwise, an error is posted and the binary hash is
 * not added.
 * When signature verification is disabled, it accepts the binary hash file
 * without an appended signature and adds it to the db list.
 *
 * Also, note that the adding of the trusted binary hash using this command does
 * not persist across reboots.
 */
static grub_err_t
grub_cmd_add_db_hash (grub_command_t cmd __attribute__((unused)), int argc, char**args)
{
  grub_err_t rc;
  grub_file_t hash_file;
  grub_uint8_t *hash_data = NULL;
  grub_size_t hash_data_size = 0;

  if (append_key_mgmt == false)
    return grub_error (GRUB_ERR_ACCESS_DENIED,
                       "append_add_db_hash command is unsupported in static key mode");

  if (argc != 1)
    return grub_error (GRUB_ERR_BAD_ARGUMENT,
                       "a trusted binary hash file is expected in binary format\n"
                       "Example:\n\tappend_add_db_hash <BINARY HASH FILE>\n");

  if (!grub_strlen (args[0]))
    return grub_error (GRUB_ERR_BAD_FILENAME, "missing trusted binary hash file");

  hash_file = grub_file_open (args[0], GRUB_FILE_TYPE_HASH_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
  if (hash_file == NULL)
    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "unable to open %s file", args[0]);

  rc = file_read_whole (hash_file, &hash_data, &hash_data_size);
  grub_file_close (hash_file);
  if (rc != GRUB_ERR_NONE)
    return rc;

  /*
   * If signature verification is enabled (check_sigs is set to true), obtain
   * the actual hash data size by subtracting the appended signature size from
   * the hash data size because the hash has an appended signature, and this
   * actual hash data size is used to get the hash data.
   */
  if (check_sigs == true)
    hash_data_size -= append_sig_len;

  grub_dprintf ("appendedsig",
                "adding a trusted binary hash %02x%02x%02x%02x... with size of %" PRIuGRUB_SIZE "\n",
                hash_data[0], hash_data[1], hash_data[2], hash_data[3], hash_data_size);

  /* Only accept SHA256, SHA384 and SHA512 binary hash */
  if (hash_data_size != SHA256_HASH_SIZE && hash_data_size != SHA384_HASH_SIZE &&
      hash_data_size != SHA512_HASH_SIZE)
    {
      grub_free (hash_data);
      return grub_error (GRUB_ERR_BAD_SIGNATURE, "unacceptable trusted binary hash type");
    }

  rc = add_hash (hash_data, hash_data_size, &db);
  grub_free (hash_data);

  return rc;
}

/*
 * Remove the distrusted binary/certificate hash from the db list if present.
 * And add them to the dbx list if it is not already present.
 *
 * Note: When signature verification is enabled, this command only accepts the
 * binary/certificate hash file that is signed with an appended signature. The
 * signature is verified by the appendedsig module. If verification succeeds,
 * the binary/certificate hash is added to the dbx list. Otherwise, an error is
 * posted and the binary/certificate hash is not added.
 * When signature verification is disabled, it accepts the binary/certificate
 * hash file without an appended signature and adds it to the dbx list.
 *
 * Also, note that the adding of the distrusted binary/certificate hash using
 * this command does not persist across reboots.
 */
static grub_err_t
grub_cmd_add_dbx_hash (grub_extcmd_context_t ctxt, int argc __attribute__ ((unused)),
                       char **args __attribute__ ((unused)))
{
  grub_err_t rc;
  grub_file_t hash_file;
  grub_uint8_t *hash_data = NULL;
  grub_size_t hash_data_size = 0;
  char *file_path;

  if (append_key_mgmt == false)
    return grub_error (GRUB_ERR_ACCESS_DENIED,
                       "append_add_dbx_hash command is unsupported in static key mode");

  if (!ctxt->state[OPTION_BINARY_HASH].set && !ctxt->state[OPTION_CERT_HASH].set)
    return grub_error (GRUB_ERR_BAD_ARGUMENT,
                       "a distrusted certificate/binary hash file is expected in binary format\n"
                       "Example:\n\tappend_add_dbx_hash [option] <FILE>\n"
                       "option:\n[-b|--binary-hash] FILE [BINARY HASH FILE]\n"
                       "[-c|--cert-hash] FILE [CERTFICATE HASH FILE]\n");

  if (ctxt->state[OPTION_BINARY_HASH].arg == NULL && ctxt->state[OPTION_CERT_HASH].arg == NULL)
    return grub_error (GRUB_ERR_BAD_FILENAME, "missing distrusted certificate/binary hash file");

  if (ctxt->state[OPTION_BINARY_HASH].arg != NULL)
    file_path = ctxt->state[OPTION_BINARY_HASH].arg;
  else
    file_path = ctxt->state[OPTION_CERT_HASH].arg;

  hash_file = grub_file_open (file_path, GRUB_FILE_TYPE_HASH_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
  if (hash_file == NULL)
    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "unable to open %s file", file_path);

  rc = file_read_whole (hash_file, &hash_data, &hash_data_size);
  grub_file_close (hash_file);
  if (rc != GRUB_ERR_NONE)
    return rc;

  /*
   * If signature verification is enabled (check_sigs is set to true), obtain
   * the actual hash data size by subtracting the appended signature size from
   * the hash data size because the hash has an appended signature, and this
   * actual hash data size is used to get the hash data.
   */
  if (check_sigs == true)
    hash_data_size -= append_sig_len;

  grub_dprintf ("appendedsig",
                "adding a distrusted certificate/binary hash %02x%02x%02x%02x..."
                " with size of %" PRIuGRUB_SIZE "\n", hash_data[0], hash_data[1],
                hash_data[2], hash_data[3], hash_data_size);

  if (ctxt->state[OPTION_BINARY_HASH].set || ctxt->state[OPTION_CERT_HASH].set)
    {
      /* Only accept SHA256, SHA384 and SHA512 certificate/binary hash */
      if (hash_data_size != SHA256_HASH_SIZE && hash_data_size != SHA384_HASH_SIZE &&
          hash_data_size != SHA512_HASH_SIZE)
        {
          grub_free (hash_data);
          return grub_error (GRUB_ERR_BAD_SIGNATURE,
                             "unacceptable distrusted certificate/binary hash type");
        }
    }

  /* Remove distrusted binary hash/certificate from the db list if present. */
  remove_hash_from_db (hash_data, hash_data_size,
                       (ctxt->state[OPTION_BINARY_HASH].set) ? true : false);
  rc = add_hash (hash_data, hash_data_size, &dbx);
  grub_free (hash_data);

  return rc;
}

/* Add the X.509 certificates/binary hash to the db list from PKS. */
static grub_err_t
load_pks2db (void)
{
  grub_err_t rc;
  grub_uint32_t i;

  for (i = 0; i < pks_keystore->db_entries; i++)
    {
      if (is_hash (&pks_keystore->db[i].guid) == true)
        {
          rc = add_hash (pks_keystore->db[i].data,
                         pks_keystore->db[i].data_size, &db);
          if (rc == GRUB_ERR_OUT_OF_MEMORY)
            return rc;
        }
      else if (is_x509 (&pks_keystore->db[i].guid) == true)
        {
          rc = add_certificate (pks_keystore->db[i].data,
                                pks_keystore->db[i].data_size, &db);
          if (rc == GRUB_ERR_OUT_OF_MEMORY)
            return rc;
        }
      else
        grub_dprintf ("appendedsig", "unsupported signature data type and "
                      "skipped (%u)\n", i + 1);
    }

  return GRUB_ERR_NONE;
}

/* Add the certificates and certificate/binary hash to the dbx list from PKS. */
static grub_err_t
load_pks2dbx (void)
{
  grub_err_t rc;
  grub_uint32_t i;

  for (i = 0; i < pks_keystore->dbx_entries; i++)
    {
      if (is_x509 (&pks_keystore->dbx[i].guid) == true)
        {
          rc = add_certificate (pks_keystore->dbx[i].data,
                                pks_keystore->dbx[i].data_size, &dbx);
          if (rc == GRUB_ERR_OUT_OF_MEMORY)
            return rc;
        }
      else if (is_hash (&pks_keystore->dbx[i].guid) == true)
        {
          rc = add_hash (pks_keystore->dbx[i].data,
                         pks_keystore->dbx[i].data_size, &dbx);
          if (rc != GRUB_ERR_NONE)
            return rc;
        }
      else
        grub_dprintf ("appendedsig", "unsupported signature data type and "
                      "skipped (%u)\n", i + 1);
    }

  return GRUB_ERR_NONE;
}

/*
 * Extract the X.509 certificates from the ELF Note header, parse it, and add
 * it to the db list.
 */
static void
load_elf2db (void)
{
  grub_err_t err;
  struct grub_module_header *header;
  struct grub_file pseudo_file;
  grub_uint8_t *cert_data = NULL;
  grub_size_t cert_data_size = 0;

  FOR_MODULES (header)
    {
      /* Not an X.509 certificate, skip. */
      if (header->type != OBJ_TYPE_X509_PUBKEY)
        continue;

      grub_memset (&pseudo_file, 0, sizeof (pseudo_file));
      pseudo_file.fs = &pseudo_fs;
      pseudo_file.size = header->size - sizeof (struct grub_module_header);
      pseudo_file.data = (char *) header + sizeof (struct grub_module_header);

      grub_dprintf ("appendedsig", "found an X.509 certificate, size=%" PRIuGRUB_UINT64_T "\n",
                    pseudo_file.size);

      err = file_read_whole (&pseudo_file, &cert_data, &cert_data_size);
      if (err == GRUB_ERR_OUT_OF_MEMORY)
        return;
      else if (err != GRUB_ERR_NONE)
        continue;

      err = add_certificate (cert_data, cert_data_size, &db);
      grub_free (cert_data);
      if (err == GRUB_ERR_OUT_OF_MEMORY)
        return;
    }
}

/*
 * Extract trusted and distrusted keys from PKS and store them in the db and
 * dbx list.
 */
static void
create_dbs_from_pks (void)
{
  grub_err_t err;

  err = load_pks2dbx ();
  if (err != GRUB_ERR_NONE)
    grub_printf ("warning: dbx list might not be fully populated\n");

  /*
   * If db does not exist in the PKS storage, then read the static keys as a db
   * default keys from the GRUB ELF Note and add them into the db list.
   */
  if (pks_keystore->db_exists == false)
    load_elf2db ();
  else
    {
      err = load_pks2db ();
      if (err != GRUB_ERR_NONE)
        grub_printf ("warning: db list might not be fully populated\n");
    }

  grub_pks_free_data ();
  grub_dprintf ("appendedsig", "the db list now has %u keys\n"
                "the dbx list now has %u keys\n",
                db.hash_entries + db.cert_entries,
                dbx.hash_entries + dbx.cert_entries);
}

/* Free db list memory */
static void
free_db_list (void)
{
  grub_x509_cert_t *cert;
  grub_uint32_t i;

  while (db.certs != NULL)
    {
      cert = db.certs;
      db.certs = db.certs->next;
      grub_x509_cert_release (cert);
      grub_free (cert);
    }

  for (i = 0; i < db.hash_entries; i++)
    grub_free (db.hashes[i]);

  grub_free (db.hashes);
  grub_free (db.hash_sizes);
  grub_memset (&db, 0, sizeof (sb_database_t));
}

/* Free dbx list memory */
static void
free_dbx_list (void)
{
  grub_x509_cert_t *cert;
  grub_uint32_t i;

  while (dbx.certs != NULL)
    {
      cert = dbx.certs;
      dbx.certs = dbx.certs->next;
      grub_x509_cert_release (cert);
      grub_free (cert);
    }

  for (i = 0; i < dbx.hash_entries; i++)
    grub_free (dbx.hashes[i]);

  grub_free (dbx.hashes);
  grub_free (dbx.hash_sizes);
  grub_memset (&dbx, 0, sizeof (sb_database_t));
}

static const char *
grub_env_read_sec (struct grub_env_var *var __attribute__ ((unused)),
                   const char *val __attribute__ ((unused)))
{
  if (check_sigs == true)
    return "yes";

  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 enabled
   * (check_sigs is set to true) and GRUB is locked down.
   */
  if (check_sigs == true && grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
    {
      ret = grub_strdup ("yes");
      if (ret == NULL)
        grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not duplicate a string enforce");

      return ret;
    }

  if (grub_strcmp (val, "yes") == 0)
    check_sigs = true;
  else if (grub_strcmp (val, "no") == 0)
    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 const char *
grub_env_read_key_mgmt (struct grub_env_var *var __attribute__ ((unused)),
                        const char *val __attribute__ ((unused)))
{
  if (append_key_mgmt == true)
    return "dynamic";

  return "static";
}

static char *
grub_env_write_key_mgmt (struct grub_env_var *var __attribute__ ((unused)), const char *val)
{
  char *ret;

  /*
   * Do not allow the value to be changed if signature verification is enabled
   * (check_sigs is set to true) and GRUB is locked down.
   */
  if (check_sigs == true && grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
    {
      ret = grub_strdup (grub_env_read_key_mgmt (NULL, NULL));
      if (ret == NULL)
        grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");

      return ret;
    }

  if (grub_strcmp (val, "dynamic") == 0)
    append_key_mgmt = true;
  else if (grub_strcmp (val, "static") == 0)
    append_key_mgmt = false;

  ret = grub_strdup (grub_env_read_key_mgmt (NULL, NULL));
  if (ret == NULL)
    grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");

  return ret;
}

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 == false)
    {
      *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_HASH_TRUST:
        /*
         * This is a certificate/binary hash to add to db/dbx. This needs to be
         * verified or blocked.
         */
      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
         * 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;
    }
}

static grub_err_t
appendedsig_write (void *ctxt __attribute__ ((unused)), void *buf, grub_size_t size)
{
  return grub_verify_appended_signature (buf, size);
}

struct grub_file_verifier grub_appendedsig_verifier = {
  .name = "appendedsig",
  .init = appendedsig_init,
  .write = appendedsig_write,
};

static grub_command_t cmd_verify, cmd_list_db, cmd_dbx_cert, cmd_db_cert;
static grub_command_t cmd_list_dbx, cmd_db_hash;
static grub_extcmd_t cmd_dbx_hash;

GRUB_MOD_INIT (appendedsig)
{
  grub_int32_t rc;

  /*
   * If secure boot is enabled with enforce mode and GRUB is locked down, enable
   * signature verification.
   */
  if (grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
    check_sigs = true;

  /* If PKS keystore is available, use dynamic key management. */
  pks_keystore = grub_pks_get_keystore ();
  if (pks_keystore != NULL)
    append_key_mgmt = true;

  /*
   * This is appended signature verification environment variable. It is
   * automatically set to either "no" or "yes" based on the ’ibm,secure-boot’
   * device tree property.
   *
   * "no": No signature verification. This is the default.
   *
   * "yes": Enforce signature verification. When GRUB is locked down, user cannot
   *        change the value by setting the check_appended_signatures variable
   *        back to ‘no’
   */
  grub_register_variable_hook ("check_appended_signatures", grub_env_read_sec, grub_env_write_sec);
  grub_env_export ("check_appended_signatures");

  /*
   * This is appended signature key management environment variable. It is
   * automatically set to either "static" or "dynamic" based on the
   * Platform KeyStore.
   *
   * "static": Enforce static key management signature verification. This is
   *           the default. When the GRUB is locked down, user cannot change
   *           the value by setting the appendedsig_key_mgmt variable back to
   *           "dynamic".
   *
   * "dynamic": Enforce dynamic key management signature verification. When the
   *            GRUB is locked down, user cannot change the value by setting the
   *            appendedsig_key_mgmt variable back to "static".
   */
  grub_register_variable_hook ("appendedsig_key_mgmt", grub_env_read_key_mgmt, grub_env_write_key_mgmt);
  grub_env_export ("appendedsig_key_mgmt");

  rc = grub_asn1_init ();
  if (rc != ASN1_SUCCESS)
    grub_fatal ("error initing ASN.1 data structures: %d: %s\n", rc, asn1_strerror (rc));

  /*
   * If signature verification is enabled with the dynamic key management,
   * extract trusted and distrusted keys from PKS and store them in the db
   * and dbx list.
   */
  if (append_key_mgmt == true)
    create_dbs_from_pks ();
  /*
   * If signature verification is enabled with the static key management,
   * extract trusted keys from ELF Note and store them in the db list.
   */
  else
    {
      load_elf2db ();
      grub_dprintf ("appendedsig", "the db list now has %u static keys\n",
                    db.cert_entries);
    }

  cmd_verify = grub_register_command ("append_verify", grub_cmd_verify_signature, N_("<SIGNED_FILE>"),
                                      N_("Verify SIGNED_FILE against the trusted X.509 certificates in the db list"));
  cmd_list_db = grub_register_command ("append_list_db", grub_cmd_list_db, 0,
                                       N_("Show the list of trusted X.509 certificates from the db list"));
  cmd_db_cert = grub_register_command ("append_add_db_cert", grub_cmd_db_cert, N_("<X509_CERTIFICATE>"),
                                       N_("Add trusted X509_CERTIFICATE to the db list"));
  cmd_dbx_cert = grub_register_command ("append_add_dbx_cert", grub_cmd_dbx_cert, N_("<X509_CERTIFICATE>"),
                                        N_("Add distrusted X509_CERTIFICATE to the dbx list"));

  cmd_list_dbx = grub_register_command ("append_list_dbx", grub_cmd_list_dbx, 0,
                                        N_("Show the list of distrusted certificates and"
                                        " certificate/binary hashes from the dbx list"));
  cmd_db_hash = grub_register_command ("append_add_db_hash", grub_cmd_add_db_hash, N_("BINARY HASH FILE"),
                                       N_("Add trusted BINARY HASH to the db list."));
  cmd_dbx_hash = grub_register_extcmd ("append_add_dbx_hash", grub_cmd_add_dbx_hash, 0,
                                       N_("[-b|--binary-hash] FILE [BINARY HASH FILE]\n"
                                       "[-c|--cert-hash] FILE [CERTFICATE HASH FILE]"),
                                       N_("Add distrusted CERTFICATE/BINARY HASH to the dbx list."), options);

  grub_verifier_register (&grub_appendedsig_verifier);
  grub_dl_set_persistent (mod);
}

GRUB_MOD_FINI (appendedsig)
{
  /*
   * grub_dl_set_persistent should prevent this from actually running, but it
   * does still run under emu.
   */

  free_db_list ();
  free_dbx_list ();
  grub_register_variable_hook ("check_appended_signatures", NULL, NULL);
  grub_env_unset ("check_appended_signatures");
  grub_register_variable_hook ("appendedsig_key_mgmt", NULL, NULL);
  grub_env_unset ("appendedsig_key_mgmt");
  grub_verifier_unregister (&grub_appendedsig_verifier);
  grub_unregister_command (cmd_verify);
  grub_unregister_command (cmd_list_db);
  grub_unregister_command (cmd_db_cert);
  grub_unregister_command (cmd_dbx_cert);
  grub_unregister_command (cmd_list_dbx);
  grub_unregister_command (cmd_db_hash);
  grub_unregister_extcmd (cmd_dbx_hash);
}
