On Thu, Mar 27, 2025 at 01:02:31AM +0530, Sudhakar Kuppusamy wrote:
From: Daniel Axtens <d...@axtens.net>
Building on the parsers and the ability to embed x509 certificates, as
well as the existing gcrypt functionality, add a module for verifying
appended signatures.
This includes a verifier that requires that Linux kernels and grub
modules
have appended signatures, and commands to manage the list of trusted
certificates for verification.
Verification must be enabled by setting check_appended_signatures. If
GRUB is locked down when the module is loaded, verification will be
enabled and locked automatically.
As with the PGP verifier, it is not a complete secure-boot solution:
other mechanisms, such as a password or lockdown, must be used to
ensure
that a user cannot drop to the grub shell and disable verification.
Signed-off-by: Daniel Axtens <d...@axtens.net>
Signed-off-by: Sudhakar Kuppusamy <sudha...@linux.ibm.com>
Reviewed-by: Stefan Berger <stef...@linux.ibm.com>
Reviewed-by: Avnish Chouhan <avn...@linux.ibm.com>
---
grub-core/Makefile.core.def | 14 +
grub-core/commands/appendedsig/appendedsig.c | 618
++++++++++++++++++
grub-core/commands/appendedsig/appendedsig.h | 2 +-
grub-core/commands/appendedsig/asn1util.c | 2 +-
.../commands/appendedsig/gnutls_asn1_tab.c | 2 +-
.../commands/appendedsig/pkix_asn1_tab.c | 2 +-
grub-core/commands/appendedsig/x509.c | 2 +-
Please take into account Gary's comment...
include/grub/file.h | 2 +
8 files changed, 639 insertions(+), 5 deletions(-)
create mode 100644 grub-core/commands/appendedsig/appendedsig.c
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 60db2adc5..d693986c6 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -979,6 +979,20 @@ module = {
cppflags = '-I$(srcdir)/lib/posix_wrap';
};
+module = {
+ name = appendedsig;
+ common = commands/appendedsig/appendedsig.c;
+ common = commands/appendedsig/x509.c;
+ common = commands/appendedsig/pkcs7.c;
+ common = commands/appendedsig/asn1util.c;
+ common = commands/appendedsig/gnutls_asn1_tab.c;
+ common = commands/appendedsig/pkix_asn1_tab.c;
+
+ // posix wrapper required for gcry to get sys/types.h
+ cflags = '$(CFLAGS_POSIX)';
+ cppflags = '-I$(srcdir)/lib/posix_wrap
-I$(srcdir)/lib/libtasn1-grub';
+};
+
module = {
name = hdparm;
common = commands/hdparm.c;
diff --git a/grub-core/commands/appendedsig/appendedsig.c
b/grub-core/commands/appendedsig/appendedsig.c
new file mode 100644
index 000000000..cbd227a3c
--- /dev/null
+++ b/grub-core/commands/appendedsig/appendedsig.c
@@ -0,0 +1,618 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2020, 2021, 2022 Free Software Foundation, Inc.
+ * Copyright (C) 2020, 2021, 2022 IBM Corporation
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/file.h>
+#include <grub/command.h>
+#include <grub/crypto.h>
+#include <grub/pkcs1_v15.h>
+#include <grub/i18n.h>
+#include <grub/gcrypt/gcrypt.h>
+#include <grub/kernel.h>
+#include <grub/extcmd.h>
+#include <grub/verify.h>
+#include <libtasn1.h>
+#include <grub/env.h>
+#include <grub/lockdown.h>
+
+#include "appendedsig.h"
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+const char magic[] = "~Module signature appended~\n";
static const char magic[]...?
+/*
+ * 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;
Please take a look at the commit 6744840b1 (build: Track explicit
module
dependencies in Makefile.core.def) and use "depends" instead.
I think I saw similar things in earlier patches. If it is the same case
please fix the other issues as above...
+static enum
+{
+ check_sigs_no = 0,
+ check_sigs_enforce = 1,
+ check_sigs_forced = 2
Please use uppercase for all these constants.
+} check_sigs = check_sigs_no;
+
+static const char *
+grub_env_read_sec (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ if (check_sigs == check_sigs_forced)
+ return "forced";
+ else if (check_sigs == check_sigs_enforce)
+ return "enforce";
+ 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 == check_sigs_forced)
+ return grub_strdup ("forced");
grub_strdup() may fail...
+
+ if ((*val == '2') || (*val == 'f'))
+ check_sigs = check_sigs_forced;
+ else if ((*val == '1') || (*val == 'e'))
+ check_sigs = check_sigs_enforce;
+ else if ((*val == '0') || (*val == 'n'))
+ check_sigs = check_sigs_no;
+
+ return grub_strdup (grub_env_read_sec (NULL, NULL));
Ditto...
+}
+
+static grub_err_t
+file_read_all (grub_file_t file, grub_uint8_t **buf, grub_size_t
*len)
+{
+ grub_off_t full_file_size;
+ grub_size_t file_size, total_read_size = 0;
+ grub_ssize_t read_size;
+
+ full_file_size = grub_file_size (file);
+ if (full_file_size == GRUB_FILE_SIZE_UNKNOWN)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("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,
+ N_("File is too large to read: %"
PRIuGRUB_UINT64_T " bytes"),
+ full_file_size);
+
+ file_size = (grub_size_t) full_file_size;
+
+ *buf = grub_malloc (file_size);
+ if (!*buf)
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ N_("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,
+ N_("Could not read full file size "
+ "(%" PRIuGRUB_SIZE "), only %"
PRIuGRUB_SIZE " bytes read"),
Be careful, you cannot use PRIuGRUB_SIZE conversions in N_(). Though
I would suggest to drop N_() here and from cryptic error messages which
should be targeted only for experts. And probably it would be better to
add file path to this error message.
+ file_size, total_read_size);
+ }
+
+ total_read_size += read_size;
+ }
+ *len = file_size;
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+read_cert_from_file (grub_file_t f, struct x509_certificate
*certificate)
+{
+ grub_err_t err;
+ grub_uint8_t *buf;
+ grub_size_t file_size;
+
+ err = file_read_all (f, &buf, &file_size);
+ if (err != GRUB_ERR_NONE)
+ return err;
+
+ err = parse_x509_certificate (buf, file_size, certificate);
+ grub_free (buf);
+
+ return err;
+}
+
+static grub_err_t
+extract_appended_signature (const grub_uint8_t *buf, grub_size_t
bufsize,
+ struct grub_appended_signature *sig)
+{
+ grub_size_t pkcs7_size;
+ grub_size_t remaining_len;
+ const grub_uint8_t *appsigdata = buf + bufsize - grub_strlen
(magic);
+
+ if (bufsize < grub_strlen (magic))
+ return grub_error (GRUB_ERR_BAD_SIGNATURE, 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"));
+
+ pkcs7_size = grub_be_to_cpu32 (sig->sig_metadata.sig_len);
+
+ 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;
+
+ return parse_pkcs7_signedData (appsigdata, pkcs7_size,
&sig->pkcs7);
+}
+
+static grub_err_t
+grub_verify_appended_signature (const 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;
+ struct pkcs7_signerInfo *si;
+ int i;
+
+ 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;
+
+ for (i = 0; i < sig.pkcs7.signerInfo_count; i++)
+ {
+ /*
+ * This could be optimised in a couple of ways:
+ * - we could only compute hashes once per hash type
+ * - we could track signer information and only verify where
IDs match
+ * For now we do the naive O(trusted keys * pkcs7 signers)
approach.
+ */
+ si = &sig.pkcs7.signerInfos[i];
+ context = grub_zalloc (si->hash->contextsize);
+ if (!context)
+ return grub_errno;
+
+ si->hash->init (context);
+ si->hash->write (context, buf, datasize);
+ si->hash->final (context);
+ hash = si->hash->read (context);
+
+ grub_dprintf ("appendedsig", "data size %" PRIxGRUB_SIZE ",
signer %d hash %02x%02x%02x%02x...\n",
+ datasize, i, 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, si->hash,
pk->mpis[0]);
+ if (rc)
+ {
+ err = grub_error (GRUB_ERR_BAD_SIGNATURE,
+ N_("Error padding hash for RSA
verification: %d"), rc);
Is gcry_err_code_t always equal int type? Even if yes it would be nice
to cast rc to int. And I would drop N_() here.
+ grub_free (context);
+ goto cleanup;
+ }
+
+ rc = _gcry_pubkey_spec_rsa.verify (0, hashmpi,
&si->sig_mpi, pk->mpis, NULL, NULL);
+ gcry_mpi_release (hashmpi);
+
+ if (rc == 0)
+ {
+ grub_dprintf ("appendedsig", "verify signer %d with key
'%s' succeeded\n",
+ i, pk->subject);
+ err = GRUB_ERR_NONE;
+ break;
+ }
+
+ grub_dprintf ("appendedsig", "verify signer %d with key
'%s' failed with %d\n",
+ i, pk->subject, rc);
+ }
+
+ grub_free (context);
+
+ if (err == GRUB_ERR_NONE)
+ break;
+ }
+
+ /* 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:
Missing space before label.
+ 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_size_t file_size;
+
+ 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;
+ }
+
+ err = file_read_all (f, &data, &file_size);
+ if (err != GRUB_ERR_NONE)
+ goto cleanup;
+
+ err = grub_verify_appended_signature (data, file_size);
+
+ grub_free (data);
+
+cleanup:
Ditto...
+ 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;
This check is unreliable. Please take a look at commit ac8a37dda
(net/http: Allow use of non-standard TCP/IP ports) and fix checks
for all grub_strtoul() et consortes calls.
Daniel