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