On Thu, Mar 27, 2025 at 01:02:30AM +0530, Sudhakar Kuppusamy wrote: > From: Daniel Axtens <d...@axtens.net> > > This code allows us to parse: > > - PKCS#7 signedData messages. Only a single signerInfo is supported, > which is all that the Linux sign-file utility supports creating > out-of-the-box. Only RSA, SHA-256 and SHA-512 are supported. > Any certificate embedded in the PKCS#7 message will be ignored. > > - X.509 certificates: at least enough to verify the signatures on the > PKCS#7 messages. We expect that the certificates embedded in grub will
s/grub/GRUB/ > be leaf certificates, not CA certificates. The parser enforces this. > > - X.509 certificates support the Extended Key Usage extension and handle > it by verifying that the certificate has a Code Signing usage. Could not you split this patch to 2 or 3 logical parts? > Signed-off-by: Javier Martinez Canillas <javi...@redhat.com> # EKU support > Reported-by: Michal Suchanek <msucha...@suse.com> # key usage issue > 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/commands/appendedsig/appendedsig.h | 110 +++ > grub-core/commands/appendedsig/asn1util.c | 98 ++ > grub-core/commands/appendedsig/pkcs7.c | 460 +++++++++ > grub-core/commands/appendedsig/x509.c | 953 +++++++++++++++++++ > 4 files changed, 1621 insertions(+) > create mode 100644 grub-core/commands/appendedsig/appendedsig.h > create mode 100644 grub-core/commands/appendedsig/asn1util.c > create mode 100644 grub-core/commands/appendedsig/pkcs7.c > create mode 100644 grub-core/commands/appendedsig/x509.c > > diff --git a/grub-core/commands/appendedsig/appendedsig.h > b/grub-core/commands/appendedsig/appendedsig.h > new file mode 100644 > index 000000000..3f4d7002e > --- /dev/null > +++ b/grub-core/commands/appendedsig/appendedsig.h > @@ -0,0 +1,110 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * Copyright (C) 2020, 2022 Free Software Foundation, Inc. > + * Copyright (C) 2020, 2022 IBM Corporation s/2020, 2022/2020, 2022, 2025/ everywhere please... > + * > + * 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/crypto.h> > +#include <grub/libtasn1.h> > + > +extern asn1_node _gnutls_gnutls_asn; s/_gnutls_gnutls_asn/grub_gnutls_gnutls_asn/ > +extern asn1_node _gnutls_pkix_asn; s/_gnutls_pkix_asn/grub_gnutls_pkix_asn/ > +#define MAX_OID_LEN 32 > + > +/* > + * 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. > + */ > +struct pkcs7_signedData > +{ > + int signerInfo_count; > + struct pkcs7_signerInfo *signerInfos; > +}; > + > +/* Do libtasn1 init */ > +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 > +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); > + > +/* > + * 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 (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); > + > +/* > + * 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 > + * - 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); > diff --git a/grub-core/commands/appendedsig/asn1util.c > b/grub-core/commands/appendedsig/asn1util.c > new file mode 100644 > index 000000000..06c3b61c1 > --- /dev/null > +++ b/grub-core/commands/appendedsig/asn1util.c > @@ -0,0 +1,98 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * 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 > + * 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/libtasn1.h> > +#include <grub/types.h> > +#include <grub/err.h> > +#include <grub/mm.h> > +#include <grub/crypto.h> > +#include <grub/misc.h> > +#include <grub/gcrypt/gcrypt.h> > + > +#include "appendedsig.h" > + > +asn1_node _gnutls_gnutls_asn = NULL; > +asn1_node _gnutls_pkix_asn = NULL; Global variables should be prefixed with grub_... > +extern const asn1_static_node gnutls_asn1_tab[]; > +extern const asn1_static_node pkix_asn1_tab[]; Ditto. > +/* > + * 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 > + * - 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) > +{ > + int result; > + grub_uint8_t *tmpstr = NULL; > + int tmpstr_size = 0; > + > + result = asn1_read_value (node, name, NULL, &tmpstr_size); > + if (result != ASN1_MEM_ERROR) > + { > + grub_snprintf (grub_errmsg, sizeof (grub_errmsg), > + _("Reading size of %s did not return expected status: > %s"), Please drop _(). > + friendly_name, asn1_strerror (result)); > + grub_errno = GRUB_ERR_BAD_FILE_TYPE; Why do not you use grub_error() here? > + return NULL; > + } > + > + tmpstr = grub_malloc (tmpstr_size); > + if (tmpstr == NULL) > + { > + grub_snprintf (grub_errmsg, sizeof (grub_errmsg), > + "Could not allocate memory to store %s", friendly_name); > + grub_errno = GRUB_ERR_OUT_OF_MEMORY; Ditto. > + return NULL; > + } > + > + result = asn1_read_value (node, name, tmpstr, &tmpstr_size); > + if (result != ASN1_SUCCESS) > + { > + grub_free (tmpstr); > + grub_snprintf (grub_errmsg, sizeof (grub_errmsg), "Error reading %s: > %s", > + friendly_name, asn1_strerror (result)); > + grub_errno = GRUB_ERR_BAD_FILE_TYPE; Ditto. > + return NULL; > + } > + > + *content_size = tmpstr_size; > + > + return tmpstr; > +} > + > +int > +asn1_init (void) > +{ > + int res; Missing empty line. > + res = asn1_array2tree (gnutls_asn1_tab, &_gnutls_gnutls_asn, NULL); > + if (res != ASN1_SUCCESS) > + 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 > new file mode 100644 > index 000000000..31a5eab6a > --- /dev/null > +++ b/grub-core/commands/appendedsig/pkcs7.c > @@ -0,0 +1,460 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * 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 > + * 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 "appendedsig.h" > +#include <grub/misc.h> > +#include <grub/crypto.h> > +#include <grub/gcrypt/gcrypt.h> > +#include <sys/types.h> > + > +static char asn1_error[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; > + > +/* > + * RFC 5652 s 5.1 > + */ Please put this comment in one line and s/s/section/? > +static const char *signedData_oid = "1.2.840.113549.1.7.2"; > + > +/* > + * RFC 4055 s 2.1 > + */ Ditto. Please fix similar problems everywhere... > +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) > +{ > + int res; > + asn1_node signed_part; > + grub_err_t err = GRUB_ERR_NONE; > + 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); > + if (res != ASN1_SUCCESS) > + return grub_error (GRUB_ERR_OUT_OF_MEMORY, > + "Could not create ASN.1 structure for PKCS#7 signed > part."); All error messages in the core GRUB code start with lowercase and do not have full stop at the end. Please fix it in all patches. > + res = asn1_der_decoding2 (&signed_part, content, &size, > + 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); > + goto cleanup_signed_part; > + } > + > + /* > + * SignedData ::= SEQUENCE { > + * version CMSVersion, > + * digestAlgorithms DigestAlgorithmIdentifiers, > + * encapContentInfo EncapsulatedContentInfo, > + * certificates [0] IMPLICIT CertificateSet OPTIONAL, > + * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, > + * signerInfos SignerInfos } > + */ > + > + /* version per the algo in 5.1, must be 1 */ Could you expand this comment? It has to be clear. > + 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)); > + goto cleanup_signed_part; > + } > + > + if (version != 1) > + { > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, > + "Unexpected signature version v%d, only v1 > supported", version); > + goto cleanup_signed_part; > + } > + > + /* > + * digestAlgorithms DigestAlgorithmIdentifiers > + * > + * 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); > + if (res != ASN1_SUCCESS) > + { > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error counting number of > digest algorithms: %s", > + asn1_strerror (res)); > + goto cleanup_signed_part; > + } > + > + if (algo_count <= 0) > + { > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, > + "A minimum of 1 digest algorithm is required"); > + goto cleanup_signed_part; > + } > + > + if (algo_count > 2) > + { > + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, > + "A maximum of 2 digest algorithms is supported"); > + goto cleanup_signed_part; > + } > + > + sha256_in_da = false; > + sha512_in_da = false; > + > + for (i = 0; i < algo_count; i++) > + { > + 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 == false (for bools) please everywhere... > + 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 > + * no way of dealing with that at the moment. > + */ > + 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"); > + goto cleanup_signed_part; > + } > + > + /* read the signatures */ > + > + res = asn1_number_of_elements (signed_part, "signerInfos", &signer_count); > + if (res != ASN1_SUCCESS) > + { > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "Error counting number of > signers: %s", > + asn1_strerror (res)); > + goto cleanup_signed_part; > + } > + > + if (signer_count <= 0) > + { > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, "A minimum of 1 signer is > required"); > + goto cleanup_signed_part; > + } > + > + msg->signerInfos = grub_calloc (signer_count, sizeof (struct > pkcs7_signerInfo)); > + if (!msg->signerInfos) msg->signerInfos == NULL (for pointers) everywhere please... Additionally, all (goto) labels should be prefixed with one space. Daniel _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel