You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
716 lines
21 KiB
716 lines
21 KiB
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
|
From: Daniel Axtens <dja@axtens.net> |
|
Date: Thu, 30 Jul 2020 01:35:43 +1000 |
|
Subject: [PATCH] appended signatures: support verifying appended signatures |
|
|
|
Building on the parsers and the ability to embed x509 certificates, as |
|
well as the existing gcrypt functionality, add a module for verifying |
|
appended signatures. |
|
|
|
This includes a verifier that requires that Linux kernels and grub modules |
|
have appended signatures, and commands to manage the list of trusted |
|
certificates for verification. |
|
|
|
Verification must be enabled by setting check_appended_signatures. If |
|
GRUB is locked down when the module is loaded, verification will be |
|
enabled and locked automatically. |
|
|
|
As with the PGP verifier, it is not a complete secure-boot solution: |
|
other mechanisms, such as a password or lockdown, must be used to ensure |
|
that a user cannot drop to the grub shell and disable verification. |
|
|
|
Signed-off-by: Daniel Axtens <dja@axtens.net> |
|
--- |
|
grub-core/Makefile.core.def | 12 + |
|
grub-core/commands/appendedsig/appendedsig.c | 645 +++++++++++++++++++++++++++ |
|
include/grub/file.h | 2 + |
|
3 files changed, 659 insertions(+) |
|
create mode 100644 grub-core/commands/appendedsig/appendedsig.c |
|
|
|
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def |
|
index b4aaccf7b57..77321d218c8 100644 |
|
--- a/grub-core/Makefile.core.def |
|
+++ b/grub-core/Makefile.core.def |
|
@@ -980,6 +980,18 @@ module = { |
|
cppflags = '-I$(srcdir)/lib/posix_wrap'; |
|
}; |
|
|
|
+module = { |
|
+ name = appendedsig; |
|
+ common = commands/appendedsig/appendedsig.c; |
|
+ common = commands/appendedsig/x509.c; |
|
+ common = commands/appendedsig/pkcs7.c; |
|
+ common = commands/appendedsig/asn1util.c; |
|
+ common = commands/appendedsig/gnutls_asn1_tab.c; |
|
+ common = commands/appendedsig/pkix_asn1_tab.c; |
|
+ cflags = '$(CFLAGS_POSIX)'; |
|
+ cppflags = '-I$(srcdir)/lib/posix_wrap'; |
|
+}; |
|
+ |
|
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 00000000000..dc294cd339e |
|
--- /dev/null |
|
+++ b/grub-core/commands/appendedsig/appendedsig.c |
|
@@ -0,0 +1,645 @@ |
|
+/* |
|
+ * GRUB -- GRand Unified Bootloader |
|
+ * Copyright (C) 2020-2021 IBM Corporation. |
|
+ * |
|
+ * GRUB is free software: you can redistribute it and/or modify |
|
+ * it under the terms of the GNU General Public License as published by |
|
+ * the Free Software Foundation, either version 3 of the License, or |
|
+ * (at your option) any later version. |
|
+ * |
|
+ * GRUB is distributed in the hope that it will be useful, |
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ * GNU General Public License for more details. |
|
+ * |
|
+ * You should have received a copy of the GNU General Public License |
|
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
|
+ */ |
|
+ |
|
+#include <grub/types.h> |
|
+#include <grub/misc.h> |
|
+#include <grub/mm.h> |
|
+#include <grub/err.h> |
|
+#include <grub/dl.h> |
|
+#include <grub/file.h> |
|
+#include <grub/command.h> |
|
+#include <grub/crypto.h> |
|
+#include <grub/pkcs1_v15.h> |
|
+#include <grub/i18n.h> |
|
+#include <grub/gcrypt/gcrypt.h> |
|
+#include <grub/kernel.h> |
|
+#include <grub/extcmd.h> |
|
+#include <grub/verify.h> |
|
+#include <grub/libtasn1.h> |
|
+#include <grub/env.h> |
|
+#include <grub/lockdown.h> |
|
+ |
|
+#include "appendedsig.h" |
|
+ |
|
+GRUB_MOD_LICENSE ("GPLv3+"); |
|
+ |
|
+const char magic[] = "~Module signature appended~\n"; |
|
+ |
|
+/* |
|
+ * This structure is extracted from scripts/sign-file.c in the linux kernel |
|
+ * source. It was licensed as LGPLv2.1+, which is GPLv3+ compatible. |
|
+ */ |
|
+struct module_signature |
|
+{ |
|
+ grub_uint8_t algo; /* Public-key crypto algorithm [0] */ |
|
+ grub_uint8_t hash; /* Digest algorithm [0] */ |
|
+ grub_uint8_t id_type; /* Key identifier type [PKEY_ID_PKCS7] */ |
|
+ grub_uint8_t signer_len; /* Length of signer's name [0] */ |
|
+ grub_uint8_t key_id_len; /* Length of key identifier [0] */ |
|
+ grub_uint8_t __pad[3]; |
|
+ grub_uint32_t sig_len; /* Length of signature data */ |
|
+} GRUB_PACKED; |
|
+ |
|
+ |
|
+/* This represents an entire, parsed, appended signature */ |
|
+struct grub_appended_signature |
|
+{ |
|
+ grub_size_t signature_len; /* Length of PKCS#7 data + |
|
+ * metadata + magic */ |
|
+ |
|
+ struct module_signature sig_metadata; /* Module signature metadata */ |
|
+ struct pkcs7_signedData pkcs7; /* Parsed PKCS#7 data */ |
|
+}; |
|
+ |
|
+/* Trusted certificates for verifying appended signatures */ |
|
+struct x509_certificate *grub_trusted_key; |
|
+ |
|
+/* |
|
+ * Force gcry_rsa to be a module dependency. |
|
+ * |
|
+ * If we use grub_crypto_pk_rsa, then then the gcry_rsa module won't be built |
|
+ * in if you add 'appendedsig' to grub-install --modules. You would need to |
|
+ * add 'gcry_rsa' too. That's confusing and seems suboptimal, especially when |
|
+ * we only support RSA. |
|
+ * |
|
+ * Dynamic loading also causes some concerns. We can't load gcry_rsa from the |
|
+ * the filesystem after we install the verifier - we won't be able to verify |
|
+ * it without having it already present. We also shouldn't load it before we |
|
+ * install the verifier, because that would mean it wouldn't be verified - an |
|
+ * attacker could insert any code they wanted into the module. |
|
+ * |
|
+ * So instead, reference the internal symbol from gcry_rsa. That creates a |
|
+ * direct dependency on gcry_rsa, so it will be built in when this module |
|
+ * is built in. Being built in (assuming the core image is itself signed!) |
|
+ * also resolves our concerns about loading from the filesystem. |
|
+ */ |
|
+extern gcry_pk_spec_t _gcry_pubkey_spec_rsa; |
|
+ |
|
+static int check_sigs = 0; |
|
+ |
|
+static const char * |
|
+grub_env_read_sec (struct grub_env_var *var __attribute__ ((unused)), |
|
+ const char *val __attribute__ ((unused))) |
|
+{ |
|
+ if (check_sigs == 2) |
|
+ return "forced"; |
|
+ else if (check_sigs == 1) |
|
+ return "enforce"; |
|
+ else |
|
+ return "no"; |
|
+} |
|
+ |
|
+static char * |
|
+grub_env_write_sec (struct grub_env_var *var __attribute__((unused)), |
|
+ const char *val) |
|
+{ |
|
+ /* Do not allow the value to be changed if set to forced */ |
|
+ if (check_sigs == 2) |
|
+ return grub_strdup ("forced"); |
|
+ |
|
+ if ((*val == '2') || (*val == 'f')) |
|
+ check_sigs = 2; |
|
+ else if ((*val == '1') || (*val == 'e')) |
|
+ check_sigs = 1; |
|
+ else if ((*val == '0') || (*val == 'n')) |
|
+ check_sigs = 0; |
|
+ |
|
+ return grub_strdup (grub_env_read_sec (NULL, NULL)); |
|
+} |
|
+ |
|
+static grub_err_t |
|
+read_cert_from_file (grub_file_t f, struct x509_certificate *certificate) |
|
+{ |
|
+ grub_err_t err; |
|
+ grub_uint8_t *buf = NULL; |
|
+ grub_ssize_t read_size; |
|
+ grub_off_t total_read_size = 0; |
|
+ grub_off_t file_size = grub_file_size (f); |
|
+ |
|
+ |
|
+ if (file_size == GRUB_FILE_SIZE_UNKNOWN) |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, |
|
+ N_("Cannot parse a certificate file of unknown size")); |
|
+ |
|
+ buf = grub_zalloc (file_size); |
|
+ if (!buf) |
|
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, |
|
+ N_("Could not allocate buffer for certificate file contents")); |
|
+ |
|
+ while (total_read_size < file_size) |
|
+ { |
|
+ read_size = |
|
+ grub_file_read (f, &buf[total_read_size], |
|
+ file_size - total_read_size); |
|
+ if (read_size < 0) |
|
+ { |
|
+ err = grub_error (GRUB_ERR_READ_ERROR, |
|
+ N_("Error reading certificate file")); |
|
+ goto cleanup_buf; |
|
+ } |
|
+ total_read_size += read_size; |
|
+ } |
|
+ |
|
+ err = certificate_import (buf, total_read_size, certificate); |
|
+ if (err != GRUB_ERR_NONE) |
|
+ goto cleanup_buf; |
|
+ |
|
+ return GRUB_ERR_NONE; |
|
+ |
|
+cleanup_buf: |
|
+ grub_free (buf); |
|
+ return err; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+extract_appended_signature (grub_uint8_t * buf, grub_size_t bufsize, |
|
+ struct grub_appended_signature *sig) |
|
+{ |
|
+ grub_err_t err; |
|
+ grub_size_t pkcs7_size; |
|
+ grub_size_t remaining_len; |
|
+ grub_uint8_t *appsigdata = buf + bufsize - grub_strlen (magic); |
|
+ |
|
+ if (bufsize < grub_strlen (magic)) |
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("File too short for signature magic")); |
|
+ |
|
+ if (grub_memcmp (appsigdata, (grub_uint8_t *) magic, grub_strlen (magic))) |
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("Missing or invalid signature magic")); |
|
+ |
|
+ remaining_len = bufsize - grub_strlen (magic); |
|
+ |
|
+ if (remaining_len < sizeof (struct module_signature)) |
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("File too short for signature metadata")); |
|
+ |
|
+ appsigdata -= sizeof (struct module_signature); |
|
+ |
|
+ /* extract the metadata */ |
|
+ grub_memcpy (&(sig->sig_metadata), appsigdata, |
|
+ sizeof (struct module_signature)); |
|
+ |
|
+ remaining_len -= sizeof (struct module_signature); |
|
+ |
|
+ if (sig->sig_metadata.id_type != 2) |
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("Wrong signature type")); |
|
+ |
|
+#ifdef GRUB_TARGET_WORDS_BIGENDIAN |
|
+ pkcs7_size = sig->sig_metadata.sig_len; |
|
+#else |
|
+ pkcs7_size = __builtin_bswap32 (sig->sig_metadata.sig_len); |
|
+#endif |
|
+ |
|
+ if (pkcs7_size > remaining_len) |
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("File too short for PKCS#7 message")); |
|
+ |
|
+ grub_dprintf ("appendedsig", "sig len %" PRIuGRUB_SIZE "\n", pkcs7_size); |
|
+ |
|
+ sig->signature_len = |
|
+ grub_strlen (magic) + sizeof (struct module_signature) + pkcs7_size; |
|
+ |
|
+ /* rewind pointer and parse pkcs7 data */ |
|
+ appsigdata -= pkcs7_size; |
|
+ |
|
+ err = parse_pkcs7_signedData (appsigdata, pkcs7_size, &sig->pkcs7); |
|
+ if (err != GRUB_ERR_NONE) |
|
+ return err; |
|
+ |
|
+ return GRUB_ERR_NONE; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_verify_appended_signature (grub_uint8_t * buf, grub_size_t bufsize) |
|
+{ |
|
+ grub_err_t err = GRUB_ERR_NONE; |
|
+ grub_size_t datasize; |
|
+ void *context; |
|
+ unsigned char *hash; |
|
+ gcry_mpi_t hashmpi; |
|
+ gcry_err_code_t rc; |
|
+ struct x509_certificate *pk; |
|
+ struct grub_appended_signature sig; |
|
+ |
|
+ if (!grub_trusted_key) |
|
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("No trusted keys to verify against")); |
|
+ |
|
+ err = extract_appended_signature (buf, bufsize, &sig); |
|
+ if (err != GRUB_ERR_NONE) |
|
+ return err; |
|
+ |
|
+ datasize = bufsize - sig.signature_len; |
|
+ |
|
+ context = grub_zalloc (sig.pkcs7.hash->contextsize); |
|
+ if (!context) |
|
+ return grub_errno; |
|
+ |
|
+ sig.pkcs7.hash->init (context); |
|
+ sig.pkcs7.hash->write (context, buf, datasize); |
|
+ sig.pkcs7.hash->final (context); |
|
+ hash = sig.pkcs7.hash->read (context); |
|
+ grub_dprintf ("appendedsig", |
|
+ "data size %" PRIxGRUB_SIZE ", hash %02x%02x%02x%02x...\n", |
|
+ datasize, hash[0], hash[1], hash[2], hash[3]); |
|
+ |
|
+ err = GRUB_ERR_BAD_SIGNATURE; |
|
+ for (pk = grub_trusted_key; pk; pk = pk->next) |
|
+ { |
|
+ rc = grub_crypto_rsa_pad (&hashmpi, hash, sig.pkcs7.hash, pk->mpis[0]); |
|
+ if (rc) |
|
+ { |
|
+ err = grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("Error padding hash for RSA verification: %d"), |
|
+ rc); |
|
+ goto cleanup; |
|
+ } |
|
+ |
|
+ rc = _gcry_pubkey_spec_rsa.verify (0, hashmpi, &sig.pkcs7.sig_mpi, |
|
+ pk->mpis, NULL, NULL); |
|
+ gcry_mpi_release (hashmpi); |
|
+ |
|
+ if (rc == 0) |
|
+ { |
|
+ grub_dprintf ("appendedsig", "verify with key '%s' succeeded\n", |
|
+ pk->subject); |
|
+ err = GRUB_ERR_NONE; |
|
+ break; |
|
+ } |
|
+ |
|
+ grub_dprintf ("appendedsig", "verify with key '%s' failed with %d\n", |
|
+ pk->subject, rc); |
|
+ } |
|
+ |
|
+ /* If we didn't verify, provide a neat message */ |
|
+ if (err != GRUB_ERR_NONE) |
|
+ err = grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ N_("Failed to verify signature against a trusted key")); |
|
+ |
|
+cleanup: |
|
+ grub_free (context); |
|
+ pkcs7_signedData_release (&sig.pkcs7); |
|
+ |
|
+ return err; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_cmd_verify_signature (grub_command_t cmd __attribute__((unused)), |
|
+ int argc, char **args) |
|
+{ |
|
+ grub_file_t f; |
|
+ grub_err_t err = GRUB_ERR_NONE; |
|
+ grub_uint8_t *data; |
|
+ grub_ssize_t read_size; |
|
+ grub_off_t file_size, total_read_size = 0; |
|
+ |
|
+ if (argc < 1) |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); |
|
+ |
|
+ grub_dprintf ("appendedsig", "verifying %s\n", args[0]); |
|
+ |
|
+ f = grub_file_open (args[0], GRUB_FILE_TYPE_VERIFY_SIGNATURE); |
|
+ if (!f) |
|
+ { |
|
+ err = grub_errno; |
|
+ goto cleanup; |
|
+ } |
|
+ |
|
+ file_size = grub_file_size (f); |
|
+ if (file_size == GRUB_FILE_SIZE_UNKNOWN) |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, |
|
+ N_("Cannot verify the signature of a file of unknown size")); |
|
+ |
|
+ data = grub_malloc (file_size); |
|
+ if (!data) |
|
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, |
|
+ N_("Could not allocate data buffer size " |
|
+ PRIuGRUB_UINT64_T " for verification"), file_size); |
|
+ |
|
+ while (total_read_size < file_size) |
|
+ { |
|
+ read_size = |
|
+ grub_file_read (f, &data[total_read_size], |
|
+ file_size - total_read_size); |
|
+ if (read_size < 0) |
|
+ { |
|
+ err = grub_error (GRUB_ERR_READ_ERROR, |
|
+ N_("Error reading file to verify")); |
|
+ goto cleanup_data; |
|
+ } |
|
+ total_read_size += read_size; |
|
+ } |
|
+ |
|
+ err = grub_verify_appended_signature (data, file_size); |
|
+ |
|
+cleanup_data: |
|
+ grub_free (data); |
|
+cleanup: |
|
+ if (f) |
|
+ grub_file_close (f); |
|
+ return err; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_cmd_distrust (grub_command_t cmd __attribute__((unused)), |
|
+ int argc, char **args) |
|
+{ |
|
+ unsigned long cert_num, i; |
|
+ struct x509_certificate *cert, *prev; |
|
+ |
|
+ if (argc != 1) |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("One argument expected")); |
|
+ |
|
+ grub_errno = GRUB_ERR_NONE; |
|
+ cert_num = grub_strtoul (args[0], NULL, 10); |
|
+ if (grub_errno != GRUB_ERR_NONE) |
|
+ return grub_errno; |
|
+ |
|
+ if (cert_num < 1) |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, |
|
+ N_("Certificate number too small - numbers start at 1")); |
|
+ |
|
+ if (cert_num == 1) |
|
+ { |
|
+ cert = grub_trusted_key; |
|
+ grub_trusted_key = cert->next; |
|
+ |
|
+ certificate_release (cert); |
|
+ grub_free (cert); |
|
+ return GRUB_ERR_NONE; |
|
+ } |
|
+ i = 2; |
|
+ prev = grub_trusted_key; |
|
+ cert = grub_trusted_key->next; |
|
+ while (cert) |
|
+ { |
|
+ if (i == cert_num) |
|
+ { |
|
+ prev->next = cert->next; |
|
+ certificate_release (cert); |
|
+ grub_free (cert); |
|
+ return GRUB_ERR_NONE; |
|
+ } |
|
+ i++; |
|
+ prev = cert; |
|
+ cert = cert->next; |
|
+ } |
|
+ |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, |
|
+ N_("No certificate number %d found - only %d certificates in the store"), |
|
+ cert_num, i - 1); |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_cmd_trust (grub_command_t cmd __attribute__((unused)), |
|
+ int argc, char **args) |
|
+{ |
|
+ grub_file_t certf; |
|
+ struct x509_certificate *cert = NULL; |
|
+ grub_err_t err; |
|
+ |
|
+ if (argc != 1) |
|
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); |
|
+ |
|
+ certf = grub_file_open (args[0], |
|
+ GRUB_FILE_TYPE_CERTIFICATE_TRUST |
|
+ | GRUB_FILE_TYPE_NO_DECOMPRESS); |
|
+ if (!certf) |
|
+ return grub_errno; |
|
+ |
|
+ |
|
+ cert = grub_zalloc (sizeof (struct x509_certificate)); |
|
+ if (!cert) |
|
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, |
|
+ N_("Could not allocate memory for certificate")); |
|
+ |
|
+ err = read_cert_from_file (certf, cert); |
|
+ grub_file_close (certf); |
|
+ if (err != GRUB_ERR_NONE) |
|
+ { |
|
+ grub_free (cert); |
|
+ return err; |
|
+ } |
|
+ grub_dprintf ("appendedsig", "Loaded certificate with CN: %s\n", |
|
+ cert->subject); |
|
+ |
|
+ cert->next = grub_trusted_key; |
|
+ grub_trusted_key = cert; |
|
+ |
|
+ return GRUB_ERR_NONE; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_cmd_list (grub_command_t cmd __attribute__((unused)), |
|
+ int argc __attribute__((unused)), |
|
+ char **args __attribute__((unused))) |
|
+{ |
|
+ struct x509_certificate *cert; |
|
+ int cert_num = 1; |
|
+ grub_size_t i; |
|
+ |
|
+ for (cert = grub_trusted_key; cert; cert = cert->next) |
|
+ { |
|
+ grub_printf (N_("Certificate %d:\n"), cert_num); |
|
+ |
|
+ grub_printf (N_("\tSerial: ")); |
|
+ 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 ("\tCN: %s\n\n", cert->subject); |
|
+ cert_num++; |
|
+ |
|
+ } |
|
+ |
|
+ return GRUB_ERR_NONE; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+appendedsig_init (grub_file_t io __attribute__((unused)), |
|
+ enum grub_file_type type, |
|
+ void **context __attribute__((unused)), |
|
+ enum grub_verify_flags *flags) |
|
+{ |
|
+ if (!check_sigs) |
|
+ { |
|
+ *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. |
|
+ */ |
|
+ |
|
+ /* Fall through */ |
|
+ |
|
+ 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_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 |
|
+}; |
|
+ |
|
+static grub_command_t cmd_verify, cmd_list, cmd_distrust, cmd_trust; |
|
+ |
|
+GRUB_MOD_INIT (appendedsig) |
|
+{ |
|
+ int rc; |
|
+ struct grub_module_header *header; |
|
+ |
|
+ /* If in lockdown, immediately enter forced mode */ |
|
+ if (grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED) |
|
+ check_sigs = 2; |
|
+ |
|
+ grub_trusted_key = NULL; |
|
+ |
|
+ grub_register_variable_hook ("check_appended_signatures", |
|
+ grub_env_read_sec, |
|
+ grub_env_write_sec); |
|
+ grub_env_export ("check_appended_signatures"); |
|
+ |
|
+ rc = asn1_init (); |
|
+ if (rc) |
|
+ grub_fatal ("Error initing ASN.1 data structures: %d: %s\n", rc, |
|
+ asn1_strerror (rc)); |
|
+ |
|
+ FOR_MODULES (header) |
|
+ { |
|
+ struct grub_file pseudo_file; |
|
+ struct x509_certificate *pk = NULL; |
|
+ grub_err_t err; |
|
+ |
|
+ /* Not an ELF module, 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 x509 key, size=%" PRIuGRUB_UINT64_T "\n", |
|
+ pseudo_file.size); |
|
+ |
|
+ pk = grub_zalloc (sizeof (struct x509_certificate)); |
|
+ if (!pk) |
|
+ { |
|
+ grub_fatal ("Out of memory loading initial certificates"); |
|
+ } |
|
+ |
|
+ err = read_cert_from_file (&pseudo_file, pk); |
|
+ if (err != GRUB_ERR_NONE) |
|
+ grub_fatal ("Error loading initial key: %s", grub_errmsg); |
|
+ |
|
+ grub_dprintf ("appendedsig", "loaded certificate CN='%s'\n", pk->subject); |
|
+ |
|
+ pk->next = grub_trusted_key; |
|
+ grub_trusted_key = pk; |
|
+ } |
|
+ |
|
+ cmd_trust = |
|
+ grub_register_command ("trust_certificate", grub_cmd_trust, |
|
+ N_("X509_CERTIFICATE"), |
|
+ N_("Add X509_CERTIFICATE to trusted certificates.")); |
|
+ cmd_list = |
|
+ grub_register_command ("list_certificates", grub_cmd_list, 0, |
|
+ N_("Show the list of trusted x509 certificates.")); |
|
+ cmd_verify = |
|
+ grub_register_command ("verify_appended", grub_cmd_verify_signature, |
|
+ N_("FILE"), |
|
+ N_("Verify FILE against the trusted x509 certificates.")); |
|
+ cmd_distrust = |
|
+ grub_register_command ("distrust_certificate", grub_cmd_distrust, |
|
+ N_("CERT_NUMBER"), |
|
+ N_("Remove CERT_NUMBER (as listed by list_certificates) from trusted certificates.")); |
|
+ |
|
+ 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. |
|
+ */ |
|
+ |
|
+ grub_verifier_unregister (&grub_appendedsig_verifier); |
|
+ grub_unregister_command (cmd_verify); |
|
+ grub_unregister_command (cmd_list); |
|
+ grub_unregister_command (cmd_trust); |
|
+ grub_unregister_command (cmd_distrust); |
|
+} |
|
diff --git a/include/grub/file.h b/include/grub/file.h |
|
index 31567483ccf..96827a4f896 100644 |
|
--- a/include/grub/file.h |
|
+++ b/include/grub/file.h |
|
@@ -80,6 +80,8 @@ enum grub_file_type |
|
GRUB_FILE_TYPE_PUBLIC_KEY, |
|
/* File holding public key to add to trused keys. */ |
|
GRUB_FILE_TYPE_PUBLIC_KEY_TRUST, |
|
+ /* File holding x509 certificiate to add to trusted keys. */ |
|
+ GRUB_FILE_TYPE_CERTIFICATE_TRUST, |
|
/* File of which we intend to print a blocklist to the user. */ |
|
GRUB_FILE_TYPE_PRINT_BLOCKLIST, |
|
/* File we intend to use for test loading or testing speed. */
|
|
|