File 0007-appended-signatures-Parse-PKCS-7-signedData.patch of Package grub2
From 728d2d0708c4d84aee8c25f5d95e7feb302449cc Mon Sep 17 00:00:00 2001
From: Sudhakar Kuppusamy <sudhakar@linux.ibm.com>
Date: Mon, 25 Aug 2025 16:38:29 +0530
Subject: [PATCH 07/18] appended signatures: Parse PKCS#7 signedData
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.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Signed-off-by: Sudhakar Kuppusamy <sudhakar@linux.ibm.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Reviewed-by: Avnish Chouhan <avnish@linux.ibm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
grub-core/commands/appendedsig/appendedsig.h | 36 ++
grub-core/commands/appendedsig/pkcs7.c | 454 +++++++++++++++++++
2 files changed, 490 insertions(+)
create mode 100644 grub-core/commands/appendedsig/pkcs7.c
diff --git a/grub-core/commands/appendedsig/appendedsig.h b/grub-core/commands/appendedsig/appendedsig.h
index 91cc040bb..cac7fb02c 100644
--- a/grub-core/commands/appendedsig/appendedsig.h
+++ b/grub-core/commands/appendedsig/appendedsig.h
@@ -17,11 +17,47 @@
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <grub/crypto.h>
#include <grub/libtasn1.h>
extern asn1_node grub_gnutls_gnutls_asn;
extern asn1_node grub_gnutls_pkix_asn;
+#define MAX_OID_LEN 32
+
+/* 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;
+};
+
+/*
+ * 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.
+ */
+extern 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.
+ */
+extern void
+pkcs7_signedData_release (struct pkcs7_signedData *msg);
+
/* Do libtasn1 init. */
extern int
asn1_init (void);
diff --git a/grub-core/commands/appendedsig/pkcs7.c b/grub-core/commands/appendedsig/pkcs7.c
new file mode 100644
index 000000000..57dc2a981
--- /dev/null
+++ b/grub-core/commands/appendedsig/pkcs7.c
@@ -0,0 +1,454 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * 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
+ * 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. */
+static const char *signedData_oid = "1.2.840.113549.1.7.2";
+
+/* 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)
+{
+ int res;
+ asn1_node signed_part;
+ grub_err_t err = GRUB_ERR_NONE;
+ char algo_oid[MAX_OID_LEN];
+ int algo_oid_size;
+ 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 (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");
+
+ 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 }
+ */
+
+ 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;
+ }
+
+ /* 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);
+ 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 == 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.
+ */
+ 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 == NULL)
+ {
+ 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 == 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->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 == 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->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 == 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->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 == 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;
+ }
+
+ 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->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 (const void *sigbuf, grub_size_t data_size, struct pkcs7_signedData *msg)
+{
+ int res;
+ asn1_node content_info;
+ grub_err_t err = GRUB_ERR_NONE;
+ char content_oid[MAX_OID_LEN];
+ grub_uint8_t *content;
+ int content_size;
+ int content_oid_size = sizeof (content_oid);
+ 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 > GRUB_INT_MAX");
+
+ size = (int) data_size;
+
+ 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));
+
+ res = asn1_der_decoding2 (&content_info, sigbuf, &size,
+ 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);
+ goto cleanup;
+ }
+
+ /*
+ * ContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * content [0] EXPLICIT ANY DEFINED BY contentType }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ */
+ 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));
+ 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);
+ goto cleanup;
+ }
+
+ content = grub_asn1_allocate_and_read (content_info, "content", "PKCS#7 message content", &content_size);
+ if (content == NULL)
+ {
+ err = grub_errno;
+ goto cleanup;
+ }
+
+ err = process_content (content, content_size, msg);
+ grub_free (content);
+
+ 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.
+ */
+void
+pkcs7_signedData_release (struct pkcs7_signedData *msg)
+{
+ grub_ssize_t i;
+
+ for (i = 0; i < msg->signerInfo_count; i++)
+ _gcry_mpi_release (msg->signerInfos[i].sig_mpi);
+
+ grub_free (msg->signerInfos);
+}
--
2.43.0