grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v3 15/19] appended signatures: parse PKCS#7 signedData and X.


From: Michal Suchánek
Subject: Re: [PATCH v3 15/19] appended signatures: parse PKCS#7 signedData and X.509 certificates
Date: Thu, 21 Apr 2022 09:57:50 +0200
User-agent: Mutt/1.10.1 (2018-07-13)

Hello,

On Thu, Apr 21, 2022 at 05:47:10PM +1000, Daniel Axtens wrote:
> 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
>    be leaf certificates, not CA certificates. The parser enforces this.

Doesn't grub support CA certificates for EFI?

Why limit to leaf certificates here?

If this is technical limitation of the code in question could you fail
the build when CA certificate is used rather than crashing the
bootloader when it boots?

> 
>  - X.509 certificates support the Extended Key Usage extension and handle
>    it by verifying that the certificate has a single purpose, that is code
                                                ^^^^^^^^^^^^^^
This should be updated I suppose.

Thanks

Michal

>    signing. This is required because Red Hat certificates have both Key
>    Usage and Extended Key Usage extensions present.
> 
> 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>
> 
> ---
> 
> v3 changes:
>   - fix nits from Stefan
>   - correct copyright headers
>   - fixes for libtasn1-4.18.0
>   - Roll in a fix for a bug reported by Michal:
> 
> Currently the x509 certificate parser for appended signature
> verification requires that the certificate have the Digitial Signature
> key usage and _only_ the Digitial Signature use. This is overly strict
> and becomes policy enforcement rather than a security property.
> 
> Require that the Digitial Signature usage is present, but do not
> require that it is the only usage present.
> 
> v2 changes:
> 
>  - Handle the Extended Key Usage extension
>  - Fix 2 leaks in x509 cert parsing
>  - Improve x509 parser function name
>  - Constify the data parameter in parser signatures
>  - Support multiple signers in a pkcs7 message. Accept any passing sig.
>  - Allow padding after a pkcs7 message in an appended signature, required
>     to support my model for signers separated in time.
>  - Fix a test that used GRUB_ERR_NONE rather than ASN1_SUCCESS. They're
>     both 0 so no harm was done, but better to be correct.
>  - Various code and comment cleanups.
> 
> Thanks to Nayna Jain and Stefan Berger for their reviews.
> ---
>  grub-core/commands/appendedsig/appendedsig.h |  119 ++
>  grub-core/commands/appendedsig/asn1util.c    |  104 ++
>  grub-core/commands/appendedsig/pkcs7.c       |  512 +++++++++
>  grub-core/commands/appendedsig/x509.c        | 1082 ++++++++++++++++++
>  4 files changed, 1817 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 000000000000..d3dfd4616862
> --- /dev/null
> +++ b/grub-core/commands/appendedsig/appendedsig.h
> @@ -0,0 +1,119 @@
> +/*
> + *  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/crypto.h>
> +#include <grub/libtasn1.h>
> +
> +extern asn1_node _gnutls_gnutls_asn;
> +extern asn1_node _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 000000000000..f373b5fdcd68
> --- /dev/null
> +++ b/grub-core/commands/appendedsig/asn1util.c
> @@ -0,0 +1,104 @@
> +/*
> + *  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;
> +
> +extern const asn1_static_node gnutls_asn1_tab[];
> +extern const asn1_static_node pkix_asn1_tab[];
> +
> +/*
> + * 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"),
> +                  friendly_name, asn1_strerror (result));
> +      grub_errno = GRUB_ERR_BAD_FILE_TYPE;
> +      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;
> +      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;
> +      return NULL;
> +    }
> +
> +  *content_size = tmpstr_size;
> +
> +  return tmpstr;
> +}
> +
> +int
> +asn1_init (void)
> +{
> +  int res;
> +  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 000000000000..e12c47454376
> --- /dev/null
> +++ b/grub-core/commands/appendedsig/pkcs7.c
> @@ -0,0 +1,512 @@
> +/*
> + *  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
> + */
> +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 = 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.");
> +    }
> +
> +  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 */
> +  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 are 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 = 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)
> +    {
> +      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)
> +     {
> +       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)
> +         {
> +           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)
> +         {
> +           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)
> +     {
> +       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)
> +     {
> +       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)
> +     {
> +       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 && !sha512_in_si)
> +    {
> +      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 && !sha256_in_si)
> +    {
> +      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 > 
> INT_MAX");
> +  size = (int) data_size;
> +
> +  res = asn1_create_element (_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)
> +    {
> +      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);
> +}
> diff --git a/grub-core/commands/appendedsig/x509.c 
> b/grub-core/commands/appendedsig/x509.c
> new file mode 100644
> index 000000000000..1aea411905d1
> --- /dev/null
> +++ b/grub-core/commands/appendedsig/x509.c
> @@ -0,0 +1,1082 @@
> +/*
> + *  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"
> +
> +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 (_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)
> +    {
> +      err = grub_errno;
> +      goto cleanup;
> +    }
> +
> +  e_data =
> +    grub_asn1_allocate_and_read (spk, "publicExponent", "RSA public 
> exponent",
> +                              &e_size);
> +  if (!e_data)
> +    {
> +      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)
> +    {
> +      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 (_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)
> +    {
> +      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 UTF-8 string: %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 null */
> +  tmp_size++;
> +
> +  *string = grub_malloc (tmp_size);
> +  if (!*string)
> +    {
> +      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);
> +      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)
> +    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)
> +     {
> +       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)
> +         {
> +           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)
> +         {
> +           err =
> +             grub_error (GRUB_ERR_OUT_OF_MEMORY,
> +                         "Could not allocate memory for %s name component 
> value path",
> +                         name_path);
> +           goto cleanup_set;
> +         }
> +
> +       string_der =
> +         grub_asn1_allocate_and_read (asn, val_path, name_path,
> +                                      &string_size);
> +       if (!string_der)
> +         {
> +           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 (_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 (_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;
> +}
> +
> +/*
> + * 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;
> +  grub_err_t err = GRUB_ERR_NONE;
> +  char usage[MAX_OID_LEN];
> +  int usage_size = sizeof (usage);
> +
> +  result =
> +    asn1_create_element (_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, there must be exactly 1 usage and it must be a
> +   * codeSigning usage. (If we get to this point, we are parsing an EKU
> +   * extension and therefore must have a usage. The code that makes having an
> +   * EKU extension optional is in verify_extensions.)
> +   */
> +  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;
> +    }
> +
> +  if (count != 1)
> +    {
> +      err =
> +     grub_error (GRUB_ERR_BAD_FILE_TYPE,
> +                 "Unexpected number of Extended Key Usages: %d, 1 expected",
> +                 count);
> +      goto cleanup;
> +    }
> +
> +  result = asn1_read_value (extendedasn, "?1", 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)
> +    {
> +      err =
> +     grub_error (GRUB_ERR_BAD_FILE_TYPE,
> +                 "Unexpected Extended Key Usage OID, got: %s", usage);
> +      goto cleanup;
> +    }
> +
> +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 only
> + *  - not be a CA
> + *  - contain no extended usages, or only a 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);
> +
> +      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);
> +      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);
> +      value =
> +     grub_asn1_allocate_and_read (cert, value_path,
> +                                  "certificate extension value",
> +                                  &value_size);
> +      if (!value)
> +     {
> +       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 > INT_MAX");
> +  size = (int) data_size;
> +
> +  result = asn1_create_element (_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)
> +    {
> +      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.
> +   */
> +
> +  /*
> +   * issuer               Name,
> +   *
> +   * The RFC only requires the serial number to be unique within
> +   * issuers, so to avoid ambiguity we _technically_ ought to make
> +   * this available.
> +   */
> +
> +  /*
> +   * 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.
> +   */
> +
> +  /*
> +   * 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_serial;
> +
> +  /*
> +   * 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_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->subject);
> +  grub_free (cert->serial);
> +  gcry_mpi_release (cert->mpis[0]);
> +  gcry_mpi_release (cert->mpis[1]);
> +}
> -- 
> 2.32.0
> 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]