From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Sudhakar Kuppusamy Date: Tue, 18 Nov 2025 16:20:56 +0100 Subject: [PATCH] appended signatures: Parse PKCS#7 signed data This code allows us to parse: - PKCS#7 signed data messages. Only a single signer info 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. Signed-off-by: Daniel Axtens Signed-off-by: Sudhakar Kuppusamy Reviewed-by: Avnish Chouhan Reviewed-by: Daniel Kiper --- grub-core/commands/appendedsig/appendedsig.h | 37 +++ grub-core/commands/appendedsig/pkcs7.c | 439 ++++++++++++++++++--------- 2 files changed, 330 insertions(+), 146 deletions(-) diff --git a/grub-core/commands/appendedsig/appendedsig.h b/grub-core/commands/appendedsig/appendedsig.h index 601d616..b0beb89 100644 --- a/grub-core/commands/appendedsig/appendedsig.h +++ b/grub-core/commands/appendedsig/appendedsig.h @@ -17,11 +17,48 @@ * along with GRUB. If not, see . */ +#include #include extern asn1_node grub_gnutls_gnutls_asn; extern asn1_node grub_gnutls_pkix_asn; +#define GRUB_MAX_OID_LEN 32 + +/* A PKCS#7 signed data signer info. */ +struct pkcs7_signer +{ + const gcry_md_spec_t *hash; + gcry_mpi_t sig_mpi; +}; +typedef struct pkcs7_signer grub_pkcs7_signer_t; + +/* + * A PKCS#7 signed data message. We make no attempt to match intelligently, so + * we don't save any info about the signer. + */ +struct pkcs7_data +{ + grub_int32_t signer_count; + grub_pkcs7_signer_t *signers; +}; +typedef struct pkcs7_data grub_pkcs7_data_t; + +/* + * Parse a PKCS#7 message, which must be a signed data message. The message must + * be in 'sigbuf' and of size 'data_size'. The result is placed in 'msg', which + * must already be allocated. + */ +extern grub_err_t +grub_pkcs7_data_parse (const void *sigbuf, grub_size_t data_size, grub_pkcs7_data_t *msg); + +/* + * Release all the storage associated with the PKCS#7 message. If the caller + * dynamically allocated the message, it must free it. + */ +extern void +grub_pkcs7_data_release (grub_pkcs7_data_t *msg); + /* Do libtasn1 init. */ extern int grub_asn1_init (void); diff --git a/grub-core/commands/appendedsig/pkcs7.c b/grub-core/commands/appendedsig/pkcs7.c index dc6afe2..b8e2720 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, 2025 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,57 +21,55 @@ #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"; +/* RFC 5652 s 5.1. */ +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"; +/* RFC 4055 s 2.1. */ +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, grub_int32_t size, grub_pkcs7_data_t *msg) { - int res; + grub_int32_t 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; + char algo_oid[GRUB_MAX_OID_LEN]; + grub_int32_t algo_oid_size; + grub_int32_t algo_count; + grub_int32_t signer_count; + grub_int32_t i; char version; - int version_size = sizeof (version); + grub_int32_t version_size = sizeof (version); grub_uint8_t *result_buf; - int result_size = 0; - int crls_size = 0; + grub_int32_t result_size = 0; + grub_int32_t 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 (grub_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, @@ -79,23 +78,19 @@ process_content (grub_uint8_t * content, int size, * signerInfos SignerInfos } */ - /* version per the algo in 5.1, must be 1 */ 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; } + /* Signature version must be 1 because appended signature only support v1. */ 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,147 +99,301 @@ 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 == NULL) + { + 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 == false) + 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 == false) + 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. + * 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"); + 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; + } + + msg->signers = grub_calloc (signer_count, sizeof (grub_pkcs7_signer_t)); + if (msg->signers == NULL) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, + "could not allocate space for %d signers", signer_count); + goto cleanup_signed_part; + } + + msg->signer_count = 0; + for (i = 0; i < signer_count; i++) + { + si_da_path = grub_xasprintf ("signerInfos.?%d.digestAlgorithm.algorithm", i + 1); + if (si_da_path == NULL) + { + 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 == false) + { + 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->signers[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 == false) + { + 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->signers[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->signers[i].hash == NULL) + { + 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 == NULL) + { + 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 == NULL) + { + err = grub_errno; + goto cleanup_signerInfos; + } + + gcry_err = _gcry_mpi_scan (&(msg->signers[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->signer_count to track fully populated signerInfos so we know + * how many we need to clean up. + */ + msg->signer_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 == true && sha512_in_si == false) + { + err = grub_error (GRUB_ERR_BAD_SIGNATURE, + "SHA-512 specified in DigestAlgorithms but did not appear in SignerInfos"); + goto cleanup_signerInfos; } -cleanup_result: - grub_free (result_buf); -cleanup_signed_part: + if (sha256_in_da == true && sha256_in_si == false) + { + 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->signer_count; i++) + _gcry_mpi_release (msg->signers[i].sig_mpi); + + grub_free (msg->signers); + + 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) +grub_pkcs7_data_parse (const void *sigbuf, grub_size_t data_size, grub_pkcs7_data_t *msg) { - int res; + grub_int32_t res; asn1_node content_info; grub_err_t err = GRUB_ERR_NONE; - char content_oid[MAX_OID_LEN]; + char content_oid[GRUB_MAX_OID_LEN]; grub_uint8_t *content; - int content_size; - int content_oid_size = sizeof (content_oid); - int size; + grub_int32_t content_size; + grub_int32_t content_oid_size = sizeof (content_oid); + grub_int32_t size = (grub_int32_t) data_size; - if (data_size > GRUB_INT_MAX) + if (data_size > GRUB_UINT_MAX) return grub_error (GRUB_ERR_OUT_OF_RANGE, - "Cannot parse a PKCS#7 message where data size > INT_MAX"); - size = (int) data_size; + "cannot parse a PKCS#7 message where data size > GRUB_UINT_MAX"); - res = asn1_create_element (_gnutls_pkix_asn, - "PKIX1.pkcs-7-ContentInfo", &content_info); + res = asn1_create_element (grub_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,32 +404,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 */ + /* 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); - if (!content) + content = grub_asn1_allocate_and_read (content_info, "content", "PKCS#7 message content", &content_size); + if (content == NULL) { err = grub_errno; goto cleanup; @@ -289,17 +430,23 @@ parse_pkcs7_signedData (void *sigbuf, grub_size_t data_size, err = process_content (content, content_size, msg); grub_free (content); -cleanup: + cleanup: asn1_delete_structure (&content_info); + return err; } /* - * Release all the storage associated with the PKCS#7 message. - * If the caller dynamically allocated the message, it must free it. + * 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) +grub_pkcs7_data_release (grub_pkcs7_data_t *msg) { - gcry_mpi_release (msg->sig_mpi); + grub_int32_t i; + + for (i = 0; i < msg->signer_count; i++) + _gcry_mpi_release (msg->signers[i].sig_mpi); + + grub_free (msg->signers); }