Blame SOURCES/0367-appended-signatures-support-verifying-appended-signa.patch

3efed6
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
3efed6
From: Daniel Axtens <dja@axtens.net>
3efed6
Date: Thu, 30 Jul 2020 01:35:43 +1000
3efed6
Subject: [PATCH] appended signatures: support verifying appended signatures
3efed6
3efed6
Building on the parsers and the ability to embed x509 certificates, as
3efed6
well as the existing gcrypt functionality, add a module for verifying
3efed6
appended signatures.
3efed6
3efed6
This includes:
3efed6
3efed6
 - a verifier that requires that kernels and grub modules have appended
3efed6
   signatures. It shares lots of logic with shim-lock verifier about what
3efed6
   files need to be verified and what modules are unsafe to have loaded.
3efed6
3efed6
 - commands to manage the list of trusted certificates for verification.
3efed6
3efed6
Similar to the PGP verifier, if a certificate is embedded in the core
3efed6
image, verification will be enforced unless disabled on the the grub
3efed6
command line or by load_env.
3efed6
3efed6
Thus, as with the PGP verifier, it is not a complete secure-boot solution:
3efed6
other mechanisms must be used to ensure that a user cannot drop to the
3efed6
grub shell and disable verification.
3efed6
3efed6
Signed-off-by: Daniel Axtens <dja@axtens.net>
3efed6
---
3efed6
 grub-core/Makefile.core.def                  |  12 +
3efed6
 grub-core/commands/appendedsig/appendedsig.c | 644 +++++++++++++++++++++++++++
3efed6
 include/grub/file.h                          |   2 +
3efed6
 3 files changed, 658 insertions(+)
3efed6
 create mode 100644 grub-core/commands/appendedsig/appendedsig.c
3efed6
3efed6
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
b71686
index fd1229c63..1cf6b60f8 100644
3efed6
--- a/grub-core/Makefile.core.def
3efed6
+++ b/grub-core/Makefile.core.def
3efed6
@@ -921,6 +921,18 @@ module = {
3efed6
   cppflags = '-I$(srcdir)/lib/posix_wrap';
3efed6
 };
3efed6
 
3efed6
+module = {
3efed6
+  name = appendedsig;
3efed6
+  common = commands/appendedsig/appendedsig.c;
3efed6
+  common = commands/appendedsig/x509.c;
3efed6
+  common = commands/appendedsig/pkcs7.c;
3efed6
+  common = commands/appendedsig/asn1util.c;
3efed6
+  common = commands/appendedsig/gnutls_asn1_tab.c;
3efed6
+  common = commands/appendedsig/pkix_asn1_tab.c;
3efed6
+  cflags = '$(CFLAGS_POSIX)';
3efed6
+  cppflags = '-I$(srcdir)/lib/posix_wrap';
3efed6
+};
3efed6
+
3efed6
 module = {
3efed6
   name = verifiers;
3efed6
   common = commands/verifiers.c;
3efed6
diff --git a/grub-core/commands/appendedsig/appendedsig.c b/grub-core/commands/appendedsig/appendedsig.c
3efed6
new file mode 100644
b71686
index 000000000..5d8897be5
3efed6
--- /dev/null
3efed6
+++ b/grub-core/commands/appendedsig/appendedsig.c
3efed6
@@ -0,0 +1,644 @@
3efed6
+/*
3efed6
+ *  GRUB  --  GRand Unified Bootloader
3efed6
+ *  Copyright (C) 2020  IBM Corporation.
3efed6
+ *
3efed6
+ *  GRUB is free software: you can redistribute it and/or modify
3efed6
+ *  it under the terms of the GNU General Public License as published by
3efed6
+ *  the Free Software Foundation, either version 3 of the License, or
3efed6
+ *  (at your option) any later version.
3efed6
+ *
3efed6
+ *  GRUB is distributed in the hope that it will be useful,
3efed6
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
3efed6
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3efed6
+ *  GNU General Public License for more details.
3efed6
+ *
3efed6
+ *  You should have received a copy of the GNU General Public License
3efed6
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
3efed6
+ */
3efed6
+
3efed6
+#include <grub/types.h>
3efed6
+#include <grub/misc.h>
3efed6
+#include <grub/mm.h>
3efed6
+#include <grub/err.h>
3efed6
+#include <grub/dl.h>
3efed6
+#include <grub/file.h>
3efed6
+#include <grub/command.h>
3efed6
+#include <grub/crypto.h>
3efed6
+#include <grub/pkcs1_v15.h>
3efed6
+#include <grub/i18n.h>
3efed6
+#include <grub/gcrypt/gcrypt.h>
3efed6
+#include <grub/kernel.h>
3efed6
+#include <grub/extcmd.h>
3efed6
+#include <grub/verify.h>
3efed6
+#include <grub/libtasn1.h>
3efed6
+#include <grub/env.h>
3efed6
+
3efed6
+#include "appendedsig.h"
3efed6
+
3efed6
+GRUB_MOD_LICENSE ("GPLv3+");
3efed6
+
3efed6
+const char magic[] = "~Module signature appended~\n";
3efed6
+
3efed6
+/*
3efed6
+ * This structure is extracted from scripts/sign-file.c in the linux kernel
3efed6
+ * source. It was licensed as LGPLv2.1+, which is GPLv3+ compatible.
3efed6
+ */
3efed6
+struct module_signature
3efed6
+{
3efed6
+  grub_uint8_t algo;		/* Public-key crypto algorithm [0] */
3efed6
+  grub_uint8_t hash;		/* Digest algorithm [0] */
3efed6
+  grub_uint8_t id_type;		/* Key identifier type [PKEY_ID_PKCS7] */
3efed6
+  grub_uint8_t signer_len;	/* Length of signer's name [0] */
3efed6
+  grub_uint8_t key_id_len;	/* Length of key identifier [0] */
3efed6
+  grub_uint8_t __pad[3];
3efed6
+  grub_uint32_t sig_len;	/* Length of signature data */
3efed6
+} GRUB_PACKED;
3efed6
+
3efed6
+
3efed6
+/* This represents an entire, parsed, appended signature */
3efed6
+struct grub_appended_signature
3efed6
+{
3efed6
+  grub_size_t signature_len;		/* Length of PKCS#7 data +
3efed6
+                                         * metadata + magic */
3efed6
+
3efed6
+  struct module_signature sig_metadata;	/* Module signature metadata */
3efed6
+  struct pkcs7_signedData pkcs7;	/* Parsed PKCS#7 data */
3efed6
+};
3efed6
+
3efed6
+/* Trusted certificates for verifying appended signatures */
3efed6
+struct x509_certificate *grub_trusted_key;
3efed6
+
3efed6
+/*
3efed6
+ * Force gcry_rsa to be a module dependency.
3efed6
+ *
3efed6
+ * If we use grub_crypto_pk_rsa, then then the gcry_rsa module won't be built
3efed6
+ * in if you add 'appendedsig' to grub-install --modules. You would need to
3efed6
+ * add 'gcry_rsa' too. That's confusing and seems suboptimal, especially when
3efed6
+ * we only support RSA.
3efed6
+ *
3efed6
+ * Dynamic loading also causes some concerns. We can't load gcry_rsa from the
3efed6
+ * the filesystem after we install the verifier - we won't be able to verify
3efed6
+ * it without having it already present. We also shouldn't load it before we
3efed6
+ * install the verifier, because that would mean it wouldn't be verified - an
3efed6
+ * attacker could insert any code they wanted into the module.
3efed6
+ *
3efed6
+ * So instead, reference the internal symbol from gcry_rsa. That creates a
3efed6
+ * direct dependency on gcry_rsa, so it will be built in when this module
3efed6
+ * is built in. Being built in (assuming the core image is itself signed!)
3efed6
+ * also resolves our concerns about loading from the filesystem.
3efed6
+ */
3efed6
+extern gcry_pk_spec_t _gcry_pubkey_spec_rsa;
3efed6
+
3efed6
+static int check_sigs = 0;
3efed6
+
3efed6
+static char *
3efed6
+grub_env_write_sec (struct grub_env_var *var __attribute__((unused)),
3efed6
+		    const char *val)
3efed6
+{
3efed6
+  check_sigs = (*val == '1') || (*val == 'e');
3efed6
+  return grub_strdup (check_sigs ? "enforce" : "no");
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+read_cert_from_file (grub_file_t f, struct x509_certificate *certificate)
3efed6
+{
3efed6
+  grub_err_t err;
3efed6
+  grub_uint8_t *buf = NULL;
3efed6
+  grub_ssize_t read_size;
3efed6
+  grub_off_t total_read_size = 0;
3efed6
+  grub_off_t file_size = grub_file_size (f);
3efed6
+
3efed6
+
3efed6
+  if (file_size == GRUB_FILE_SIZE_UNKNOWN)
3efed6
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
3efed6
+		       N_("Cannot parse a certificate file of unknown size"));
3efed6
+
3efed6
+  buf = grub_zalloc (file_size);
3efed6
+  if (!buf)
3efed6
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
3efed6
+		       N_("Could not allocate buffer for certificate file contents"));
3efed6
+
3efed6
+  while (total_read_size < file_size)
3efed6
+    {
3efed6
+      read_size =
3efed6
+	grub_file_read (f, &buf[total_read_size],
3efed6
+			file_size - total_read_size);
3efed6
+      if (read_size < 0)
3efed6
+	{
3efed6
+	  err = grub_error (GRUB_ERR_READ_ERROR,
3efed6
+			    N_("Error reading certificate file"));
3efed6
+	  goto cleanup_buf;
3efed6
+	}
3efed6
+      total_read_size += read_size;
3efed6
+    }
3efed6
+
3efed6
+  err = certificate_import (buf, total_read_size, certificate);
3efed6
+  if (err != GRUB_ERR_NONE)
3efed6
+    goto cleanup_buf;
3efed6
+
3efed6
+  return GRUB_ERR_NONE;
3efed6
+
3efed6
+cleanup_buf:
3efed6
+  grub_free (buf);
3efed6
+  return err;
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+extract_appended_signature (grub_uint8_t * buf, grub_size_t bufsize,
3efed6
+			    struct grub_appended_signature *sig)
3efed6
+{
3efed6
+  grub_err_t err;
3efed6
+  grub_size_t pkcs7_size;
3efed6
+  grub_size_t remaining_len;
3efed6
+  grub_uint8_t *appsigdata = buf + bufsize - grub_strlen (magic);
3efed6
+
3efed6
+  if (bufsize < grub_strlen (magic))
3efed6
+    return grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+		       N_("File too short for signature magic"));
3efed6
+
3efed6
+  if (grub_memcmp (appsigdata, (grub_uint8_t *) magic, grub_strlen (magic)))
3efed6
+    return grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+		       N_("Missing or invalid signature magic"));
3efed6
+
3efed6
+  remaining_len = bufsize - grub_strlen (magic);
3efed6
+
3efed6
+  if (remaining_len < sizeof (struct module_signature))
3efed6
+    return grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+		       N_("File too short for signature metadata"));
3efed6
+
3efed6
+  appsigdata -= sizeof (struct module_signature);
3efed6
+
3efed6
+  /* extract the metadata */
3efed6
+  grub_memcpy (&(sig->sig_metadata), appsigdata,
3efed6
+	       sizeof (struct module_signature));
3efed6
+
3efed6
+  remaining_len -= sizeof (struct module_signature);
3efed6
+
3efed6
+  if (sig->sig_metadata.id_type != 2)
3efed6
+    return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("Wrong signature type"));
3efed6
+
3efed6
+#ifdef GRUB_TARGET_WORDS_BIGENDIAN
3efed6
+  pkcs7_size = sig->sig_metadata.sig_len;
3efed6
+#else
3efed6
+  pkcs7_size = __builtin_bswap32 (sig->sig_metadata.sig_len);
3efed6
+#endif
3efed6
+
3efed6
+  if (pkcs7_size > remaining_len)
3efed6
+    return grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+		       N_("File too short for PKCS#7 message"));
3efed6
+
3efed6
+  grub_dprintf ("appendedsig", "sig len %" PRIuGRUB_SIZE "\n", pkcs7_size);
3efed6
+
3efed6
+  sig->signature_len =
3efed6
+    grub_strlen (magic) + sizeof (struct module_signature) + pkcs7_size;
3efed6
+
3efed6
+  /* rewind pointer and parse pkcs7 data */
3efed6
+  appsigdata -= pkcs7_size;
3efed6
+
3efed6
+  err = parse_pkcs7_signedData (appsigdata, pkcs7_size, &sig->pkcs7);
3efed6
+  if (err != GRUB_ERR_NONE)
3efed6
+    return err;
3efed6
+
3efed6
+  return GRUB_ERR_NONE;
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+grub_verify_appended_signature (grub_uint8_t * buf, grub_size_t bufsize)
3efed6
+{
3efed6
+  grub_err_t err = GRUB_ERR_NONE;
3efed6
+  grub_size_t datasize;
3efed6
+  void *context;
3efed6
+  unsigned char *hash;
3efed6
+  gcry_mpi_t hashmpi;
3efed6
+  gcry_err_code_t rc;
3efed6
+  struct x509_certificate *pk;
3efed6
+  struct grub_appended_signature sig;
3efed6
+
3efed6
+  if (!grub_trusted_key)
3efed6
+    return grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+		       N_("No trusted keys to verify against"));
3efed6
+
3efed6
+  err = extract_appended_signature (buf, bufsize, &sig);
3efed6
+  if (err != GRUB_ERR_NONE)
3efed6
+    return err;
3efed6
+
3efed6
+  datasize = bufsize - sig.signature_len;
3efed6
+
3efed6
+  context = grub_zalloc (sig.pkcs7.hash->contextsize);
3efed6
+  if (!context)
3efed6
+    return grub_errno;
3efed6
+
3efed6
+  sig.pkcs7.hash->init (context);
3efed6
+  sig.pkcs7.hash->write (context, buf, datasize);
3efed6
+  sig.pkcs7.hash->final (context);
3efed6
+  hash = sig.pkcs7.hash->read (context);
3efed6
+  grub_dprintf ("appendedsig",
3efed6
+		"data size %" PRIxGRUB_SIZE ", hash %02x%02x%02x%02x...\n",
3efed6
+		datasize, hash[0], hash[1], hash[2], hash[3]);
3efed6
+
3efed6
+  err = GRUB_ERR_BAD_SIGNATURE;
3efed6
+  for (pk = grub_trusted_key; pk; pk = pk->next)
3efed6
+    {
3efed6
+      rc = grub_crypto_rsa_pad (&hashmpi, hash, sig.pkcs7.hash, pk->mpis[0]);
3efed6
+      if (rc)
3efed6
+	{
3efed6
+	  err = grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+			    N_("Error padding hash for RSA verification: %d"),
3efed6
+			    rc);
3efed6
+	  goto cleanup;
3efed6
+	}
3efed6
+
3efed6
+      rc = _gcry_pubkey_spec_rsa.verify (0, hashmpi, &sig.pkcs7.sig_mpi,
3efed6
+					 pk->mpis, NULL, NULL);
3efed6
+      gcry_mpi_release (hashmpi);
3efed6
+
3efed6
+      if (rc == 0)
3efed6
+	{
3efed6
+	  grub_dprintf ("appendedsig", "verify with key '%s' succeeded\n",
3efed6
+			pk->subject);
3efed6
+	  err = GRUB_ERR_NONE;
3efed6
+	  break;
3efed6
+	}
3efed6
+
3efed6
+      grub_dprintf ("appendedsig", "verify with key '%s' failed with %d\n",
3efed6
+		    pk->subject, rc);
3efed6
+    }
3efed6
+
3efed6
+  /* If we didn't verify, provide a neat message */
3efed6
+  if (err != GRUB_ERR_NONE)
3efed6
+      err = grub_error (GRUB_ERR_BAD_SIGNATURE,
3efed6
+			N_("Failed to verify signature against a trusted key"));
3efed6
+
3efed6
+cleanup:
3efed6
+  grub_free (context);
3efed6
+  pkcs7_signedData_release (&sig.pkcs7);
3efed6
+
3efed6
+  return err;
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+grub_cmd_verify_signature (grub_command_t cmd __attribute__((unused)),
3efed6
+			   int argc, char **args)
3efed6
+{
3efed6
+  grub_file_t f;
3efed6
+  grub_err_t err = GRUB_ERR_NONE;
3efed6
+  grub_uint8_t *data;
3efed6
+  grub_ssize_t read_size;
3efed6
+  grub_off_t file_size, total_read_size = 0;
3efed6
+
3efed6
+  if (argc < 1)
3efed6
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
3efed6
+
3efed6
+  grub_dprintf ("appendedsig", "verifying %s\n", args[0]);
3efed6
+
3efed6
+  f = grub_file_open (args[0], GRUB_FILE_TYPE_VERIFY_SIGNATURE);
3efed6
+  if (!f)
3efed6
+    {
3efed6
+      err = grub_errno;
3efed6
+      goto cleanup;
3efed6
+    }
3efed6
+
3efed6
+  file_size = grub_file_size (f);
3efed6
+  if (file_size == GRUB_FILE_SIZE_UNKNOWN)
3efed6
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
3efed6
+		       N_("Cannot verify the signature of a file of unknown size"));
3efed6
+
3efed6
+  data = grub_malloc (file_size);
3efed6
+  if (!data)
3efed6
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
3efed6
+		       N_("Could not allocate data buffer size "
3efed6
+		       PRIuGRUB_UINT64_T " for verification"), file_size);
3efed6
+
3efed6
+  while (total_read_size < file_size)
3efed6
+    {
3efed6
+      read_size =
3efed6
+	grub_file_read (f, &data[total_read_size],
3efed6
+			file_size - total_read_size);
3efed6
+      if (read_size < 0)
3efed6
+	{
3efed6
+	  err = grub_error (GRUB_ERR_READ_ERROR,
3efed6
+			    N_("Error reading file to verify"));
3efed6
+	  goto cleanup_data;
3efed6
+	}
3efed6
+      total_read_size += read_size;
3efed6
+    }
3efed6
+
3efed6
+  err = grub_verify_appended_signature (data, file_size);
3efed6
+
3efed6
+cleanup_data:
3efed6
+  grub_free (data);
3efed6
+cleanup:
3efed6
+  if (f)
3efed6
+    grub_file_close (f);
3efed6
+  return err;
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+grub_cmd_distrust (grub_command_t cmd __attribute__((unused)),
3efed6
+		   int argc, char **args)
3efed6
+{
3efed6
+  unsigned long cert_num, i;
3efed6
+  struct x509_certificate *cert, *prev;
3efed6
+
3efed6
+  if (argc != 1)
3efed6
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("One argument expected"));
3efed6
+
3efed6
+  grub_errno = GRUB_ERR_NONE;
3efed6
+  cert_num = grub_strtoul (args[0], NULL, 10);
3efed6
+  if (grub_errno != GRUB_ERR_NONE)
3efed6
+    return grub_errno;
3efed6
+
3efed6
+  if (cert_num < 1)
3efed6
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
3efed6
+		       N_("Certificate number too small - numbers start at 1"));
3efed6
+
3efed6
+  if (cert_num == 1)
3efed6
+    {
3efed6
+      cert = grub_trusted_key;
3efed6
+      grub_trusted_key = cert->next;
3efed6
+
3efed6
+      certificate_release (cert);
3efed6
+      grub_free (cert);
3efed6
+      return GRUB_ERR_NONE;
3efed6
+    }
3efed6
+  i = 2;
3efed6
+  prev = grub_trusted_key;
3efed6
+  cert = grub_trusted_key->next;
3efed6
+  while (cert)
3efed6
+    {
3efed6
+      if (i == cert_num)
3efed6
+	{
3efed6
+	  prev->next = cert->next;
3efed6
+	  certificate_release (cert);
3efed6
+	  grub_free (cert);
3efed6
+	  return GRUB_ERR_NONE;
3efed6
+	}
3efed6
+      i++;
3efed6
+      prev = cert;
3efed6
+      cert = cert->next;
3efed6
+    }
3efed6
+
3efed6
+  return grub_error (GRUB_ERR_BAD_ARGUMENT,
3efed6
+		     N_("No certificate number %d found - only %d certificates in the store"),
3efed6
+		     cert_num, i - 1);
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+grub_cmd_trust (grub_command_t cmd __attribute__((unused)),
3efed6
+		int argc, char **args)
3efed6
+{
3efed6
+  grub_file_t certf;
3efed6
+  struct x509_certificate *cert = NULL;
3efed6
+  grub_err_t err;
3efed6
+
3efed6
+  if (argc != 1)
3efed6
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
3efed6
+
3efed6
+  certf = grub_file_open (args[0],
3efed6
+			  GRUB_FILE_TYPE_CERTIFICATE_TRUST
3efed6
+			  | GRUB_FILE_TYPE_NO_DECOMPRESS);
3efed6
+  if (!certf)
3efed6
+    return grub_errno;
3efed6
+
3efed6
+
3efed6
+  cert = grub_zalloc (sizeof (struct x509_certificate));
3efed6
+  if (!cert)
3efed6
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
3efed6
+		       N_("Could not allocate memory for certificate"));
3efed6
+
3efed6
+  err = read_cert_from_file (certf, cert);
3efed6
+  grub_file_close (certf);
3efed6
+  if (err != GRUB_ERR_NONE)
3efed6
+    {
3efed6
+      grub_free (cert);
3efed6
+      return err;
3efed6
+    }
3efed6
+  grub_dprintf ("appendedsig", "Loaded certificate with CN: %s\n",
3efed6
+		cert->subject);
3efed6
+
3efed6
+  cert->next = grub_trusted_key;
3efed6
+  grub_trusted_key = cert;
3efed6
+
3efed6
+  return GRUB_ERR_NONE;
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+grub_cmd_list (grub_command_t cmd __attribute__((unused)),
3efed6
+	       int argc __attribute__((unused)),
3efed6
+	       char **args __attribute__((unused)))
3efed6
+{
3efed6
+  struct x509_certificate *cert;
3efed6
+  int cert_num = 1;
3efed6
+  grub_size_t i;
3efed6
+
3efed6
+  for (cert = grub_trusted_key; cert; cert = cert->next)
3efed6
+    {
3efed6
+      grub_printf (N_("Certificate %d:\n"), cert_num);
3efed6
+
3efed6
+      grub_printf (N_("\tSerial: "));
3efed6
+      for (i = 0; i < cert->serial_len - 1; i++)
3efed6
+	{
3efed6
+	  grub_printf ("%02x:", cert->serial[i]);
3efed6
+	}
3efed6
+      grub_printf ("%02x\n", cert->serial[cert->serial_len - 1]);
3efed6
+
3efed6
+      grub_printf ("\tCN: %s\n\n", cert->subject);
3efed6
+      cert_num++;
3efed6
+
3efed6
+    }
3efed6
+
3efed6
+  return GRUB_ERR_NONE;
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+appendedsig_init (grub_file_t io, enum grub_file_type type,
3efed6
+		  void **context __attribute__((unused)),
3efed6
+		  enum grub_verify_flags *flags)
3efed6
+{
3efed6
+  const char *dangerous_mod;
3efed6
+
3efed6
+  if (!check_sigs)
3efed6
+    {
3efed6
+      *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION;
3efed6
+      return GRUB_ERR_NONE;
3efed6
+    }
3efed6
+
3efed6
+  switch (type & GRUB_FILE_TYPE_MASK)
3efed6
+    {
3efed6
+    case GRUB_FILE_TYPE_GRUB_MODULE:
3efed6
+      if (grub_is_dangerous_module (io))
3efed6
+	return grub_error (GRUB_ERR_ACCESS_DENIED,
3efed6
+			   N_("module cannot be loaded in appended signature mode: %s"),
3efed6
+			   io->name);
3efed6
+
3efed6
+      *flags = GRUB_VERIFY_FLAGS_SINGLE_CHUNK;
3efed6
+      return GRUB_ERR_NONE;
3efed6
+
3efed6
+    case GRUB_FILE_TYPE_ACPI_TABLE:
3efed6
+    case GRUB_FILE_TYPE_DEVICE_TREE_IMAGE:
3efed6
+      *flags = GRUB_VERIFY_FLAGS_DEFER_AUTH;
3efed6
+      return GRUB_ERR_NONE;
3efed6
+
3efed6
+    case GRUB_FILE_TYPE_CERTIFICATE_TRUST:
3efed6
+      /*
3efed6
+       * This is a certificate to add to trusted keychain.
3efed6
+       *
3efed6
+       * This needs to be verified or blocked. Ideally we'd write an x509
3efed6
+       * verifier, but we lack the hubris required to take this on. Instead,
3efed6
+       * require that it have an appended signature.
3efed6
+       */
3efed6
+
3efed6
+      /* Fall through */
3efed6
+
3efed6
+    case GRUB_FILE_TYPE_LINUX_KERNEL:
3efed6
+    case GRUB_FILE_TYPE_MULTIBOOT_KERNEL:
3efed6
+    case GRUB_FILE_TYPE_BSD_KERNEL:
3efed6
+    case GRUB_FILE_TYPE_XNU_KERNEL:
3efed6
+    case GRUB_FILE_TYPE_PLAN9_KERNEL:
3efed6
+
3efed6
+      dangerous_mod = grub_dangerous_module_loaded ();
3efed6
+      if (dangerous_mod)
3efed6
+	return grub_error (GRUB_ERR_ACCESS_DENIED,
3efed6
+			   N_("cannot proceed due to dangerous module in memory: %s"),
3efed6
+			   dangerous_mod);
3efed6
+
3efed6
+      *flags = GRUB_VERIFY_FLAGS_SINGLE_CHUNK;
3efed6
+      return GRUB_ERR_NONE;
3efed6
+
3efed6
+    default:
3efed6
+      /*
3efed6
+       * powerpc only supports the linux loader. If you support more,
3efed6
+       * (especially chain loaded binaries) make sure they're checked!
3efed6
+       */
3efed6
+      *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION;
3efed6
+      return GRUB_ERR_NONE;
3efed6
+    }
3efed6
+}
3efed6
+
3efed6
+static grub_err_t
3efed6
+appendedsig_write (void *ctxt __attribute__((unused)),
3efed6
+		   void *buf, grub_size_t size)
3efed6
+{
3efed6
+  return grub_verify_appended_signature (buf, size);
3efed6
+}
3efed6
+
3efed6
+struct grub_file_verifier grub_appendedsig_verifier = {
3efed6
+  .name = "appendedsig",
3efed6
+  .init = appendedsig_init,
3efed6
+  .write = appendedsig_write,
3efed6
+};
3efed6
+
3efed6
+static grub_ssize_t
3efed6
+pseudo_read (struct grub_file *file, char *buf, grub_size_t len)
3efed6
+{
3efed6
+  grub_memcpy (buf, (grub_uint8_t *) file->data + file->offset, len);
3efed6
+  return len;
3efed6
+}
3efed6
+
3efed6
+/* Filesystem descriptor.  */
3efed6
+static struct grub_fs pseudo_fs = {
3efed6
+  .name = "pseudo",
3efed6
+  .read = pseudo_read
3efed6
+};
3efed6
+
3efed6
+static grub_command_t cmd_verify, cmd_list, cmd_distrust, cmd_trust;
3efed6
+
3efed6
+GRUB_MOD_INIT (appendedsig)
3efed6
+{
3efed6
+  int rc;
3efed6
+  struct grub_module_header *header;
3efed6
+  const char *val;
3efed6
+
3efed6
+  val = grub_env_get ("check_appended_signatures");
3efed6
+  grub_dprintf ("appendedsig", "check_appended_signatures='%s'\n", val);
3efed6
+
3efed6
+  if (val && (val[0] == '1' || val[0] == 'e'))
3efed6
+    check_sigs = 1;
3efed6
+  else
3efed6
+    check_sigs = 0;
3efed6
+
3efed6
+  grub_trusted_key = NULL;
3efed6
+
3efed6
+  grub_register_variable_hook ("check_appended_signatures", 0,
3efed6
+			       grub_env_write_sec);
3efed6
+  grub_env_export ("check_appended_signatures");
3efed6
+
3efed6
+  rc = asn1_init ();
3efed6
+  if (rc)
3efed6
+    grub_fatal ("Error initing ASN.1 data structures: %d: %s\n", rc,
3efed6
+		asn1_strerror (rc));
3efed6
+
3efed6
+  FOR_MODULES (header)
3efed6
+  {
3efed6
+    struct grub_file pseudo_file;
3efed6
+    struct x509_certificate *pk = NULL;
3efed6
+    grub_err_t err;
3efed6
+
3efed6
+    /* Not an ELF module, skip.  */
3efed6
+    if (header->type != OBJ_TYPE_X509_PUBKEY)
3efed6
+      continue;
3efed6
+
3efed6
+    grub_memset (&pseudo_file, 0, sizeof (pseudo_file));
3efed6
+    pseudo_file.fs = &pseudo_fs;
3efed6
+    pseudo_file.size = header->size - sizeof (struct grub_module_header);
3efed6
+    pseudo_file.data = (char *) header + sizeof (struct grub_module_header);
3efed6
+
3efed6
+    grub_dprintf ("appendedsig",
3efed6
+		  "Found an x509 key, size=%" PRIuGRUB_UINT64_T "\n",
3efed6
+		  pseudo_file.size);
3efed6
+
3efed6
+    pk = grub_zalloc (sizeof (struct x509_certificate));
3efed6
+    if (!pk)
3efed6
+      {
3efed6
+	grub_fatal ("Out of memory loading initial certificates");
3efed6
+      }
3efed6
+
3efed6
+    err = read_cert_from_file (&pseudo_file, pk);
3efed6
+    if (err != GRUB_ERR_NONE)
3efed6
+      grub_fatal ("Error loading initial key: %s", grub_errmsg);
3efed6
+
3efed6
+    grub_dprintf ("appendedsig", "loaded certificate CN='%s'\n", pk->subject);
3efed6
+
3efed6
+    pk->next = grub_trusted_key;
3efed6
+    grub_trusted_key = pk;
3efed6
+  }
3efed6
+
3efed6
+  if (!val || val[0] == '\0')
3efed6
+    {
3efed6
+      grub_env_set ("check_appended_signatures",
3efed6
+		    grub_trusted_key ? "enforce" : "no");
3efed6
+    }
3efed6
+
3efed6
+  cmd_trust =
3efed6
+    grub_register_command ("trust_certificate", grub_cmd_trust,
3efed6
+			   N_("X509_CERTIFICATE"),
3efed6
+			   N_("Add X509_CERTIFICATE to trusted certificates."));
3efed6
+  cmd_list =
3efed6
+    grub_register_command ("list_certificates", grub_cmd_list, 0,
3efed6
+			   N_("Show the list of trusted x509 certificates."));
3efed6
+  cmd_verify =
3efed6
+    grub_register_command ("verify_appended", grub_cmd_verify_signature,
3efed6
+			   N_("FILE"),
3efed6
+			   N_("Verify FILE against the trusted x509 certificates."));
3efed6
+  cmd_distrust =
3efed6
+    grub_register_command ("distrust_certificate", grub_cmd_distrust,
3efed6
+			   N_("CERT_NUMBER"),
3efed6
+			   N_("Remove CERT_NUMBER (as listed by list_certificates) from trusted certificates."));
3efed6
+
3efed6
+  grub_verifier_register (&grub_appendedsig_verifier);
3efed6
+  grub_dl_set_persistent (mod);
3efed6
+}
3efed6
+
3efed6
+GRUB_MOD_FINI (appendedsig)
3efed6
+{
3efed6
+  /*
3efed6
+   * grub_dl_set_persistent should prevent this from actually running, but
3efed6
+   * it does still run under emu.
3efed6
+   */
3efed6
+
3efed6
+  grub_verifier_unregister (&grub_appendedsig_verifier);
3efed6
+  grub_unregister_command (cmd_verify);
3efed6
+  grub_unregister_command (cmd_list);
3efed6
+  grub_unregister_command (cmd_trust);
3efed6
+  grub_unregister_command (cmd_distrust);
3efed6
+}
3efed6
diff --git a/include/grub/file.h b/include/grub/file.h
b71686
index cbbd29465..2e337dbd6 100644
3efed6
--- a/include/grub/file.h
3efed6
+++ b/include/grub/file.h
3efed6
@@ -82,6 +82,8 @@ enum grub_file_type
3efed6
     GRUB_FILE_TYPE_PUBLIC_KEY,
3efed6
     /* File holding public key to add to trused keys.  */
3efed6
     GRUB_FILE_TYPE_PUBLIC_KEY_TRUST,
3efed6
+    /* File holding x509 certificiate to add to trusted keys.  */
3efed6
+    GRUB_FILE_TYPE_CERTIFICATE_TRUST,
3efed6
     /* File of which we intend to print a blocklist to the user.  */
3efed6
     GRUB_FILE_TYPE_PRINT_BLOCKLIST,
3efed6
     /* File we intend to use for test loading or testing speed.  */