From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from list by lists.gnu.org with archive (Exim 4.90_1) id 1m5bLB-0003b4-DB for mharc-grub-devel@gnu.org; Mon, 19 Jul 2021 18:02:21 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:37844) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1m5bL9-0003Yt-MM for grub-devel@gnu.org; Mon, 19 Jul 2021 18:02:19 -0400 Received: from mx0a-001b2d01.pphosted.com ([148.163.156.1]:8650) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1m5bL4-00018G-E8 for grub-devel@gnu.org; Mon, 19 Jul 2021 18:02:19 -0400 Received: from pps.filterd (m0187473.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.16.0.43/8.16.0.43) with SMTP id 16JLXu8d132945; Mon, 19 Jul 2021 18:02:11 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=subject : to : cc : references : from : message-id : date : in-reply-to : content-type : content-transfer-encoding : mime-version; s=pp1; bh=w9TjBMV9TG2RRIy/IFWpRr2TrNSyUgjl8/38rNj0T10=; b=GtMMH5HJVtUs53SzkfKCljtkkoCK5ndE/bG8ShvOy5g+W9XKFtdNoyMdKmsjc17KUZVa VgNFdWNfEosAuOVQXgJGEqHXTRQ7YGxNfDgz/F/tigL9dMTNyEV+29iEytqk76c+sMxQ wDqZT15MLaIqPDytM6y8SFwYQseu0LBKk7CS2alb2myB9ChBUp1xuWFKGXckl71erHh4 qU76TlnqtorMPOkiQ8dW5XUmIEx2vmgMJCtJfM0nK3EaBarWDL3IlDn6yCYQ2NVqOEhP VAgnT8/zQ80ZXJfzWgRVKBZ+i7HA+AnWZ3E9wvqkW2dlHmbo9oGyCGAVhlZJ88zcovZL rw== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com with ESMTP id 39wh6drxfn-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 19 Jul 2021 18:02:11 -0400 Received: from m0187473.ppops.net (m0187473.ppops.net [127.0.0.1]) by pps.reinject (8.16.0.43/8.16.0.43) with SMTP id 16JLYlZT138652; Mon, 19 Jul 2021 18:02:10 -0400 Received: from ppma02wdc.us.ibm.com (aa.5b.37a9.ip4.static.sl-reverse.com [169.55.91.170]) by mx0a-001b2d01.pphosted.com with ESMTP id 39wh6drxev-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 19 Jul 2021 18:02:10 -0400 Received: from pps.filterd (ppma02wdc.us.ibm.com [127.0.0.1]) by ppma02wdc.us.ibm.com (8.16.1.2/8.16.1.2) with SMTP id 16JLqqin016836; Mon, 19 Jul 2021 22:02:09 GMT Received: from b01cxnp22033.gho.pok.ibm.com (b01cxnp22033.gho.pok.ibm.com [9.57.198.23]) by ppma02wdc.us.ibm.com with ESMTP id 39vvw712py-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 19 Jul 2021 22:02:09 +0000 Received: from b01ledav002.gho.pok.ibm.com (b01ledav002.gho.pok.ibm.com [9.57.199.107]) by b01cxnp22033.gho.pok.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 16JM28EC34472346 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Mon, 19 Jul 2021 22:02:08 GMT Received: from b01ledav002.gho.pok.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 82937124058; Mon, 19 Jul 2021 22:02:08 +0000 (GMT) Received: from b01ledav002.gho.pok.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 3BAFD124054; Mon, 19 Jul 2021 22:02:08 +0000 (GMT) Received: from [9.47.158.152] (unknown [9.47.158.152]) by b01ledav002.gho.pok.ibm.com (Postfix) with ESMTP; Mon, 19 Jul 2021 22:02:08 +0000 (GMT) Subject: Re: [PATCH v2 18/22] appended signatures: parse PKCS#7 signedData and X.509 certificates To: The development of GNU GRUB , Daniel Axtens Cc: rashmica.g@gmail.com, alastair@d-silva.org, nayna@linux.ibm.com, Javier Martinez Canillas References: <20210630084031.2663622-1-dja@axtens.net> <20210630084031.2663622-19-dja@axtens.net> From: Stefan Berger Message-ID: <34b59279-d426-8150-da01-7b420af66c7e@linux.ibm.com> Date: Mon, 19 Jul 2021 18:02:08 -0400 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.11.0 In-Reply-To: <20210630084031.2663622-19-dja@axtens.net> Content-Type: text/plain; charset=utf-8; format=flowed Content-Language: en-US X-TM-AS-GCONF: 00 X-Proofpoint-ORIG-GUID: WStD0Sj-htn7PTo_-OBFJWDS_1cWh4qI X-Proofpoint-GUID: vq_hL4zKf1nPTebTG3xbPvjPfqj3pcEk Content-Transfer-Encoding: 7bit X-Proofpoint-UnRewURL: 0 URL was un-rewritten MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.391, 18.0.790 definitions=2021-07-19_11:2021-07-19, 2021-07-19 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 malwarescore=0 mlxlogscore=999 clxscore=1015 priorityscore=1501 impostorscore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 phishscore=0 mlxscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2104190000 definitions=main-2107190123 Received-SPF: pass client-ip=148.163.156.1; envelope-from=stefanb@linux.ibm.com; helo=mx0a-001b2d01.pphosted.com X-Spam_score_int: -19 X-Spam_score: -2.0 X-Spam_bar: -- X-Spam_report: (-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, NICE_REPLY_A=-0.001, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: grub-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: The development of GNU GRUB List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 19 Jul 2021 22:02:19 -0000 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 # EKU support > Signed-off-by: Daniel Axtens 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 > --- > 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 . > + */ > + > +#include > +#include > + > +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 . > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 . > + */ > + > +#include "appendedsig.h" > +#include > +#include > +#include > +#include > + > +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"; > + > +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); > + 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. > + > + 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. > + > + /* 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++) Nit: probably empty line after variable declaration > + { > + 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 . > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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'? > + > +/* > + * 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 = ASN1_TYPE_EMPTY; > + 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, ¶ms_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 only permit 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, "Unexpected Key Usage value: %x", > + 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 and it must be a > + * codeSigning usage. Is this comment correct? It looks like your code requires one EKU. > + */ > + 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]); > +}