grub-devel
[Top][All Lists]
Advanced

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

Re: [PATCH v2 18/22] appended signatures: parse PKCS#7 signedData and X.


From: Daniel Axtens
Subject: Re: [PATCH v2 18/22] appended signatures: parse PKCS#7 signedData and X.509 certificates
Date: Thu, 21 Apr 2022 16:36:04 +1000

Stefan Berger <stefanb@linux.ibm.com> writes:

> On 6/30/21 4:40 AM, 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.
>>
>>   - X.509 certificates support the Extended Key Usage extension and handle
>>     it by verifying that the certificate has a single purpose, that is code
>>     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
>> Signed-off-by: Daniel Axtens <dja@axtens.net>
>
> A few comments below.
>
>
>>
>> ---
>>
>> 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.
>>
>> revert
>>
>> Signed-off-by: Daniel Axtens <dja@axtens.net>
>> ---
>>   grub-core/commands/appendedsig/appendedsig.h |  118 ++
>>   grub-core/commands/appendedsig/asn1util.c    |  103 ++
>>   grub-core/commands/appendedsig/pkcs7.c       |  509 +++++++++
>>   grub-core/commands/appendedsig/x509.c        | 1079 ++++++++++++++++++
>>   4 files changed, 1809 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..327d68ddb1b7
>> --- /dev/null
>> +++ b/grub-core/commands/appendedsig/appendedsig.h
>> @@ -0,0 +1,118 @@
>> +/*
>> + *  GRUB  --  GRand Unified Bootloader
>> + *  Copyright (C) 2020  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..6b508222a081
>> --- /dev/null
>> +++ b/grub-core/commands/appendedsig/asn1util.c
>> @@ -0,0 +1,103 @@
>> +/*
>> + *  GRUB  --  GRand Unified Bootloader
>> + *  Copyright (C) 2020  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 = ASN1_TYPE_EMPTY;
>> +asn1_node _gnutls_pkix_asn = ASN1_TYPE_EMPTY;
>> +
>> +extern const ASN1_ARRAY_TYPE gnutls_asn1_tab[];
>> +extern const ASN1_ARRAY_TYPE 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..845f58a53e83
>> --- /dev/null
>> +++ b/grub-core/commands/appendedsig/pkcs7.c
>> @@ -0,0 +1,509 @@
>> +/*
>> + *  GRUB  --  GRand Unified Bootloader
>> + *  Copyright (C) 2020  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
>> + */
>> +const char *signedData_oid = "1.2.840.113549.1.7.2";
>> +
>> +/*
>> + * RFC 4055 s 2.1
>> + */
>> +const char *sha256_oid = "2.16.840.1.101.3.4.2.1";
>> +const char *sha512_oid = "2.16.840.1.101.3.4.2.3";

Made these static too.

>> +      goto cleanup_signerInfos;
>> +    }
>> +
>> +      result_buf =
>> +    grub_asn1_allocate_and_read (signed_part, si_sig_path,
>> +                                 "signature data", &result_size);
>> +      if (!result_buf)
>> +    {
>> +      err = grub_errno;
>> +      grub_free (si_sig_path);
>> +      goto cleanup_signerInfos;
>> +    }
>> +      grub_free (si_sig_path);
>
> Nit: You could probably move this before the if statement so you only 
> have to write this once.
>

Yep, fixed.
>
>> +
>> +      gcry_err =
>> +    gcry_mpi_scan (&(msg->signerInfos[i].sig_mpi), GCRYMPI_FMT_USG,
>> +                   result_buf, result_size, NULL);
>> +      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);
>> +      grub_free (result_buf);
>> +      goto cleanup_signerInfos;
>> +    }
>> +
>> +      grub_free (result_buf);
>
> Same with this one.
>
>
Fixed.

>> +
>> +      /* use msg->signerInfo_count to track fully populated signerInfos so 
>> we
>> +         know how many we need to clean up */
>> +      msg->signerInfo_count++;
>> +    }

>> +/*
>> + * 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++)
>
>
> Nit: probably empty line after variable declaration

Done
>
>
>> +    {
>> +      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..a17a46102872
>> --- /dev/null
>> +++ b/grub-core/commands/appendedsig/x509.c
>> @@ -0,0 +1,1079 @@
>> +/*
>> + *  GRUB  --  GRand Unified Bootloader
>> + *  Copyright (C) 2020  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
>> + */
>> +const char *rsaEncryption_oid = "1.2.840.113549.1.1.1";
>> +
>> +/*
>> + * RFC 5280 Appendix A
>> + */
>> +const char *commonName_oid = "2.5.4.3";
>> +
>> +/*
>> + * RFC 5280 4.2.1.3 Key Usage
>> + */
>> +const char *keyUsage_oid = "2.5.29.15";
>> +
>> +const grub_uint8_t digitalSignatureUsage = 0x80;
>> +
>> +/*
>> + * RFC 5280 4.2.1.9 Basic Constraints
>> + */
>> +const char *basicConstraints_oid = "2.5.29.19";
>> +
>> +/*
>> + * RFC 5280 4.2.1.12 Extended Key Usage
>> + */
>> +const char *extendedKeyUsage_oid = "2.5.29.37";
>> +const char *codeSigningUsage_oid = "1.3.6.1.5.5.7.3.3";
>
>
> Should they be visible to other modules or only used here and can be 
> 'static'?
>
Indeed you are right. Marked as static.


>> +  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 and it must be a
>> +   * codeSigning usage.
>
>
> Is this comment correct? It looks like your code requires one EKU.
>

That function will only be called if we have an EKU extension in
verify_extensions. I will clarify the comment: what it should be saying
is if we have an EKU extension, there must be at least 1 usage.

Thank you for the detailed review, I'm sorry it's taken me so long to respond!

Kind regards,
Daniel



reply via email to

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