From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Nicolas Frayer Date: Wed, 21 May 2025 15:43:44 -0400 Subject: [PATCH] appended/sig: sync'd with upstream code Signed-off-by: Nicolas Frayer --- grub-core/commands/appendedsig/appendedsig.c | 479 +++++++++--------- grub-core/commands/appendedsig/appendedsig.h | 50 +- grub-core/commands/appendedsig/asn1util.c | 34 +- grub-core/commands/appendedsig/pkcs7.c | 389 +++++++++----- grub-core/commands/appendedsig/x509.c | 727 ++++++++++++--------------- 5 files changed, 853 insertions(+), 826 deletions(-) diff --git a/grub-core/commands/appendedsig/appendedsig.c b/grub-core/commands/appendedsig/appendedsig.c index bf8b18b..f4eefe5 100644 --- a/grub-core/commands/appendedsig/appendedsig.c +++ b/grub-core/commands/appendedsig/appendedsig.c @@ -1,6 +1,7 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2020-2021 IBM Corporation. + * 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 @@ -46,24 +47,21 @@ const char magic[] = "~Module signature appended~\n"; */ 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 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_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 */ + 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 */ @@ -90,143 +88,154 @@ struct x509_certificate *grub_trusted_key; */ extern gcry_pk_spec_t _gcry_pubkey_spec_rsa; -static int check_sigs = 0; +static enum +{ + check_sigs_no = 0, + check_sigs_enforce = 1, + check_sigs_forced = 2 +} 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 == 2) + if (check_sigs == check_sigs_forced) return "forced"; - else if (check_sigs == 1) + 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) +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) + if (check_sigs == check_sigs_forced) return grub_strdup ("forced"); if ((*val == '2') || (*val == 'f')) - check_sigs = 2; + check_sigs = check_sigs_forced; else if ((*val == '1') || (*val == 'e')) - check_sigs = 1; + check_sigs = check_sigs_enforce; else if ((*val == '0') || (*val == 'n')) - check_sigs = 0; + check_sigs = check_sigs_no; return grub_strdup (grub_env_read_sec (NULL, NULL)); } +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"), + 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 = NULL; - grub_ssize_t read_size; - grub_off_t total_read_size = 0; - grub_off_t file_size = grub_file_size (f); + grub_uint8_t *buf; + grub_size_t file_size; - - 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); + err = file_read_all (f, &buf, &file_size); if (err != GRUB_ERR_NONE) - goto cleanup_buf; + return err; - return GRUB_ERR_NONE; - -cleanup_buf: + err = parse_x509_certificate (buf, file_size, certificate); 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) +extract_appended_signature (const 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); + 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")); + 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")); + 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")); + 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)); + 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 + 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")); + 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; + 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; + return parse_pkcs7_signedData (appsigdata, pkcs7_size, &sig->pkcs7); } static grub_err_t -grub_verify_appended_signature (grub_uint8_t * buf, grub_size_t bufsize) +grub_verify_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize) { grub_err_t err = GRUB_ERR_NONE; grub_size_t datasize; @@ -236,10 +245,11 @@ grub_verify_appended_signature (grub_uint8_t * buf, grub_size_t bufsize) 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")); + 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) @@ -247,67 +257,78 @@ grub_verify_appended_signature (grub_uint8_t * buf, grub_size_t bufsize) 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) + for (i = 0; i < sig.pkcs7.signerInfo_count; i++) { - 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); + /* + * 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); + 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")); + 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_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; + grub_size_t file_size; if (argc < 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); @@ -321,35 +342,14 @@ grub_cmd_verify_signature (grub_command_t cmd __attribute__((unused)), 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 = file_read_all (f, &data, &file_size); + if (err != GRUB_ERR_NONE) + goto cleanup; err = grub_verify_appended_signature (data, file_size); -cleanup_data: grub_free (data); + cleanup: if (f) grub_file_close (f); @@ -357,8 +357,7 @@ cleanup: } static grub_err_t -grub_cmd_distrust (grub_command_t cmd __attribute__((unused)), - int argc, char **args) +grub_cmd_distrust (grub_command_t cmd __attribute__ ((unused)), int argc, char **args) { unsigned long cert_num, i; struct x509_certificate *cert, *prev; @@ -373,7 +372,7 @@ grub_cmd_distrust (grub_command_t cmd __attribute__((unused)), if (cert_num < 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, - N_("Certificate number too small - numbers start at 1")); + N_("Certificate number too small - numbers start at 1")); if (cert_num == 1) { @@ -390,25 +389,24 @@ grub_cmd_distrust (grub_command_t cmd __attribute__((unused)), while (cert) { if (i == cert_num) - { - prev->next = cert->next; - certificate_release (cert); - grub_free (cert); - return GRUB_ERR_NONE; - } + { + 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); + N_("No certificate number %lu found - only %lu 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_cmd_trust (grub_command_t cmd __attribute__ ((unused)), int argc, char **args) { grub_file_t certf; struct x509_certificate *cert = NULL; @@ -417,17 +415,13 @@ grub_cmd_trust (grub_command_t cmd __attribute__((unused)), 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); + 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")); + 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); @@ -436,8 +430,7 @@ grub_cmd_trust (grub_command_t cmd __attribute__((unused)), grub_free (cert); return err; } - grub_dprintf ("appendedsig", "Loaded certificate with CN: %s\n", - cert->subject); + grub_dprintf ("appendedsig", "Loaded certificate with CN: %s\n", cert->subject); cert->next = grub_trusted_key; grub_trusted_key = cert; @@ -446,9 +439,8 @@ grub_cmd_trust (grub_command_t cmd __attribute__((unused)), } static grub_err_t -grub_cmd_list (grub_command_t cmd __attribute__((unused)), - int argc __attribute__((unused)), - char **args __attribute__((unused))) +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; @@ -460,26 +452,23 @@ grub_cmd_list (grub_command_t cmd __attribute__((unused)), grub_printf (N_("\tSerial: ")); for (i = 0; i < cert->serial_len - 1; i++) - { - grub_printf ("%02x:", cert->serial[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) +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) + if (check_sigs == check_sigs_no) { *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION; return GRUB_ERR_NONE; @@ -487,48 +476,47 @@ appendedsig_init (grub_file_t io __attribute__((unused)), 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_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 */ + /* 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_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; + 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; + 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) +appendedsig_write (void *ctxt __attribute__ ((unused)), void *buf, grub_size_t size) { return grub_verify_appended_signature (buf, size); } @@ -547,10 +535,7 @@ pseudo_read (struct grub_file *file, char *buf, grub_size_t len) } /* Filesystem descriptor. */ -static struct grub_fs pseudo_fs = { - .name = "pseudo", - .fs_read = pseudo_read -}; +static struct grub_fs pseudo_fs = { .name = "pseudo", .fs_read = pseudo_read }; static grub_command_t cmd_verify, cmd_list, cmd_distrust, cmd_trust; @@ -561,19 +546,15 @@ GRUB_MOD_INIT (appendedsig) /* If in lockdown, immediately enter forced mode */ if (grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED) - check_sigs = 2; + check_sigs = check_sigs_forced; grub_trusted_key = NULL; - - grub_register_variable_hook ("check_appended_signatures", - grub_env_read_sec, - grub_env_write_sec); + 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)); + grub_fatal ("Error initing ASN.1 data structures: %d: %s\n", rc, asn1_strerror (rc)); FOR_MODULES (header) { @@ -581,7 +562,7 @@ GRUB_MOD_INIT (appendedsig) struct x509_certificate *pk = NULL; grub_err_t err; - /* Not an ELF module, skip. */ + /* Not an X.509 certificate, skip. */ if (header->type != OBJ_TYPE_X509_PUBKEY) continue; @@ -590,15 +571,12 @@ GRUB_MOD_INIT (appendedsig) 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); + 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"); - } + grub_fatal ("Out of memory loading initial certificates"); err = read_cert_from_file (&pseudo_file, pk); if (err != GRUB_ERR_NONE) @@ -610,21 +588,16 @@ GRUB_MOD_INIT (appendedsig) 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.")); + 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); diff --git a/grub-core/commands/appendedsig/appendedsig.h b/grub-core/commands/appendedsig/appendedsig.h index 9792ef3..3f4d700 100644 --- a/grub-core/commands/appendedsig/appendedsig.h +++ b/grub-core/commands/appendedsig/appendedsig.h @@ -1,6 +1,7 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2020 IBM Corporation. + * Copyright (C) 2020, 2022 Free Software Foundation, Inc. + * Copyright (C) 2020, 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 @@ -26,85 +27,84 @@ extern asn1_node _gnutls_pkix_asn; /* * One or more x509 certificates. - * * We do limited parsing: extracting only the serial, CN and RSA public key. */ struct x509_certificate { struct x509_certificate *next; - grub_uint8_t *serial; grub_size_t serial_len; - char *subject; grub_size_t subject_len; - /* We only support RSA public keys. This encodes [modulus, publicExponent] */ gcry_mpi_t mpis[2]; }; +/* + * A PKCS#7 signedData signerInfo. + */ +struct pkcs7_signerInfo +{ + const gcry_md_spec_t *hash; + gcry_mpi_t sig_mpi; +}; + /* * A PKCS#7 signedData message. - * * We make no attempt to match intelligently, so we don't save any info about - * the signer. We also support only 1 signerInfo, so we only store a single - * MPI for the signature. + * the signer. */ struct pkcs7_signedData { - const gcry_md_spec_t *hash; - gcry_mpi_t sig_mpi; + int signerInfo_count; + struct pkcs7_signerInfo *signerInfos; }; - /* Do libtasn1 init */ -int asn1_init (void); +int +asn1_init (void); /* * Import a DER-encoded certificate at 'data', of size 'size'. - * * Place the results into 'results', which must be already allocated. */ grub_err_t -certificate_import (void *data, grub_size_t size, - struct x509_certificate *results); +parse_x509_certificate (const void *data, grub_size_t size, struct x509_certificate *results); /* * Release all the storage associated with the x509 certificate. * If the caller dynamically allocated the certificate, it must free it. * The caller is also responsible for maintenance of the linked list. */ -void certificate_release (struct x509_certificate *cert); +void +certificate_release (struct x509_certificate *cert); /* * Parse a PKCS#7 message, which must be a signedData message. - * * The message must be in 'sigbuf' and of size 'data_size'. The result is * placed in 'msg', which must already be allocated. */ grub_err_t -parse_pkcs7_signedData (void *sigbuf, grub_size_t data_size, - struct pkcs7_signedData *msg); +parse_pkcs7_signedData (const void *sigbuf, grub_size_t data_size, struct pkcs7_signedData *msg); /* * Release all the storage associated with the PKCS#7 message. * If the caller dynamically allocated the message, it must free it. */ -void pkcs7_signedData_release (struct pkcs7_signedData *msg); +void +pkcs7_signedData_release (struct pkcs7_signedData *msg); /* * Read a value from an ASN1 node, allocating memory to store it. - * * It will work for anything where the size libtasn1 returns is right: * - Integers * - Octet strings * - DER encoding of other structures * It will _not_ work for things where libtasn1 size requires adjustment: - * - Strings that require an extra NULL byte at the end + * - Strings that require an extra null byte at the end * - Bit strings because libtasn1 returns the length in bits, not bytes. * * If the function returns a non-NULL value, the caller must free it. */ -void *grub_asn1_allocate_and_read (asn1_node node, const char *name, - const char *friendly_name, - int *content_size); +void * +grub_asn1_allocate_and_read (asn1_node node, const char *name, const char *friendly_name, int *content_size); diff --git a/grub-core/commands/appendedsig/asn1util.c b/grub-core/commands/appendedsig/asn1util.c index eff095a..06c3b61 100644 --- a/grub-core/commands/appendedsig/asn1util.c +++ b/grub-core/commands/appendedsig/asn1util.c @@ -1,6 +1,7 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2020 IBM Corporation. + * Copyright (C) 2020, 2022 Free Software Foundation, Inc. + * Copyright (C) 2020, 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 @@ -21,19 +22,19 @@ #include #include #include +#include #include #include "appendedsig.h" -asn1_node _gnutls_gnutls_asn = ASN1_TYPE_EMPTY; -asn1_node _gnutls_pkix_asn = ASN1_TYPE_EMPTY; +asn1_node _gnutls_gnutls_asn = NULL; +asn1_node _gnutls_pkix_asn = NULL; -extern const ASN1_ARRAY_TYPE gnutls_asn1_tab[]; -extern const ASN1_ARRAY_TYPE pkix_asn1_tab[]; +extern const asn1_static_node gnutls_asn1_tab[]; +extern const asn1_static_node pkix_asn1_tab[]; /* * Read a value from an ASN1 node, allocating memory to store it. - * * It will work for anything where the size libtasn1 returns is right: * - Integers * - Octet strings @@ -41,12 +42,10 @@ extern const ASN1_ARRAY_TYPE pkix_asn1_tab[]; * It will _not_ work for things where libtasn1 size requires adjustment: * - Strings that require an extra NULL byte at the end * - Bit strings because libtasn1 returns the length in bits, not bytes. - * * If the function returns a non-NULL value, the caller must free it. */ void * -grub_asn1_allocate_and_read (asn1_node node, const char *name, - const char *friendly_name, int *content_size) +grub_asn1_allocate_and_read (asn1_node node, const char *name, const char *friendly_name, int *content_size) { int result; grub_uint8_t *tmpstr = NULL; @@ -56,9 +55,8 @@ grub_asn1_allocate_and_read (asn1_node node, const char *name, if (result != ASN1_MEM_ERROR) { grub_snprintf (grub_errmsg, sizeof (grub_errmsg), - _ - ("Reading size of %s did not return expected status: %s"), - friendly_name, asn1_strerror (result)); + _("Reading size of %s did not return expected status: %s"), + friendly_name, asn1_strerror (result)); grub_errno = GRUB_ERR_BAD_FILE_TYPE; return NULL; } @@ -67,7 +65,7 @@ grub_asn1_allocate_and_read (asn1_node node, const char *name, if (tmpstr == NULL) { grub_snprintf (grub_errmsg, sizeof (grub_errmsg), - "Could not allocate memory to store %s", friendly_name); + "Could not allocate memory to store %s", friendly_name); grub_errno = GRUB_ERR_OUT_OF_MEMORY; return NULL; } @@ -76,9 +74,8 @@ grub_asn1_allocate_and_read (asn1_node node, const char *name, if (result != ASN1_SUCCESS) { grub_free (tmpstr); - grub_snprintf (grub_errmsg, sizeof (grub_errmsg), - "Error reading %s: %s", - friendly_name, asn1_strerror (result)); + grub_snprintf (grub_errmsg, sizeof (grub_errmsg), "Error reading %s: %s", + friendly_name, asn1_strerror (result)); grub_errno = GRUB_ERR_BAD_FILE_TYPE; return NULL; } @@ -94,9 +91,8 @@ asn1_init (void) int res; res = asn1_array2tree (gnutls_asn1_tab, &_gnutls_gnutls_asn, NULL); if (res != ASN1_SUCCESS) - { - return res; - } + return res; + res = asn1_array2tree (pkix_asn1_tab, &_gnutls_pkix_asn, NULL); return res; } diff --git a/grub-core/commands/appendedsig/pkcs7.c b/grub-core/commands/appendedsig/pkcs7.c index dc6afe2..31a5eab 100644 --- a/grub-core/commands/appendedsig/pkcs7.c +++ b/grub-core/commands/appendedsig/pkcs7.c @@ -1,6 +1,7 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2020 IBM Corporation. + * Copyright (C) 2020, 2022 Free Software Foundation, Inc. + * Copyright (C) 2020, 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 @@ -20,24 +21,23 @@ #include #include #include - +#include static char asn1_error[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; /* * RFC 5652 s 5.1 */ -const char *signedData_oid = "1.2.840.113549.1.7.2"; +static const char *signedData_oid = "1.2.840.113549.1.7.2"; /* * RFC 4055 s 2.1 */ -const char *sha256_oid = "2.16.840.1.101.3.4.2.1"; -const char *sha512_oid = "2.16.840.1.101.3.4.2.3"; +static const char *sha256_oid = "2.16.840.1.101.3.4.2.1"; +static const char *sha512_oid = "2.16.840.1.101.3.4.2.3"; static grub_err_t -process_content (grub_uint8_t * content, int size, - struct pkcs7_signedData *msg) +process_content (grub_uint8_t *content, int size, struct pkcs7_signedData *msg) { int res; asn1_node signed_part; @@ -45,32 +45,35 @@ process_content (grub_uint8_t * content, int size, char algo_oid[MAX_OID_LEN]; int algo_oid_size = sizeof (algo_oid); int algo_count; + int signer_count; + int i; char version; int version_size = sizeof (version); grub_uint8_t *result_buf; int result_size = 0; int crls_size = 0; gcry_error_t gcry_err; + bool sha256_in_da, sha256_in_si, sha512_in_da, sha512_in_si; + char *da_path; + char *si_sig_path; + char *si_da_path; - res = asn1_create_element (_gnutls_pkix_asn, "PKIX1.pkcs-7-SignedData", - &signed_part); + res = asn1_create_element (_gnutls_pkix_asn, "PKIX1.pkcs-7-SignedData", &signed_part); if (res != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for PKCS#7 signed part."); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for PKCS#7 signed part."); res = asn1_der_decoding2 (&signed_part, content, &size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (res != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error reading PKCS#7 signed data: %s", asn1_error); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Error reading PKCS#7 signed data: %s", asn1_error); goto cleanup_signed_part; } - /* SignedData ::= SEQUENCE { + /* + * SignedData ::= SEQUENCE { * version CMSVersion, * digestAlgorithms DigestAlgorithmIdentifiers, * encapContentInfo EncapsulatedContentInfo, @@ -83,19 +86,15 @@ process_content (grub_uint8_t * content, int size, res = asn1_read_value (signed_part, "version", &version, &version_size); if (res != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error reading signedData version: %s", - asn1_strerror (res)); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error reading signedData version: %s", + asn1_strerror (res)); goto cleanup_signed_part; } if (version != 1) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Unexpected signature version v%d, only v1 supported", - version); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Unexpected signature version v%d, only v1 supported", version); goto cleanup_signed_part; } @@ -104,69 +103,96 @@ process_content (grub_uint8_t * content, int size, * * DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier * DigestAlgorithmIdentifer is an X.509 AlgorithmIdentifier (10.1.1) - * + * * RFC 4055 s 2.1: * sha256Identifier AlgorithmIdentifier ::= { id-sha256, NULL } * sha512Identifier AlgorithmIdentifier ::= { id-sha512, NULL } * * We only support 1 element in the set, and we do not check parameters atm. */ - res = - asn1_number_of_elements (signed_part, "digestAlgorithms", &algo_count); + res = asn1_number_of_elements (signed_part, "digestAlgorithms", &algo_count); if (res != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error counting number of digest algorithms: %s", - asn1_strerror (res)); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error counting number of digest algorithms: %s", + asn1_strerror (res)); goto cleanup_signed_part; } - if (algo_count != 1) + if (algo_count <= 0) { - err = - grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, - "Only 1 digest algorithm is supported"); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "A minimum of 1 digest algorithm is required"); goto cleanup_signed_part; } - res = - asn1_read_value (signed_part, "digestAlgorithms.?1.algorithm", algo_oid, - &algo_oid_size); - if (res != ASN1_SUCCESS) + if (algo_count > 2) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error reading digest algorithm: %s", - asn1_strerror (res)); + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "A maximum of 2 digest algorithms is supported"); goto cleanup_signed_part; } - if (grub_strncmp (sha512_oid, algo_oid, algo_oid_size) == 0) - { - msg->hash = grub_crypto_lookup_md_by_name ("sha512"); - } - else if (grub_strncmp (sha256_oid, algo_oid, algo_oid_size) == 0) - { - msg->hash = grub_crypto_lookup_md_by_name ("sha256"); - } - else - { - err = - grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, - "Only SHA-256 and SHA-512 hashes are supported, found OID %s", - algo_oid); - goto cleanup_signed_part; - } + sha256_in_da = false; + sha512_in_da = false; - if (!msg->hash) + for (i = 0; i < algo_count; i++) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Hash algorithm for OID %s not loaded", algo_oid); - goto cleanup_signed_part; + da_path = grub_xasprintf ("digestAlgorithms.?%d.algorithm", i + 1); + if (!da_path) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not allocate path for digest algorithm " + "parsing path"); + goto cleanup_signed_part; + } + + algo_oid_size = sizeof (algo_oid); + res = asn1_read_value (signed_part, da_path, algo_oid, &algo_oid_size); + if (res != ASN1_SUCCESS) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error reading digest algorithm: %s", + asn1_strerror (res)); + grub_free (da_path); + goto cleanup_signed_part; + } + + if (grub_strncmp (sha512_oid, algo_oid, algo_oid_size) == 0) + { + if (!sha512_in_da) + sha512_in_da = true; + else + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "SHA-512 specified twice in digest algorithm list"); + grub_free (da_path); + goto cleanup_signed_part; + } + } + else if (grub_strncmp (sha256_oid, algo_oid, algo_oid_size) == 0) + { + if (!sha256_in_da) + sha256_in_da = true; + else + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "SHA-256 specified twice in digest algorithm list"); + grub_free (da_path); + goto cleanup_signed_part; + } + } + else + { + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "Only SHA-256 and SHA-512 hashes are supported, found OID %s", + algo_oid); + grub_free (da_path); + goto cleanup_signed_part; + } + + grub_free (da_path); } + /* at this point, at least one of sha{256,512}_in_da must be true */ + /* * We ignore the certificates, but we don't permit CRLs. * A CRL entry might be revoking the certificate we're using, and we have @@ -175,45 +201,179 @@ process_content (grub_uint8_t * content, int size, res = asn1_read_value (signed_part, "crls", NULL, &crls_size); if (res != ASN1_ELEMENT_NOT_FOUND) { - err = - grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, - "PKCS#7 messages with embedded CRLs are not supported"); + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "PKCS#7 messages with embedded CRLs are not supported"); goto cleanup_signed_part; } - /* read the signature */ - result_buf = - grub_asn1_allocate_and_read (signed_part, "signerInfos.?1.signature", - "signature data", &result_size); - if (!result_buf) + /* read the signatures */ + + res = asn1_number_of_elements (signed_part, "signerInfos", &signer_count); + if (res != ASN1_SUCCESS) { - err = grub_errno; + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error counting number of signers: %s", + asn1_strerror (res)); goto cleanup_signed_part; } - gcry_err = - gcry_mpi_scan (&(msg->sig_mpi), GCRYMPI_FMT_USG, result_buf, result_size, - NULL); - if (gcry_err != GPG_ERR_NO_ERROR) + if (signer_count <= 0) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error loading signature into MPI structure: %d", - gcry_err); - goto cleanup_result; + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "A minimum of 1 signer is required"); + goto cleanup_signed_part; } -cleanup_result: - grub_free (result_buf); + msg->signerInfos = grub_calloc (signer_count, sizeof (struct pkcs7_signerInfo)); + if (!msg->signerInfos) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not allocate space for %d signers", signer_count); + goto cleanup_signed_part; + } + + msg->signerInfo_count = 0; + for (i = 0; i < signer_count; i++) + { + si_da_path = grub_xasprintf ("signerInfos.?%d.digestAlgorithm.algorithm", i + 1); + if (!si_da_path) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "Could not allocate path for signer %d's digest algorithm parsing path", + i); + goto cleanup_signerInfos; + } + + algo_oid_size = sizeof (algo_oid); + res = asn1_read_value (signed_part, si_da_path, algo_oid, &algo_oid_size); + if (res != ASN1_SUCCESS) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Error reading signer %d's digest algorithm: %s", i, + asn1_strerror (res)); + grub_free (si_da_path); + goto cleanup_signerInfos; + } + + grub_free (si_da_path); + + if (grub_strncmp (sha512_oid, algo_oid, algo_oid_size) == 0) + { + if (!sha512_in_da) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Signer %d claims a SHA-512 signature which was not specified in the outer DigestAlgorithms", + i); + goto cleanup_signerInfos; + } + else + { + sha512_in_si = true; + msg->signerInfos[i].hash = grub_crypto_lookup_md_by_name ("sha512"); + } + } + else if (grub_strncmp (sha256_oid, algo_oid, algo_oid_size) == 0) + { + if (!sha256_in_da) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Signer %d claims a SHA-256 signature which was not specified in the outer DigestAlgorithms", + i); + goto cleanup_signerInfos; + } + else + { + sha256_in_si = true; + msg->signerInfos[i].hash = grub_crypto_lookup_md_by_name ("sha256"); + } + } + else + { + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "Only SHA-256 and SHA-512 hashes are supported, found OID %s", + algo_oid); + goto cleanup_signerInfos; + } + + if (!msg->signerInfos[i].hash) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Hash algorithm for signer %d (OID %s) not loaded", i, algo_oid); + goto cleanup_signerInfos; + } + + si_sig_path = grub_xasprintf ("signerInfos.?%d.signature", i + 1); + if (!si_sig_path) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not allocate path for signer %d's signature parsing path", i); + goto cleanup_signerInfos; + } + + result_buf = grub_asn1_allocate_and_read (signed_part, si_sig_path, + "signature data", &result_size); + grub_free (si_sig_path); + + if (!result_buf) + { + err = grub_errno; + goto cleanup_signerInfos; + } + + gcry_err = gcry_mpi_scan (&(msg->signerInfos[i].sig_mpi), GCRYMPI_FMT_USG, + result_buf, result_size, NULL); + + grub_free (result_buf); + + if (gcry_err != GPG_ERR_NO_ERROR) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Error loading signature %d into MPI structure: %d", + i, gcry_err); + goto cleanup_signerInfos; + } + + /* + * use msg->signerInfo_count to track fully populated signerInfos so we + * know how many we need to clean up + */ + msg->signerInfo_count++; + } + + /* + * Final consistency check of signerInfo.*.digestAlgorithm vs + * digestAlgorithms.*.algorithm. An algorithm must be present in both + * digestAlgorithms and signerInfo or in neither. We have already checked + * for an algorithm in signerInfo that is not in digestAlgorithms, here we + * check for algorithms in digestAlgorithms but not in signerInfos. + */ + if (sha512_in_da && !sha512_in_si) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "SHA-512 specified in DigestAlgorithms but did not " + "appear in SignerInfos"); + goto cleanup_signerInfos; + } + + if (sha256_in_da && !sha256_in_si) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "SHA-256 specified in DigestAlgorithms but did not " + "appear in SignerInfos"); + goto cleanup_signerInfos; + } + + asn1_delete_structure (&signed_part); + return GRUB_ERR_NONE; + +cleanup_signerInfos: + for (i = 0; i < msg->signerInfo_count; i++) + gcry_mpi_release (msg->signerInfos[i].sig_mpi); + grub_free (msg->signerInfos); cleanup_signed_part: asn1_delete_structure (&signed_part); - return err; } grub_err_t -parse_pkcs7_signedData (void *sigbuf, grub_size_t data_size, - struct pkcs7_signedData *msg) +parse_pkcs7_signedData (const void *sigbuf, grub_size_t data_size, struct pkcs7_signedData *msg) { int res; asn1_node content_info; @@ -225,26 +385,23 @@ parse_pkcs7_signedData (void *sigbuf, grub_size_t data_size, int size; if (data_size > GRUB_INT_MAX) - return grub_error (GRUB_ERR_OUT_OF_RANGE, - "Cannot parse a PKCS#7 message where data size > INT_MAX"); + return grub_error (GRUB_ERR_OUT_OF_RANGE, "Cannot parse a PKCS#7 message " + "where data size > INT_MAX"); size = (int) data_size; - res = asn1_create_element (_gnutls_pkix_asn, - "PKIX1.pkcs-7-ContentInfo", &content_info); + res = asn1_create_element (_gnutls_pkix_asn, "PKIX1.pkcs-7-ContentInfo", &content_info); if (res != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for PKCS#7 data: %s", - asn1_strerror (res)); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for PKCS#7 data: %s", + asn1_strerror (res)); res = asn1_der_decoding2 (&content_info, sigbuf, &size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + ASN1_DECODE_FLAG_STRICT_DER | ASN1_DECODE_FLAG_ALLOW_PADDING, + asn1_error); if (res != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error decoding PKCS#7 message DER: %s", asn1_error); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Error decoding PKCS#7 message DER: %s", asn1_error); goto cleanup; } @@ -255,31 +412,24 @@ parse_pkcs7_signedData (void *sigbuf, grub_size_t data_size, * * ContentType ::= OBJECT IDENTIFIER */ - res = - asn1_read_value (content_info, "contentType", content_oid, - &content_oid_size); + res = asn1_read_value (content_info, "contentType", content_oid, &content_oid_size); if (res != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Error reading PKCS#7 content type: %s", - asn1_strerror (res)); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error reading PKCS#7 content type: %s", + asn1_strerror (res)); goto cleanup; } /* OID for SignedData defined in 5.1 */ if (grub_strncmp (signedData_oid, content_oid, content_oid_size) != 0) { - err = - grub_error (GRUB_ERR_BAD_SIGNATURE, - "Unexpected content type in PKCS#7 message: OID %s", - content_oid); + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "Unexpected content type in PKCS#7 message: OID %s", content_oid); goto cleanup; } - content = - grub_asn1_allocate_and_read (content_info, "content", - "PKCS#7 message content", &content_size); + content = grub_asn1_allocate_and_read (content_info, "content", + "PKCS#7 message content", &content_size); if (!content) { err = grub_errno; @@ -301,5 +451,10 @@ cleanup: void pkcs7_signedData_release (struct pkcs7_signedData *msg) { - gcry_mpi_release (msg->sig_mpi); + grub_ssize_t i; + + for (i = 0; i < msg->signerInfo_count; i++) + gcry_mpi_release (msg->signerInfos[i].sig_mpi); + + grub_free (msg->signerInfos); } diff --git a/grub-core/commands/appendedsig/x509.c b/grub-core/commands/appendedsig/x509.c index 42ec65c..eb87025 100644 --- a/grub-core/commands/appendedsig/x509.c +++ b/grub-core/commands/appendedsig/x509.c @@ -1,6 +1,7 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2020 IBM Corporation. + * Copyright (C) 2020, 2022 Free Software Foundation, Inc. + * Copyright (C) 2020, 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 @@ -21,6 +22,7 @@ #include #include #include +#include #include #include "appendedsig.h" @@ -30,28 +32,30 @@ static char asn1_error[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; /* * RFC 3279 2.3.1 RSA Keys */ -const char *rsaEncryption_oid = "1.2.840.113549.1.1.1"; +static const char *rsaEncryption_oid = "1.2.840.113549.1.1.1"; /* * RFC 5280 Appendix A */ -const char *commonName_oid = "2.5.4.3"; +static const char *commonName_oid = "2.5.4.3"; /* * RFC 5280 4.2.1.3 Key Usage */ -const char *keyUsage_oid = "2.5.29.15"; +static const char *keyUsage_oid = "2.5.29.15"; + +static const grub_uint8_t digitalSignatureUsage = 0x80; /* * RFC 5280 4.2.1.9 Basic Constraints */ -const char *basicConstraints_oid = "2.5.29.19"; +static const char *basicConstraints_oid = "2.5.29.19"; /* * RFC 5280 4.2.1.12 Extended Key Usage */ -const char *extendedKeyUsage_oid = "2.5.29.37"; -const char *codeSigningUsage_oid = "1.3.6.1.5.5.7.3.3"; +static const char *extendedKeyUsage_oid = "2.5.29.37"; +static const char *codeSigningUsage_oid = "1.3.6.1.5.5.7.3.3"; /* * RFC 3279 2.3.1 @@ -66,46 +70,37 @@ const char *codeSigningUsage_oid = "1.3.6.1.5.5.7.3.3"; * exponent e. */ static grub_err_t -grub_parse_rsa_pubkey (grub_uint8_t * der, int dersize, - struct x509_certificate *certificate) +grub_parse_rsa_pubkey (grub_uint8_t *der, int dersize, struct x509_certificate *certificate) { int result; - asn1_node spk = ASN1_TYPE_EMPTY; + asn1_node spk = NULL; grub_uint8_t *m_data, *e_data; int m_size, e_size; grub_err_t err = GRUB_ERR_NONE; gcry_error_t gcry_err; - result = - asn1_create_element (_gnutls_gnutls_asn, "GNUTLS.RSAPublicKey", &spk); + result = asn1_create_element (_gnutls_gnutls_asn, "GNUTLS.RSAPublicKey", &spk); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Cannot create storage for public key ASN.1 data"); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Cannot create storage for public key ASN.1 data"); - result = asn1_der_decoding2 (&spk, der, &dersize, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + result = asn1_der_decoding2 (&spk, der, &dersize, ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Cannot decode certificate public key DER: %s", - asn1_error); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Cannot decode certificate public key DER: %s", asn1_error); goto cleanup; } - m_data = - grub_asn1_allocate_and_read (spk, "modulus", "RSA modulus", &m_size); + m_data = grub_asn1_allocate_and_read (spk, "modulus", "RSA modulus", &m_size); if (!m_data) { err = grub_errno; goto cleanup; } - e_data = - grub_asn1_allocate_and_read (spk, "publicExponent", "RSA public exponent", - &e_size); + e_data = grub_asn1_allocate_and_read (spk, "publicExponent", + "RSA public exponent", &e_size); if (!e_data) { err = grub_errno; @@ -115,30 +110,22 @@ grub_parse_rsa_pubkey (grub_uint8_t * der, int dersize, /* * convert m, e to mpi * - * nscanned is not set for FMT_USG, it's only set for FMT_PGP, + * nscanned is not set for FMT_USG, it's only set for FMT_PGP, * so we can't verify it */ - gcry_err = - gcry_mpi_scan (&certificate->mpis[0], GCRYMPI_FMT_USG, m_data, m_size, - NULL); + gcry_err = gcry_mpi_scan (&certificate->mpis[0], GCRYMPI_FMT_USG, m_data, m_size, NULL); if (gcry_err != GPG_ERR_NO_ERROR) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error loading RSA modulus into MPI structure: %d", - gcry_err); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Error loading RSA modulus into MPI structure: %d", gcry_err); goto cleanup_e_data; } - gcry_err = - gcry_mpi_scan (&certificate->mpis[1], GCRYMPI_FMT_USG, e_data, e_size, - NULL); + gcry_err = gcry_mpi_scan (&certificate->mpis[1], GCRYMPI_FMT_USG, e_data, e_size, NULL); if (gcry_err != GPG_ERR_NO_ERROR) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error loading RSA exponent into MPI structure: %d", - gcry_err); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Error loading RSA exponent into MPI structure: %d", gcry_err); goto cleanup_m_mpi; } @@ -158,7 +145,6 @@ cleanup: return err; } - /* * RFC 5280: * SubjectPublicKeyInfo ::= SEQUENCE { @@ -170,17 +156,15 @@ cleanup: */ static grub_err_t -grub_x509_read_subject_public_key (asn1_node asn, - struct x509_certificate *results) +grub_x509_read_subject_public_key (asn1_node asn, struct x509_certificate *results) { int result; grub_err_t err; const char *algo_name = - "tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm"; + "tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm"; const char *params_name = - "tbsCertificate.subjectPublicKeyInfo.algorithm.parameters"; - const char *pk_name = - "tbsCertificate.subjectPublicKeyInfo.subjectPublicKey"; + "tbsCertificate.subjectPublicKeyInfo.algorithm.parameters"; + const char *pk_name = "tbsCertificate.subjectPublicKeyInfo.subjectPublicKey"; char algo_oid[MAX_OID_LEN]; int algo_size = sizeof (algo_oid); char params_value[2]; @@ -192,21 +176,14 @@ grub_x509_read_subject_public_key (asn1_node asn, /* algorithm: see notes for rsaEncryption_oid */ result = asn1_read_value (asn, algo_name, algo_oid, &algo_size); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading x509 public key algorithm: %s", - asn1_strerror (result)); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading x509 public key algorithm: %s", + asn1_strerror (result)); - if (grub_strncmp (algo_oid, rsaEncryption_oid, sizeof (rsaEncryption_oid)) - != 0) - { - return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, - "Unsupported x509 public key algorithm: %s", - algo_oid); - } + if (grub_strncmp (algo_oid, rsaEncryption_oid, sizeof (rsaEncryption_oid)) != 0) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "Unsupported x509 public key algorithm: %s", algo_oid); - /* + /* * RFC 3279 2.3.1 * The rsaEncryption OID is intended to be used in the algorithm field * of a value of type AlgorithmIdentifier. The parameters field MUST @@ -214,17 +191,12 @@ grub_x509_read_subject_public_key (asn1_node asn, */ result = asn1_read_value (asn, params_name, params_value, ¶ms_size); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading x509 public key parameters: %s", - asn1_strerror (result)); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading x509 public key parameters: %s", + asn1_strerror (result)); if (params_value[0] != ASN1_TAG_NULL) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Invalid x509 public key parameters: expected NULL"); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Invalid x509 public key parameters: expected NULL"); /* * RFC 3279 2.3.1: The DER encoded RSAPublicKey is the value of the BIT @@ -232,34 +204,26 @@ grub_x509_read_subject_public_key (asn1_node asn, */ result = asn1_read_value_type (asn, pk_name, NULL, &key_size, &key_type); if (result != ASN1_MEM_ERROR) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading size of x509 public key: %s", - asn1_strerror (result)); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading size of x509 public key: %s", + asn1_strerror (result)); if (key_type != ASN1_ETYPE_BIT_STRING) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Unexpected ASN.1 type when reading x509 public key: %x", - key_type); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected ASN.1 type when reading x509 public key: %x", + key_type); /* length is in bits */ key_size = (key_size + 7) / 8; key_data = grub_malloc (key_size); if (!key_data) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Out of memory for x509 public key"); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Out of memory for x509 public key"); result = asn1_read_value (asn, pk_name, key_data, &key_size); if (result != ASN1_SUCCESS) { grub_free (key_data); return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading public key data"); + "Error reading public key data"); } key_size = (key_size + 7) / 8; @@ -271,8 +235,7 @@ grub_x509_read_subject_public_key (asn1_node asn, /* Decode a string as defined in Appendix A */ static grub_err_t -decode_string (char *der, int der_size, char **string, - grub_size_t * string_size) +decode_string (char *der, int der_size, char **string, grub_size_t *string_size) { asn1_node strasn; int result; @@ -281,51 +244,51 @@ decode_string (char *der, int der_size, char **string, int tmp_size = 0; grub_err_t err = GRUB_ERR_NONE; - result = - asn1_create_element (_gnutls_pkix_asn, "PKIX1.DirectoryString", &strasn); + result = asn1_create_element (_gnutls_pkix_asn, "PKIX1.DirectoryString", &strasn); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for certificate: %s", - asn1_strerror (result)); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for certificate: %s", + asn1_strerror (result)); - result = asn1_der_decoding2 (&strasn, der, &der_size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + result = asn1_der_decoding2 (&strasn, der, &der_size, ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Could not parse DER for DirectoryString: %s", - asn1_error); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Could not parse DER for DirectoryString: %s", asn1_error); goto cleanup; } - choice = - grub_asn1_allocate_and_read (strasn, "", "DirectoryString choice", - &choice_size); + choice = grub_asn1_allocate_and_read (strasn, "", "DirectoryString choice", &choice_size); if (!choice) { err = grub_errno; goto cleanup; } - if (grub_strncmp ("utf8String", choice, choice_size)) + if (grub_strncmp ("utf8String", choice, choice_size) == 0) { - err = - grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, - "Only UTF-8 DirectoryStrings are supported, got %s", - choice); - goto cleanup_choice; + result = asn1_read_value (strasn, "utf8String", NULL, &tmp_size); + if (result != ASN1_MEM_ERROR) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading size of UTF-8 string: %s", + asn1_strerror (result)); + goto cleanup_choice; + } } - - result = asn1_read_value (strasn, "utf8String", NULL, &tmp_size); - if (result != ASN1_MEM_ERROR) + else if (grub_strncmp ("printableString", choice, choice_size) == 0) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading size of UTF-8 string: %s", - asn1_strerror (result)); + result = asn1_read_value (strasn, "printableString", NULL, &tmp_size); + if (result != ASN1_MEM_ERROR) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading size of UTF-8 string: %s", + asn1_strerror (result)); + goto cleanup_choice; + } + } + else + { + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "Only UTF-8 and printable DirectoryStrings are supported, got %s", + choice); goto cleanup_choice; } @@ -335,20 +298,18 @@ decode_string (char *der, int der_size, char **string, *string = grub_malloc (tmp_size); if (!*string) { - err = - grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Cannot allocate memory for DirectoryString contents"); + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Cannot allocate memory for DirectoryString contents"); goto cleanup_choice; } - result = asn1_read_value (strasn, "utf8String", *string, &tmp_size); + result = asn1_read_value (strasn, choice, *string, &tmp_size); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading out UTF-8 string in DirectoryString: %s", - asn1_strerror (result)); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading out %s in DirectoryString: %s", + choice, asn1_strerror (result)); grub_free (*string); + *string = NULL; goto cleanup_choice; } *string_size = tmp_size + 1; @@ -365,7 +326,7 @@ cleanup: * TBSCertificate ::= SEQUENCE { * version [0] EXPLICIT Version DEFAULT v1, * ... - * + * * Version ::= INTEGER { v1(0), v2(1), v3(2) } */ static grub_err_t @@ -374,19 +335,18 @@ check_version (asn1_node certificate) int rc; const char *name = "tbsCertificate.version"; grub_uint8_t version; - int len = 1; + int len = sizeof (version); rc = asn1_read_value (certificate, name, &version, &len); /* require version 3 */ if (rc != ASN1_SUCCESS || len != 1) return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading certificate version"); + "Error reading certificate version"); if (version != 0x02) - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Invalid x509 certificate version, expected v3 (0x02), got 0x%02x", - version); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Invalid x509 certificate version, expected v3 (0x02), got 0x%02x", + version); return GRUB_ERR_NONE; } @@ -397,8 +357,7 @@ check_version (asn1_node certificate) * For simplicity, we extract only the CN. */ static grub_err_t -read_name (asn1_node asn, const char *name_path, char **name, - grub_size_t * name_size) +read_name (asn1_node asn, const char *name_path, char **name, grub_size_t *name_size) { int seq_components, set_components; int result; @@ -415,16 +374,13 @@ read_name (asn1_node asn, const char *name_path, char **name, top_path = grub_xasprintf ("%s.rdnSequence", name_path); if (!top_path) return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not allocate memory for %s name parsing path", - name_path); + "Could not allocate memory for %s name parsing path", name_path); result = asn1_number_of_elements (asn, top_path, &seq_components); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error counting name components: %s", - asn1_strerror (result)); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error counting name components: %s", + asn1_strerror (result)); goto cleanup; } @@ -432,85 +388,75 @@ read_name (asn1_node asn, const char *name_path, char **name, { set_path = grub_xasprintf ("%s.?%d", top_path, i); if (!set_path) - { - err = - grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not allocate memory for %s name set parsing path", - name_path); - goto cleanup_set; - } + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "Could not allocate memory for %s name set parsing path", + name_path); + goto cleanup_set; + } /* this brings us, hopefully, to a set */ result = asn1_number_of_elements (asn, set_path, &set_components); if (result != ASN1_SUCCESS) - { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error counting name sub-components components (element %d): %s", - i, asn1_strerror (result)); - goto cleanup_set; - } + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error counting name sub-components components (element %d): %s", + i, asn1_strerror (result)); + goto cleanup_set; + } for (j = 1; j <= set_components; j++) - { - type_path = grub_xasprintf ("%s.?%d.?%d.type", top_path, i, j); - if (!type_path) - { - err = - grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not allocate memory for %s name component type path", - name_path); - goto cleanup_set; - } - type_len = sizeof (type); - result = asn1_read_value (asn, type_path, type, &type_len); - if (result != ASN1_SUCCESS) - { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading %s name component type: %s", - name_path, asn1_strerror (result)); - goto cleanup_type; - } + { + type_path = grub_xasprintf ("%s.?%d.?%d.type", top_path, i, j); + if (!type_path) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "Could not allocate memory for %s name component type path", + name_path); + goto cleanup_set; + } + type_len = sizeof (type); + result = asn1_read_value (asn, type_path, type, &type_len); + if (result != ASN1_SUCCESS) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading %s name component type: %s", + name_path, asn1_strerror (result)); + goto cleanup_type; + } - if (grub_strncmp (type, commonName_oid, type_len) != 0) - { - grub_free (type_path); - continue; - } + if (grub_strncmp (type, commonName_oid, type_len) != 0) + { + grub_free (type_path); + continue; + } - val_path = grub_xasprintf ("%s.?%d.?%d.value", top_path, i, j); - if (!val_path) - { - err = - grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not allocate memory for %s name component value path", - name_path); - goto cleanup_set; - } + val_path = grub_xasprintf ("%s.?%d.?%d.value", top_path, i, j); + if (!val_path) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "Could not allocate memory for %s name component value path", + name_path); + goto cleanup_type; + } - string_der = - grub_asn1_allocate_and_read (asn, val_path, name_path, - &string_size); - if (!string_der) - { - err = grub_errno; - goto cleanup_val_path; - } + string_der = grub_asn1_allocate_and_read (asn, val_path, name_path, &string_size); + if (!string_der) + { + err = grub_errno; + goto cleanup_val_path; + } - err = decode_string (string_der, string_size, name, name_size); - if (err) - goto cleanup_string; + err = decode_string (string_der, string_size, name, name_size); + if (err) + goto cleanup_string; - grub_free (string_der); - grub_free (type_path); - grub_free (val_path); - break; - } + grub_free (string_der); + grub_free (type_path); + grub_free (val_path); + break; + } grub_free (set_path); if (*name) - break; + break; } + grub_free (top_path); + return GRUB_ERR_NONE; cleanup_string: @@ -527,51 +473,44 @@ cleanup: } /* - * details here + * Verify the Key Usage extension. + * We require the Digital Signature usage. */ static grub_err_t -verify_key_usage (grub_uint8_t * value, int value_size) +verify_key_usage (grub_uint8_t *value, int value_size) { asn1_node usageasn; int result; grub_err_t err = GRUB_ERR_NONE; grub_uint8_t usage = 0xff; - int usage_size = 1; + int usage_size = sizeof (usage_size); - result = - asn1_create_element (_gnutls_pkix_asn, "PKIX1.KeyUsage", &usageasn); + result = asn1_create_element (_gnutls_pkix_asn, "PKIX1.KeyUsage", &usageasn); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for key usage"); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for key usage"); result = asn1_der_decoding2 (&usageasn, value, &value_size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error parsing DER for Key Usage: %s", asn1_error); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Error parsing DER for Key Usage: %s", asn1_error); goto cleanup; } result = asn1_read_value (usageasn, "", &usage, &usage_size); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading Key Usage value: %s", - asn1_strerror (result)); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading Key Usage value: %s", + asn1_strerror (result)); goto cleanup; } - /* Only the first bit is permitted to be set */ - if (usage != 0x80) + if (!(usage & digitalSignatureUsage)) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected Key Usage value: %x", - usage); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Key Usage (0x%x) missing Digital Signature usage", usage); goto cleanup; } @@ -586,31 +525,25 @@ cleanup: * pathLenConstraint INTEGER (0..MAX) OPTIONAL } */ static grub_err_t -verify_basic_constraints (grub_uint8_t * value, int value_size) +verify_basic_constraints (grub_uint8_t *value, int value_size) { asn1_node basicasn; int result; grub_err_t err = GRUB_ERR_NONE; - char cA[6]; /* FALSE or TRUE */ + char cA[6]; /* FALSE or TRUE */ int cA_size = sizeof (cA); - result = - asn1_create_element (_gnutls_pkix_asn, "PKIX1.BasicConstraints", - &basicasn); + result = asn1_create_element (_gnutls_pkix_asn, "PKIX1.BasicConstraints", &basicasn); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for Basic Constraints"); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for Basic Constraints"); result = asn1_der_decoding2 (&basicasn, value, &value_size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error parsing DER for Basic Constraints: %s", - asn1_error); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Error parsing DER for Basic Constraints: %s", asn1_error); goto cleanup; } @@ -623,18 +556,15 @@ verify_basic_constraints (grub_uint8_t * value, int value_size) } else if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading Basic Constraints cA value: %s", - asn1_strerror (result)); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading Basic Constraints cA value: %s", + asn1_strerror (result)); goto cleanup; } /* The certificate must not be a CA certificate */ if (grub_strncmp ("FALSE", cA, cA_size) != 0) { - err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected CA value: %s", - cA); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected CA value: %s", cA); goto cleanup; } @@ -644,72 +574,64 @@ cleanup: } /* + * Verify the Extended Key Usage extension. + * We require the Code Signing usage. + * * ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId * * KeyPurposeId ::= OBJECT IDENTIFIER */ static grub_err_t -verify_extended_key_usage (grub_uint8_t * value, int value_size) +verify_extended_key_usage (grub_uint8_t *value, int value_size) { asn1_node extendedasn; - int result, count; + int result, count, i = 0; grub_err_t err = GRUB_ERR_NONE; - char usage[MAX_OID_LEN]; + char usage[MAX_OID_LEN], name[3]; int usage_size = sizeof (usage); - result = - asn1_create_element (_gnutls_pkix_asn, "PKIX1.ExtKeyUsageSyntax", - &extendedasn); + result = asn1_create_element (_gnutls_pkix_asn, "PKIX1.ExtKeyUsageSyntax", &extendedasn); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for Extended Key Usage"); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for Extended Key Usage"); result = asn1_der_decoding2 (&extendedasn, value, &value_size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error parsing DER for Extended Key Usage: %s", - asn1_error); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Error parsing DER for Extended Key Usage: %s", asn1_error); goto cleanup; } - /* - * If EKUs are present, there must be exactly 1 and it must be a - * codeSigning usage. - */ - result = asn1_number_of_elements(extendedasn, "", &count); + /* If EKUs are present, it checks the presents of Code Signing usage */ + result = asn1_number_of_elements (extendedasn, "", &count); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error counting number of Extended Key Usages: %s", - asn1_strerror (result)); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error counting number of Extended Key Usages: %s", + asn1_strerror (result)); goto cleanup; } - result = asn1_read_value (extendedasn, "?1", usage, &usage_size); - if (result != ASN1_SUCCESS) - { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading Extended Key Usage: %s", - asn1_strerror (result)); - goto cleanup; - } - if (grub_strncmp (codeSigningUsage_oid, usage, usage_size) != 0) + for (i = 1; i < count + 1; i++) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Unexpected Extended Key Usage OID, got: %s", - usage); - goto cleanup; + grub_memset (name, 0, sizeof (name)); + grub_snprintf (name, sizeof (name), "?%d", i); + result = asn1_read_value (extendedasn, name, usage, &usage_size); + if (result != ASN1_SUCCESS) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading Extended Key Usage: %s", + asn1_strerror (result)); + goto cleanup; + } + + if (grub_strncmp (codeSigningUsage_oid, usage, usage_size) == 0) + goto cleanup; } + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Extended Key Usage missing Code Signing usage"); cleanup: asn1_delete_structure (&extendedasn); return err; @@ -727,10 +649,11 @@ cleanup: * -- by extnID * } * - * We require that a certificate: - * - contain the Digital Signature usage only + * A certificate must: + * - contain the Digital Signature usage * - not be a CA - * - MUST not contain any other critical extensions (RFC 5280 s 4.2) + * - contain no extended usages, or contain the Code Signing extended usage + * - not contain any other critical extensions (RFC 5280 s 4.2) */ static grub_err_t verify_extensions (asn1_node cert) @@ -742,116 +665,110 @@ verify_extensions (asn1_node cert) char extnID[MAX_OID_LEN]; int extnID_size; grub_err_t err; - char critical[6]; /* we get either "TRUE" or "FALSE" */ + char critical[6]; /* we get either "TRUE" or "FALSE" */ int critical_size; grub_uint8_t *value; int value_size; - result = - asn1_number_of_elements (cert, "tbsCertificate.extensions", - &num_extensions); + result = asn1_number_of_elements (cert, "tbsCertificate.extensions", &num_extensions); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error counting number of extensions: %s", - asn1_strerror (result)); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error counting number of extensions: %s", + asn1_strerror (result)); if (num_extensions < 2) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Insufficient number of extensions for certificate, need at least 2, got %d", - num_extensions); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Insufficient number of extensions for certificate, need at least 2, got %d", + num_extensions); for (ext = 1; ext <= num_extensions; ext++) { oid_path = grub_xasprintf ("tbsCertificate.extensions.?%d.extnID", ext); + if (!oid_path) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error extension OID path is empty"); + return err; + } extnID_size = sizeof (extnID); result = asn1_read_value (cert, oid_path, extnID, &extnID_size); - if (result != GRUB_ERR_NONE) - { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading extension OID: %s", - asn1_strerror (result)); - goto cleanup_oid_path; - } + if (result != ASN1_SUCCESS) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading extension OID: %s", + asn1_strerror (result)); + goto cleanup_oid_path; + } + + critical_path = grub_xasprintf ("tbsCertificate.extensions.?%d.critical", ext); + if (!critical_path) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error critical path is empty"); + goto cleanup_oid_path; + } - critical_path = - grub_xasprintf ("tbsCertificate.extensions.?%d.critical", ext); critical_size = sizeof (critical); - result = - asn1_read_value (cert, critical_path, critical, &critical_size); + result = asn1_read_value (cert, critical_path, critical, &critical_size); if (result == ASN1_ELEMENT_NOT_FOUND) - { - critical[0] = '\0'; - } + critical[0] = '\0'; else if (result != ASN1_SUCCESS) - { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Error reading extension criticality: %s", - asn1_strerror (result)); - goto cleanup_critical_path; - } + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error reading extension criticality: %s", + asn1_strerror (result)); + goto cleanup_critical_path; + } - value_path = - grub_xasprintf ("tbsCertificate.extensions.?%d.extnValue", ext); - value = - grub_asn1_allocate_and_read (cert, value_path, - "certificate extension value", - &value_size); + value_path = grub_xasprintf ("tbsCertificate.extensions.?%d.extnValue", ext); + if (!value_path) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "Error extnValue path is empty"); + goto cleanup_critical_path; + } + + value = grub_asn1_allocate_and_read (cert, value_path, + "certificate extension value", &value_size); if (!value) - { - err = grub_errno; - goto cleanup_value_path; - } + { + err = grub_errno; + goto cleanup_value_path; + } /* * Now we must see if we recognise the OID. * If we have an unrecognised critical extension we MUST bail. */ if (grub_strncmp (keyUsage_oid, extnID, extnID_size) == 0) - { - err = verify_key_usage (value, value_size); - if (err != GRUB_ERR_NONE) - { - goto cleanup_value; - } - usage_present++; - } + { + err = verify_key_usage (value, value_size); + if (err != GRUB_ERR_NONE) + goto cleanup_value; + + usage_present++; + } else if (grub_strncmp (basicConstraints_oid, extnID, extnID_size) == 0) - { - err = verify_basic_constraints (value, value_size); - if (err != GRUB_ERR_NONE) - { - goto cleanup_value; - } - constraints_present++; - } + { + err = verify_basic_constraints (value, value_size); + if (err != GRUB_ERR_NONE) + goto cleanup_value; + + constraints_present++; + } else if (grub_strncmp (extendedKeyUsage_oid, extnID, extnID_size) == 0) - { - err = verify_extended_key_usage (value, value_size); - if (err != GRUB_ERR_NONE) - { - goto cleanup_value; - } - extended_usage_present++; - } + { + err = verify_extended_key_usage (value, value_size); + if (err != GRUB_ERR_NONE) + goto cleanup_value; + + extended_usage_present++; + } else if (grub_strncmp ("TRUE", critical, critical_size) == 0) - { - /* - * per the RFC, we must not process a certificate with - * a critical extension we do not understand. - */ - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Unhandled critical x509 extension with OID %s", - extnID); - goto cleanup_value; - } + { + /* + * per the RFC, we must not process a certificate with + * a critical extension we do not understand. + */ + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Unhandled critical x509 extension with OID %s", extnID); + goto cleanup_value; + } grub_free (value); grub_free (value_path); @@ -860,23 +777,17 @@ verify_extensions (asn1_node cert) } if (usage_present != 1) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Unexpected number of Key Usage extensions - expected 1, got %d", - usage_present); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected number of Key Usage extensions " + "- expected 1, got %d", usage_present); + if (constraints_present != 1) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Unexpected number of basic constraints extensions - expected 1, got %d", - constraints_present); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected number of basic constraints extensions " + "- expected 1, got %d", constraints_present); + if (extended_usage_present > 1) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Unexpected number of Extended Key Usage extensions - expected 0 or 1, got %d", - extended_usage_present); - } + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "Unexpected number of Extended Key Usage extensions " + "- expected 0 or 1, got %d", extended_usage_present); + return GRUB_ERR_NONE; cleanup_value: @@ -887,6 +798,7 @@ cleanup_critical_path: grub_free (critical_path); cleanup_oid_path: grub_free (oid_path); + return err; } @@ -895,8 +807,7 @@ cleanup_oid_path: * Return the results in @results, which must point to an allocated x509 certificate. */ grub_err_t -certificate_import (void *data, grub_size_t data_size, - struct x509_certificate *results) +parse_x509_certificate (const void *data, grub_size_t data_size, struct x509_certificate *results) { int result = 0; asn1_node cert; @@ -906,45 +817,38 @@ certificate_import (void *data, grub_size_t data_size, if (data_size > GRUB_INT_MAX) return grub_error (GRUB_ERR_OUT_OF_RANGE, - "Cannot parse a certificate where data size > INT_MAX"); + "Cannot parse a certificate where data size > INT_MAX"); size = (int) data_size; result = asn1_create_element (_gnutls_pkix_asn, "PKIX1.Certificate", &cert); if (result != ASN1_SUCCESS) - { - return grub_error (GRUB_ERR_OUT_OF_MEMORY, - "Could not create ASN.1 structure for certificate: %s", - asn1_strerror (result)); - } + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could not create ASN.1 structure for certificate: %s", + asn1_strerror (result)); - result = asn1_der_decoding2 (&cert, data, &size, - ASN1_DECODE_FLAG_STRICT_DER, asn1_error); + result = asn1_der_decoding2 (&cert, data, &size, ASN1_DECODE_FLAG_STRICT_DER, asn1_error); if (result != ASN1_SUCCESS) { - err = - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "Could not parse DER for certificate: %s", asn1_error); + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, + "Could not parse DER for certificate: %s", asn1_error); goto cleanup; } - /* + /* * TBSCertificate ::= SEQUENCE { * version [0] EXPLICIT Version DEFAULT v1 */ err = check_version (cert); if (err != GRUB_ERR_NONE) - { - goto cleanup; - } + goto cleanup; /* * serialNumber CertificateSerialNumber, * * CertificateSerialNumber ::= INTEGER */ - results->serial = - grub_asn1_allocate_and_read (cert, "tbsCertificate.serialNumber", - "certificate serial number", &tmp_size); + results->serial = grub_asn1_allocate_and_read (cert, "tbsCertificate.serialNumber", + "certificate serial number", &tmp_size); if (!results->serial) { err = grub_errno; @@ -956,7 +860,7 @@ certificate_import (void *data, grub_size_t data_size, */ results->serial_len = tmp_size; - /* + /* * signature AlgorithmIdentifier, * * We don't load the signature or issuer at the moment, @@ -984,12 +888,10 @@ certificate_import (void *data, grub_size_t data_size, /* * subject Name, - * + * * This is an X501 name, we parse out just the CN. */ - err = - read_name (cert, "tbsCertificate.subject", &results->subject, - &results->subject_len); + err = read_name (cert, "tbsCertificate.subject", &results->subject, &results->subject_len); if (err != GRUB_ERR_NONE) goto cleanup_serial; @@ -1013,8 +915,7 @@ certificate_import (void *data, grub_size_t data_size, err = verify_extensions (cert); if (err != GRUB_ERR_NONE) - goto cleanup_name; - + goto cleanup_mpis; /* * We do not read or check the signature on the certificate: @@ -1025,7 +926,9 @@ certificate_import (void *data, grub_size_t data_size, asn1_delete_structure (&cert); return GRUB_ERR_NONE; - +cleanup_mpis: + gcry_mpi_release (results->mpis[0]); + gcry_mpi_release (results->mpis[1]); cleanup_name: grub_free (results->subject); cleanup_serial: