grub-devel.gnu.org archive mirror
 help / color / mirror / Atom feed
From: Sudhakar Kuppusamy <sudhakar@linux.ibm.com>
To: grub-devel@gnu.org
Cc: dja@axtens.net, jan.setjeeilers@oracle.com,
	julian.klode@canonical.com, mate.kukri@canonical.com,
	pjones@redhat.com, msuchanek@suse.com, mlewando@redhat.com,
	stefanb@linux.ibm.com, avnish@linux.ibm.com, nayna@linux.ibm.com,
	ssrish@linux.ibm.com, Sudhakar Kuppusamy <sudhakar@linux.ibm.com>,
	sridharm@linux.ibm.com,
	Javier Martinez Canillas <javierm@redhat.com>,
	Daniel Kiper <daniel.kiper@oracle.com>
Subject: [PATCH v8 08/20] appended signatures: Parse X.509 certificates
Date: Thu, 21 Aug 2025 13:25:01 +0530	[thread overview]
Message-ID: <20250821075513.82881-9-sudhakar@linux.ibm.com> (raw)
In-Reply-To: <20250821075513.82881-1-sudhakar@linux.ibm.com>

This code allows us to parse:

 - X.509 certificates: at least enough to verify the signatures on the
   PKCS#7 messages. We expect that the certificates embedded in GRUB will
   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.

Signed-off-by: Javier Martinez Canillas <javierm@redhat.com> # EKU support
Reported-by: Michal Suchanek <msuchanek@suse.com> # key usage issue
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 |  33 +
 grub-core/commands/appendedsig/x509.c        | 957 +++++++++++++++++++
 2 files changed, 990 insertions(+)
 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
index cac7fb02c..5ea31f1fd 100644
--- a/grub-core/commands/appendedsig/appendedsig.h
+++ b/grub-core/commands/appendedsig/appendedsig.h
@@ -25,6 +25,24 @@ extern asn1_node grub_gnutls_pkix_asn;
 
 #define MAX_OID_LEN 32
 
+/*
+ * One or more x509 certificates.
+ * We do limited parsing:
+ * extracting only the serial, issuer, subject and RSA public key.
+ */
+struct x509_certificate
+{
+  struct x509_certificate *next;
+  grub_uint8_t *serial;
+  grub_size_t serial_len;
+  char *issuer;
+  grub_size_t issuer_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
 {
@@ -43,6 +61,21 @@ struct pkcs7_signedData
   struct pkcs7_signerInfo *signerInfos;
 };
 
+/*
+ * Import a DER-encoded certificate at 'data', of size 'size'.
+ * Place the results into 'results', which must be already allocated.
+ */
+extern 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.
+ */
+extern 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
diff --git a/grub-core/commands/appendedsig/x509.c b/grub-core/commands/appendedsig/x509.c
new file mode 100644
index 000000000..68b12644a
--- /dev/null
+++ b/grub-core/commands/appendedsig/x509.c
@@ -0,0 +1,957 @@
+/*
+ *  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 <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"
+
+static char asn1_error[ASN1_MAX_ERROR_DESCRIPTION_SIZE];
+
+
+/* RFC 3279 2.3.1  RSA Keys. */
+static const char *rsaEncryption_oid = "1.2.840.113549.1.1.1";
+
+/* RFC 5280 Appendix A. */
+static const char *commonName_oid = "2.5.4.3";
+
+/* RFC 5280 4.2.1.3 Key Usage. */
+static const char *keyUsage_oid = "2.5.29.15";
+
+static const grub_uint8_t digitalSignatureUsage = 0x80;
+
+/* RFC 5280 4.2.1.9 Basic Constraints. */
+static const char *basicConstraints_oid = "2.5.29.19";
+
+/* RFC 5280 4.2.1.12 Extended Key Usage. */
+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
+ *
+ *  The RSA public key MUST be encoded using the ASN.1 type RSAPublicKey:
+ *
+ *     RSAPublicKey ::= SEQUENCE {
+ *        modulus            INTEGER,    -- n
+ *        publicExponent     INTEGER  }  -- e
+ *
+ *  where modulus is the modulus n, and publicExponent is the public
+ *  exponent e.
+ */
+static grub_err_t
+grub_parse_rsa_pubkey (grub_uint8_t *der, int dersize, struct x509_certificate *certificate)
+{
+  int result;
+  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 (grub_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");
+
+  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);
+      goto cleanup;
+    }
+
+  m_data = grub_asn1_allocate_and_read (spk, "modulus", "RSA modulus", &m_size);
+  if (m_data == NULL)
+    {
+      err = grub_errno;
+      goto cleanup;
+    }
+
+  e_data = grub_asn1_allocate_and_read (spk, "publicExponent", "RSA public exponent", &e_size);
+  if (e_data == NULL)
+    {
+      err = grub_errno;
+      goto cleanup_m_data;
+    }
+
+  /*
+   * Convert m, e to mpi
+   *
+   * 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);
+  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);
+      goto cleanup_e_data;
+    }
+
+  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);
+      goto cleanup_m_mpi;
+    }
+
+  grub_free (e_data);
+  grub_free (m_data);
+  asn1_delete_structure (&spk);
+
+  return GRUB_ERR_NONE;
+
+ cleanup_m_mpi:
+  _gcry_mpi_release (certificate->mpis[0]);
+ cleanup_e_data:
+  grub_free (e_data);
+ cleanup_m_data:
+  grub_free (m_data);
+ cleanup:
+  asn1_delete_structure (&spk);
+
+  return err;
+}
+
+/*
+ * RFC 5280:
+ *   SubjectPublicKeyInfo  ::=  SEQUENCE  {
+ *       algorithm            AlgorithmIdentifier,
+ *       subjectPublicKey     BIT STRING  }
+ *
+ * AlgorithmIdentifiers come from RFC 3279, we are not strictly compilant as we
+ * only support RSA Encryption.
+ */
+static grub_err_t
+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";
+  const char *params_name = "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];
+  int params_size = sizeof (params_value);
+  grub_uint8_t *key_data = NULL;
+  int key_size = 0;
+  unsigned int key_type;
+
+  /* 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));
+
+  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
+   * have ASN.1 type NULL for this algorithm identifier.
+   */
+  result = asn1_read_value (asn, params_name, params_value, &params_size);
+  if (result != ASN1_SUCCESS)
+    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");
+
+  /*
+   * RFC 3279 2.3.1:  The DER encoded RSAPublicKey is the value of the BIT
+   * STRING subjectPublicKey.
+   */
+  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));
+  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);
+
+  /* Length is in bits. */
+  key_size = (key_size + 7) / 8;
+
+  key_data = grub_malloc (key_size);
+  if (key_data == NULL)
+    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");
+    }
+
+  key_size = (key_size + 7) / 8;
+  err = grub_parse_rsa_pubkey (key_data, key_size, results);
+  grub_free (key_data);
+
+  return err;
+}
+
+/* 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)
+{
+  asn1_node strasn;
+  int result;
+  char *choice;
+  int choice_size = 0;
+  int tmp_size = 0;
+  grub_err_t err = GRUB_ERR_NONE;
+
+  result = asn1_create_element (grub_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));
+
+  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);
+      goto cleanup;
+    }
+
+  choice = grub_asn1_allocate_and_read (strasn, "", "DirectoryString choice", &choice_size);
+  if (choice == NULL)
+    {
+      err = grub_errno;
+      goto cleanup;
+    }
+
+  if (grub_strncmp ("utf8String", choice, choice_size) == 0)
+    {
+      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;
+        }
+    }
+  else if (grub_strncmp ("printableString", choice, choice_size) == 0)
+    {
+      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 printableString: %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;
+    }
+
+  /* Read size does not include trailing NUL. */
+  tmp_size++;
+
+  *string = grub_malloc (tmp_size);
+  if (*string == NULL)
+    {
+      err = grub_error (GRUB_ERR_OUT_OF_MEMORY,
+                        "cannot allocate memory for DirectoryString contents");
+      goto cleanup_choice;
+    }
+
+  result = asn1_read_value (strasn, choice, *string, &tmp_size);
+  if (result != ASN1_SUCCESS)
+    {
+      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;
+  (*string)[tmp_size] = '\0';
+
+ cleanup_choice:
+  grub_free (choice);
+ cleanup:
+  asn1_delete_structure (&strasn);
+
+  return err;
+}
+
+/*
+ * TBSCertificate  ::=  SEQUENCE  {
+ *       version         [0]  EXPLICIT Version DEFAULT v1,
+ * ...
+ *
+ * Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
+ */
+static grub_err_t
+check_version (asn1_node certificate)
+{
+  int rc;
+  const char *name = "tbsCertificate.version";
+  grub_uint8_t version;
+  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");
+
+  if (version != 0x02)
+    return grub_error (GRUB_ERR_BAD_FILE_TYPE,
+                       "invalid x509 certificate version, expected v3 (0x02), got 0x%02x.",
+                       version);
+
+  return GRUB_ERR_NONE;
+}
+
+/*
+ * This is an X.501 Name, which is complex.
+ *
+ * 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)
+{
+  int seq_components, set_components;
+  int result;
+  int i, j;
+  char *top_path, *set_path, *type_path, *val_path;
+  char type[MAX_OID_LEN];
+  int type_len = sizeof (type);
+  int string_size = 0;
+  char *string_der;
+  grub_err_t err;
+
+  *name = NULL;
+
+  top_path = grub_xasprintf ("%s.rdnSequence", name_path);
+  if (top_path == NULL)
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
+                       "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));
+      goto cleanup;
+    }
+
+  for (i = 1; i <= seq_components; i++)
+    {
+      set_path = grub_xasprintf ("%s.?%d", top_path, i);
+      if (set_path == NULL)
+        {
+          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;
+        }
+      for (j = 1; j <= set_components; j++)
+        {
+          type_path = grub_xasprintf ("%s.?%d.?%d.type", top_path, i, j);
+          if (type_path == NULL)
+            {
+              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;
+            }
+
+          val_path = grub_xasprintf ("%s.?%d.?%d.value", top_path, i, j);
+          if (val_path == NULL)
+            {
+              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 == NULL)
+            {
+              err = grub_errno;
+              goto cleanup_val_path;
+            }
+
+          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 (set_path);
+      if (*name)
+        break;
+    }
+
+  grub_free (top_path);
+
+  return GRUB_ERR_NONE;
+
+ cleanup_string:
+  grub_free (string_der);
+ cleanup_val_path:
+  grub_free (val_path);
+ cleanup_type:
+  grub_free (type_path);
+ cleanup_set:
+  grub_free (set_path);
+ cleanup:
+  grub_free (top_path);
+
+  return err;
+}
+
+/*
+ * 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)
+{
+  asn1_node usageasn;
+  int result;
+  grub_err_t err = GRUB_ERR_NONE;
+  grub_uint8_t usage = 0xff;
+  int usage_size = sizeof (usage_size);
+
+  result = asn1_create_element (grub_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");
+
+  result = asn1_der_decoding2 (&usageasn, value, &value_size,
+                               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);
+      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));
+      goto cleanup;
+    }
+
+  if (!(usage & digitalSignatureUsage))
+    {
+      err = grub_error (GRUB_ERR_BAD_FILE_TYPE,
+                        "key usage (0x%x) missing Digital Signature usage", usage);
+      goto cleanup;
+    }
+
+ cleanup:
+  asn1_delete_structure (&usageasn);
+
+  return err;
+}
+
+/*
+ * BasicConstraints ::= SEQUENCE {
+ *       cA                      BOOLEAN DEFAULT FALSE,
+ *       pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
+ */
+static grub_err_t
+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. */
+  int cA_size = sizeof (cA);
+
+  result = asn1_create_element (grub_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");
+
+  result = asn1_der_decoding2 (&basicasn, value, &value_size,
+                               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);
+      goto cleanup;
+    }
+
+  result = asn1_read_value (basicasn, "cA", cA, &cA_size);
+  if (result == ASN1_ELEMENT_NOT_FOUND)
+    {
+      /* Not present, default is False, so this is OK. */
+      err = GRUB_ERR_NONE;
+      goto cleanup;
+    }
+  else if (result != ASN1_SUCCESS)
+    {
+      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);
+      goto cleanup;
+    }
+
+ cleanup:
+  asn1_delete_structure (&basicasn);
+
+  return err;
+}
+
+/*
+ * 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)
+{
+  asn1_node extendedasn;
+  int result, count, i = 0;
+  grub_err_t err = GRUB_ERR_NONE;
+  char usage[MAX_OID_LEN], name[3];
+  int usage_size = sizeof (usage);
+
+  result = asn1_create_element (grub_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");
+
+  result = asn1_der_decoding2 (&extendedasn, value, &value_size,
+                               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);
+      goto cleanup;
+    }
+
+  /* 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));
+      goto cleanup;
+    }
+
+  for (i = 1; i < count + 1; i++)
+    {
+      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;
+}
+
+/*
+ * Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension  ::=  SEQUENCE  {
+ *      extnID      OBJECT IDENTIFIER,
+ *      critical    BOOLEAN DEFAULT FALSE,
+ *      extnValue   OCTET STRING
+ *                  -- contains the DER encoding of an ASN.1 value
+ *                  -- corresponding to the extension type identified
+ *                  -- by extnID
+ * }
+ *
+ * A certificate must:
+ *  - contain the Digital Signature usage
+ *  - not be a CA
+ *  - 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)
+{
+  int result;
+  int ext, num_extensions = 0;
+  int usage_present = 0, constraints_present = 0, extended_usage_present = 0;
+  char *oid_path, *critical_path, *value_path;
+  char extnID[MAX_OID_LEN];
+  int extnID_size;
+  grub_err_t err;
+  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);
+  if (result != ASN1_SUCCESS)
+    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);
+
+  for (ext = 1; ext <= num_extensions; ext++)
+    {
+      oid_path = grub_xasprintf ("tbsCertificate.extensions.?%d.extnID", ext);
+      if (oid_path == NULL)
+        {
+          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 != 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 == NULL)
+        {
+          err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "error critical path is empty");
+          goto cleanup_oid_path;
+        }
+
+      critical_size = sizeof (critical);
+      result = asn1_read_value (cert, critical_path, critical, &critical_size);
+      if (result == ASN1_ELEMENT_NOT_FOUND)
+        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;
+        }
+
+      value_path = grub_xasprintf ("tbsCertificate.extensions.?%d.extnValue", ext);
+      if (value_path == NULL)
+        {
+          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 == NULL)
+        {
+          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++;
+        }
+      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++;
+        }
+      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++;
+        }
+      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;
+        }
+
+      grub_free (value);
+      grub_free (value_path);
+      grub_free (critical_path);
+      grub_free (oid_path);
+    }
+
+  if (usage_present != 1)
+    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);
+
+  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_ERR_NONE;
+
+ cleanup_value:
+  grub_free (value);
+ cleanup_value_path:
+  grub_free (value_path);
+ cleanup_critical_path:
+  grub_free (critical_path);
+ cleanup_oid_path:
+  grub_free (oid_path);
+
+  return err;
+}
+
+/*
+ * Parse a certificate whose DER-encoded form is in @data, of size @data_size.
+ * Return the results in @results, which must point to an allocated x509 certificate.
+ */
+grub_err_t
+parse_x509_certificate (const void *data, grub_size_t data_size, struct x509_certificate *results)
+{
+  int result = 0;
+  asn1_node cert;
+  grub_err_t err;
+  int size;
+  int tmp_size;
+
+  if (data_size > GRUB_INT_MAX)
+    return grub_error (GRUB_ERR_OUT_OF_RANGE,
+                       "cannot parse a certificate where data size > GRUB_INT_MAX");
+  size = (int) data_size;
+
+  result = asn1_create_element (grub_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));
+
+  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);
+      goto cleanup;
+    }
+
+  /*
+   * TBSCertificate  ::=  SEQUENCE {
+   *     version         [0]  EXPLICIT Version DEFAULT v1
+   */
+  err = check_version (cert);
+  if (err != GRUB_ERR_NONE)
+    goto cleanup;
+
+  /*
+   * serialNumber         CertificateSerialNumber,
+   *
+   * CertificateSerialNumber  ::=  INTEGER
+   */
+  results->serial = grub_asn1_allocate_and_read (cert, "tbsCertificate.serialNumber",
+                                                 "certificate serial number", &tmp_size);
+  if (results->serial == NULL)
+    {
+      err = grub_errno;
+      goto cleanup;
+    }
+  /*
+   * It's safe to cast the signed int to an unsigned here, we know
+   * length is non-negative.
+   */
+  results->serial_len = tmp_size;
+
+  /*
+   * signature            AlgorithmIdentifier,
+   *
+   * We don't load the signature or issuer at the moment,
+   * as we don't attempt x509 verification.
+   */
+
+  /*
+   * validity             Validity,
+   *
+   * Validity ::= SEQUENCE {
+   *     notBefore      Time,
+   *     notAfter       Time }
+   *
+   * We can't validate this reasonably, we have no true time source on several
+   * platforms. For now we do not parse them.
+   */
+
+  /*
+   * issuer              Name,
+   *
+   * This is an X501 name, we parse out just the CN.
+   */
+  err = read_name (cert, "tbsCertificate.issuer", &results->issuer, &results->issuer_len);
+  if (err != GRUB_ERR_NONE)
+    goto cleanup_serial;
+
+  /*
+   * subject              Name,
+   *
+   * This is an X501 name, we parse out just the CN.
+   */
+  err = read_name (cert, "tbsCertificate.subject", &results->subject, &results->subject_len);
+  if (err != GRUB_ERR_NONE)
+    goto cleanup_issuer;
+
+  /*
+   * TBSCertificate  ::=  SEQUENCE  {
+   *    ...
+   *    subjectPublicKeyInfo SubjectPublicKeyInfo,
+   *    ...
+   */
+  err = grub_x509_read_subject_public_key (cert, results);
+  if (err != GRUB_ERR_NONE)
+    goto cleanup_name;
+
+  /*
+   * TBSCertificate  ::=  SEQUENCE  {
+   *    ...
+   *    extensions      [3]  EXPLICIT Extensions OPTIONAL
+   *                         -- If present, version MUST be v3
+   * }
+   */
+  err = verify_extensions (cert);
+  if (err != GRUB_ERR_NONE)
+    goto cleanup_mpis;
+
+  /*
+   * We do not read or check the signature on the certificate:
+   * as discussed we do not try to validate the certificate but trust
+   * it implictly.
+   */
+  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_issuer:
+  grub_free (results->issuer);
+ cleanup_serial:
+  grub_free (results->serial);
+ cleanup:
+  asn1_delete_structure (&cert);
+
+  return err;
+}
+
+/*
+ * 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)
+{
+  grub_free (cert->issuer);
+  grub_free (cert->subject);
+  grub_free (cert->serial);
+  _gcry_mpi_release (cert->mpis[0]);
+  _gcry_mpi_release (cert->mpis[1]);
+}
-- 
2.39.5 (Apple Git-154)


_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

  parent reply	other threads:[~2025-08-21  7:59 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-21  7:54 [PATCH v8 00/20] Appended Signature Secure Boot Support for PowerPC Sudhakar Kuppusamy
2025-08-21  7:54 ` [PATCH v8 01/20] powerpc-ieee1275: Add support for signing GRUB with an appended signature Sudhakar Kuppusamy
2025-08-21  7:54 ` [PATCH v8 02/20] crypto: Move storage for grub_crypto_pk_* to crypto.c Sudhakar Kuppusamy
2025-08-21  7:54 ` [PATCH v8 03/20] pgp: Rename OBJ_TYPE_PUBKEY to OBJ_TYPE_GPG_PUBKEY Sudhakar Kuppusamy
2025-08-21  7:54 ` [PATCH v8 04/20] grub-install: Support embedding x509 certificates Sudhakar Kuppusamy
2025-08-21  7:54 ` [PATCH v8 05/20] appended signatures: Import GNUTLS's ASN.1 description files Sudhakar Kuppusamy
2025-08-21  7:54 ` [PATCH v8 06/20] appended signatures: Parse ASN1 node Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 07/20] appended signatures: Parse PKCS#7 signedData Sudhakar Kuppusamy
2025-08-21  7:55 ` Sudhakar Kuppusamy [this message]
2025-08-21  7:55 ` [PATCH v8 09/20] powerpc_ieee1275: Enter lockdown based on /ibm, secure-boot Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 10/20] appended signatures: Support verifying appended signatures Sudhakar Kuppusamy
2025-08-21 15:23   ` Daniel Kiper
2025-08-22 15:30     ` Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 11/20] powerpc_ieee1275: Read the db and dbx secure boot variables Sudhakar Kuppusamy
2025-08-22 18:53   ` Daniel Kiper
2025-08-23  6:53     ` Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 12/20] appended signatures: Create db and dbx lists Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 13/20] appended signatures: Using db and dbx lists for signature verification Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 14/20] powerpc_ieee1275: Introduce use_static_keys flag Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 15/20] appended signatures: Read default db keys from the ELF Note Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 16/20] appended signatures: Introduce GRUB commands to access db and dbx Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 17/20] appended signatures: Verification tests Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 18/20] docs/grub: Document signing GRUB under UEFI Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 19/20] docs/grub: Document signing GRUB with an appended signature Sudhakar Kuppusamy
2025-08-21  7:55 ` [PATCH v8 20/20] docs/grub: Document " Sudhakar Kuppusamy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250821075513.82881-9-sudhakar@linux.ibm.com \
    --to=sudhakar@linux.ibm.com \
    --cc=avnish@linux.ibm.com \
    --cc=daniel.kiper@oracle.com \
    --cc=dja@axtens.net \
    --cc=grub-devel@gnu.org \
    --cc=jan.setjeeilers@oracle.com \
    --cc=javierm@redhat.com \
    --cc=julian.klode@canonical.com \
    --cc=mate.kukri@canonical.com \
    --cc=mlewando@redhat.com \
    --cc=msuchanek@suse.com \
    --cc=nayna@linux.ibm.com \
    --cc=pjones@redhat.com \
    --cc=sridharm@linux.ibm.com \
    --cc=ssrish@linux.ibm.com \
    --cc=stefanb@linux.ibm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).