From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from list by lists.gnu.org with archive (Exim 4.90_1) id 1lyVnV-0003x7-T4 for mharc-grub-devel@gnu.org; Wed, 30 Jun 2021 04:42:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:54170) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lyVnS-0003jG-F7 for grub-devel@gnu.org; Wed, 30 Jun 2021 04:42:14 -0400 Received: from mail-pg1-x52e.google.com ([2607:f8b0:4864:20::52e]:36649) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lyVnL-0004ZQ-IR for grub-devel@gnu.org; Wed, 30 Jun 2021 04:42:14 -0400 Received: by mail-pg1-x52e.google.com with SMTP id e33so1572542pgm.3 for ; Wed, 30 Jun 2021 01:42:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=axtens.net; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=RCkeabjbG3I6d1IyvWYDERdlGupxRLndU8NzhEW/ZPE=; b=nX6nrd64pLtgxRoe871ZHsM/J1zY44WJj6l9NoQhe+YklQ3jzsqdaYjsDScJeXm+iB uAotP+czG+uicDAmdXJ7FHPvwuoqyfvW9+qNIE+kE+rmDF0BVAOvPgzzuhxplK/BXX2X qAsXtUZNZpj4ST62YuTsE7CBSd1R8djj9NMEg= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=RCkeabjbG3I6d1IyvWYDERdlGupxRLndU8NzhEW/ZPE=; b=IAi+xTYGKvE9S/tBsefxNENcMWlfADqzdFad6Gk+1D8Kb00G989XH/xVZv+uJT/ItI NE8aXvhCYjgrB6y9roEGZv9MaKkG/JchHdQWNOvU2n5ccT9hl4eCW2aC0EIaSXxzk3Og 9v256xznofj5EcdsmTcdZZAHNR2HWHaik7q/8RwA1y4OPEaD+30wEAFHgY/dlGhAQZ7l mUXr/DJvb3xwTyzFeh54lC148He+v0tVSZ0UZwvZvGGwAEr3Dwysb534V09MMQhwfxLt 82Ov5H7TbXxSue651n+qIia778Sz1m3HUGYjAOYb3lEk0Lcx9G5vDOOjH4R9joxMixWj ydMw== X-Gm-Message-State: AOAM533JU2lMCglKWTmyjlNb1Yi4aOai4iW/XDAOgAmTXRGYi38yTzJd FuMs5S994AWQumtHRY8rJ9lVWx8r1Am6/A== X-Google-Smtp-Source: ABdhPJzVmsIaXXasZjfV9/U0htclBak6hgY17bCNprJZI8STS5FRYMysyj3mtfvdxDsR6zYvjYjTFg== X-Received: by 2002:a62:5257:0:b029:301:b5c1:66ae with SMTP id g84-20020a6252570000b0290301b5c166aemr34712439pfb.65.1625042525712; Wed, 30 Jun 2021 01:42:05 -0700 (PDT) Received: from localhost ([203.206.29.204]) by smtp.gmail.com with ESMTPSA id y16sm13931789pfe.70.2021.06.30.01.42.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Jun 2021 01:42:05 -0700 (PDT) From: Daniel Axtens To: grub-devel@gnu.org Cc: rashmica.g@gmail.com, alastair@d-silva.org, nayna@linux.ibm.com, Daniel Axtens , Javier Martinez Canillas Subject: [PATCH v2 18/22] appended signatures: parse PKCS#7 signedData and X.509 certificates Date: Wed, 30 Jun 2021 18:40:27 +1000 Message-Id: <20210630084031.2663622-19-dja@axtens.net> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210630084031.2663622-1-dja@axtens.net> References: <20210630084031.2663622-1-dja@axtens.net> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=2607:f8b0:4864:20::52e; envelope-from=dja@axtens.net; helo=mail-pg1-x52e.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, 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: Wed, 30 Jun 2021 08:42:14 -0000 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 --- 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); + + 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); + + /* 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..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"; + +/* + * 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. + */ + 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.30.2