You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1528 lines
41 KiB
1528 lines
41 KiB
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
|
From: Daniel Axtens <dja@axtens.net> |
|
Date: Thu, 30 Jul 2020 01:33:46 +1000 |
|
Subject: [PATCH] appended signatures: parse PKCS#7 signedData and X.509 |
|
certificates |
|
|
|
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. |
|
|
|
Signed-off-by: Daniel Axtens <dja@axtens.net> |
|
--- |
|
grub-core/commands/appendedsig/asn1util.c | 102 +++ |
|
grub-core/commands/appendedsig/pkcs7.c | 305 +++++++++ |
|
grub-core/commands/appendedsig/x509.c | 958 +++++++++++++++++++++++++++ |
|
grub-core/commands/appendedsig/appendedsig.h | 110 +++ |
|
4 files changed, 1475 insertions(+) |
|
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 |
|
create mode 100644 grub-core/commands/appendedsig/appendedsig.h |
|
|
|
diff --git a/grub-core/commands/appendedsig/asn1util.c b/grub-core/commands/appendedsig/asn1util.c |
|
new file mode 100644 |
|
index 00000000000..eff095a9df2 |
|
--- /dev/null |
|
+++ b/grub-core/commands/appendedsig/asn1util.c |
|
@@ -0,0 +1,102 @@ |
|
+/* |
|
+ * GRUB -- GRand Unified Bootloader |
|
+ * Copyright (C) 2020 IBM Corporation. |
|
+ * |
|
+ * GRUB is free software: you can redistribute it and/or modify |
|
+ * it under the terms of the GNU General Public License as published by |
|
+ * the Free Software Foundation, either version 3 of the License, or |
|
+ * (at your option) any later version. |
|
+ * |
|
+ * GRUB is distributed in the hope that it will be useful, |
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ * GNU General Public License for more details. |
|
+ * |
|
+ * You should have received a copy of the GNU General Public License |
|
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
|
+ */ |
|
+ |
|
+#include <grub/libtasn1.h> |
|
+#include <grub/types.h> |
|
+#include <grub/err.h> |
|
+#include <grub/mm.h> |
|
+#include <grub/crypto.h> |
|
+#include <grub/gcrypt/gcrypt.h> |
|
+ |
|
+#include "appendedsig.h" |
|
+ |
|
+asn1_node _gnutls_gnutls_asn = ASN1_TYPE_EMPTY; |
|
+asn1_node _gnutls_pkix_asn = ASN1_TYPE_EMPTY; |
|
+ |
|
+extern const ASN1_ARRAY_TYPE gnutls_asn1_tab[]; |
|
+extern const ASN1_ARRAY_TYPE pkix_asn1_tab[]; |
|
+ |
|
+/* |
|
+ * Read a value from an ASN1 node, allocating memory to store it. |
|
+ * |
|
+ * It will work for anything where the size libtasn1 returns is right: |
|
+ * - Integers |
|
+ * - Octet strings |
|
+ * - DER encoding of other structures |
|
+ * It will _not_ work for things where libtasn1 size requires adjustment: |
|
+ * - Strings that require an extra NULL byte at the end |
|
+ * - Bit strings because libtasn1 returns the length in bits, not bytes. |
|
+ * |
|
+ * If the function returns a non-NULL value, the caller must free it. |
|
+ */ |
|
+void * |
|
+grub_asn1_allocate_and_read (asn1_node node, const char *name, |
|
+ const char *friendly_name, int *content_size) |
|
+{ |
|
+ int result; |
|
+ grub_uint8_t *tmpstr = NULL; |
|
+ int tmpstr_size = 0; |
|
+ |
|
+ result = asn1_read_value (node, name, NULL, &tmpstr_size); |
|
+ if (result != ASN1_MEM_ERROR) |
|
+ { |
|
+ grub_snprintf (grub_errmsg, sizeof (grub_errmsg), |
|
+ _ |
|
+ ("Reading size of %s did not return expected status: %s"), |
|
+ friendly_name, asn1_strerror (result)); |
|
+ grub_errno = GRUB_ERR_BAD_FILE_TYPE; |
|
+ return NULL; |
|
+ } |
|
+ |
|
+ tmpstr = grub_malloc (tmpstr_size); |
|
+ if (tmpstr == NULL) |
|
+ { |
|
+ grub_snprintf (grub_errmsg, sizeof (grub_errmsg), |
|
+ "Could not allocate memory to store %s", friendly_name); |
|
+ grub_errno = GRUB_ERR_OUT_OF_MEMORY; |
|
+ return NULL; |
|
+ } |
|
+ |
|
+ result = asn1_read_value (node, name, tmpstr, &tmpstr_size); |
|
+ if (result != ASN1_SUCCESS) |
|
+ { |
|
+ grub_free (tmpstr); |
|
+ grub_snprintf (grub_errmsg, sizeof (grub_errmsg), |
|
+ "Error reading %s: %s", |
|
+ friendly_name, asn1_strerror (result)); |
|
+ grub_errno = GRUB_ERR_BAD_FILE_TYPE; |
|
+ return NULL; |
|
+ } |
|
+ |
|
+ *content_size = tmpstr_size; |
|
+ |
|
+ return tmpstr; |
|
+} |
|
+ |
|
+int |
|
+asn1_init (void) |
|
+{ |
|
+ int res; |
|
+ res = asn1_array2tree (gnutls_asn1_tab, &_gnutls_gnutls_asn, NULL); |
|
+ if (res != ASN1_SUCCESS) |
|
+ { |
|
+ return res; |
|
+ } |
|
+ res = asn1_array2tree (pkix_asn1_tab, &_gnutls_pkix_asn, NULL); |
|
+ return res; |
|
+} |
|
diff --git a/grub-core/commands/appendedsig/pkcs7.c b/grub-core/commands/appendedsig/pkcs7.c |
|
new file mode 100644 |
|
index 00000000000..dc6afe203f7 |
|
--- /dev/null |
|
+++ b/grub-core/commands/appendedsig/pkcs7.c |
|
@@ -0,0 +1,305 @@ |
|
+/* |
|
+ * GRUB -- GRand Unified Bootloader |
|
+ * Copyright (C) 2020 IBM Corporation. |
|
+ * |
|
+ * GRUB is free software: you can redistribute it and/or modify |
|
+ * it under the terms of the GNU General Public License as published by |
|
+ * the Free Software Foundation, either version 3 of the License, or |
|
+ * (at your option) any later version. |
|
+ * |
|
+ * GRUB is distributed in the hope that it will be useful, |
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ * GNU General Public License for more details. |
|
+ * |
|
+ * You should have received a copy of the GNU General Public License |
|
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
|
+ */ |
|
+ |
|
+#include "appendedsig.h" |
|
+#include <grub/misc.h> |
|
+#include <grub/crypto.h> |
|
+#include <grub/gcrypt/gcrypt.h> |
|
+ |
|
+ |
|
+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; |
|
+ 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; |
|
+ |
|
+ 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 != 1) |
|
+ { |
|
+ err = |
|
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
|
+ "Only 1 digest algorithm is supported"); |
|
+ goto cleanup_signed_part; |
|
+ } |
|
+ |
|
+ res = |
|
+ asn1_read_value (signed_part, "digestAlgorithms.?1.algorithm", algo_oid, |
|
+ &algo_oid_size); |
|
+ if (res != ASN1_SUCCESS) |
|
+ { |
|
+ err = |
|
+ grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ "Error reading digest algorithm: %s", |
|
+ asn1_strerror (res)); |
|
+ goto cleanup_signed_part; |
|
+ } |
|
+ |
|
+ if (grub_strncmp (sha512_oid, algo_oid, algo_oid_size) == 0) |
|
+ { |
|
+ msg->hash = grub_crypto_lookup_md_by_name ("sha512"); |
|
+ } |
|
+ else if (grub_strncmp (sha256_oid, algo_oid, algo_oid_size) == 0) |
|
+ { |
|
+ msg->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_signed_part; |
|
+ } |
|
+ |
|
+ if (!msg->hash) |
|
+ { |
|
+ err = |
|
+ grub_error (GRUB_ERR_BAD_SIGNATURE, |
|
+ "Hash algorithm for OID %s not loaded", algo_oid); |
|
+ goto cleanup_signed_part; |
|
+ } |
|
+ |
|
+ /* |
|
+ * 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 signature */ |
|
+ result_buf = |
|
+ grub_asn1_allocate_and_read (signed_part, "signerInfos.?1.signature", |
|
+ "signature data", &result_size); |
|
+ if (!result_buf) |
|
+ { |
|
+ err = grub_errno; |
|
+ goto cleanup_signed_part; |
|
+ } |
|
+ |
|
+ gcry_err = |
|
+ gcry_mpi_scan (&(msg->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 into MPI structure: %d", |
|
+ gcry_err); |
|
+ goto cleanup_result; |
|
+ } |
|
+ |
|
+cleanup_result: |
|
+ grub_free (result_buf); |
|
+cleanup_signed_part: |
|
+ asn1_delete_structure (&signed_part); |
|
+ |
|
+ return err; |
|
+} |
|
+ |
|
+grub_err_t |
|
+parse_pkcs7_signedData (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_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) |
|
+{ |
|
+ gcry_mpi_release (msg->sig_mpi); |
|
+} |
|
diff --git a/grub-core/commands/appendedsig/x509.c b/grub-core/commands/appendedsig/x509.c |
|
new file mode 100644 |
|
index 00000000000..2b38b3670a2 |
|
--- /dev/null |
|
+++ b/grub-core/commands/appendedsig/x509.c |
|
@@ -0,0 +1,958 @@ |
|
+/* |
|
+ * GRUB -- GRand Unified Bootloader |
|
+ * Copyright (C) 2020 IBM Corporation. |
|
+ * |
|
+ * GRUB is free software: you can redistribute it and/or modify |
|
+ * it under the terms of the GNU General Public License as published by |
|
+ * the Free Software Foundation, either version 3 of the License, or |
|
+ * (at your option) any later version. |
|
+ * |
|
+ * GRUB is distributed in the hope that it will be useful, |
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ * GNU General Public License for more details. |
|
+ * |
|
+ * You should have received a copy of the GNU General Public License |
|
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
|
+ */ |
|
+ |
|
+#include <grub/libtasn1.h> |
|
+#include <grub/types.h> |
|
+#include <grub/err.h> |
|
+#include <grub/mm.h> |
|
+#include <grub/crypto.h> |
|
+#include <grub/gcrypt/gcrypt.h> |
|
+ |
|
+#include "appendedsig.h" |
|
+ |
|
+static char asn1_error[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; |
|
+ |
|
+/* |
|
+ * RFC 3279 2.3.1 RSA Keys |
|
+ */ |
|
+const char *rsaEncryption_oid = "1.2.840.113549.1.1.1"; |
|
+ |
|
+/* |
|
+ * RFC 5280 Appendix A |
|
+ */ |
|
+const char *commonName_oid = "2.5.4.3"; |
|
+ |
|
+/* |
|
+ * RFC 5280 4.2.1.3 Key Usage |
|
+ */ |
|
+const char *keyUsage_oid = "2.5.29.15"; |
|
+ |
|
+/* |
|
+ * RFC 5280 4.2.1.9 Basic Constraints |
|
+ */ |
|
+const char *basicConstraints_oid = "2.5.29.19"; |
|
+ |
|
+/* |
|
+ * 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)) |
|
+ { |
|
+ err = |
|
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
|
+ "Only UTF-8 DirectoryStrings are supported, got %s", |
|
+ choice); |
|
+ goto cleanup_choice; |
|
+ } |
|
+ |
|
+ 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; |
|
+ } |
|
+ |
|
+ /* 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, "utf8String", *string, &tmp_size); |
|
+ if (result != ASN1_SUCCESS) |
|
+ { |
|
+ err = |
|
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, |
|
+ "Error reading out UTF-8 string in DirectoryString: %s", |
|
+ 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 = 1; |
|
+ |
|
+ 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; |
|
+ } |
|
+ |
|
+ 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; |
|
+} |
|
+ |
|
+/* |
|
+ * details here |
|
+ */ |
|
+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 = 1; |
|
+ |
|
+ 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; |
|
+ } |
|
+ |
|
+ /* Only the first bit is permitted to be set */ |
|
+ if (usage != 0x80) |
|
+ { |
|
+ 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; |
|
+} |
|
+ |
|
+ |
|
+/* |
|
+ * 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 |
|
+ * } |
|
+ * |
|
+ * We require that a certificate: |
|
+ * - contain the Digital Signature usage only |
|
+ * - not be a CA |
|
+ * - MUST 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; |
|
+ 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 != GRUB_ERR_NONE) |
|
+ { |
|
+ 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 ("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); |
|
+ } |
|
+ 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 |
|
+certificate_import (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_name; |
|
+ |
|
+ |
|
+ /* |
|
+ * 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_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]); |
|
+} |
|
diff --git a/grub-core/commands/appendedsig/appendedsig.h b/grub-core/commands/appendedsig/appendedsig.h |
|
new file mode 100644 |
|
index 00000000000..9792ef3901e |
|
--- /dev/null |
|
+++ b/grub-core/commands/appendedsig/appendedsig.h |
|
@@ -0,0 +1,110 @@ |
|
+/* |
|
+ * GRUB -- GRand Unified Bootloader |
|
+ * Copyright (C) 2020 IBM Corporation. |
|
+ * |
|
+ * GRUB is free software: you can redistribute it and/or modify |
|
+ * it under the terms of the GNU General Public License as published by |
|
+ * the Free Software Foundation, either version 3 of the License, or |
|
+ * (at your option) any later version. |
|
+ * |
|
+ * GRUB is distributed in the hope that it will be useful, |
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ * GNU General Public License for more details. |
|
+ * |
|
+ * You should have received a copy of the GNU General Public License |
|
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
|
+ */ |
|
+ |
|
+#include <grub/crypto.h> |
|
+#include <grub/libtasn1.h> |
|
+ |
|
+extern asn1_node _gnutls_gnutls_asn; |
|
+extern asn1_node _gnutls_pkix_asn; |
|
+ |
|
+#define MAX_OID_LEN 32 |
|
+ |
|
+/* |
|
+ * One or more x509 certificates. |
|
+ * |
|
+ * We do limited parsing: extracting only the serial, CN and RSA public key. |
|
+ */ |
|
+struct x509_certificate |
|
+{ |
|
+ struct x509_certificate *next; |
|
+ |
|
+ grub_uint8_t *serial; |
|
+ grub_size_t serial_len; |
|
+ |
|
+ char *subject; |
|
+ grub_size_t subject_len; |
|
+ |
|
+ /* We only support RSA public keys. This encodes [modulus, publicExponent] */ |
|
+ gcry_mpi_t mpis[2]; |
|
+}; |
|
+ |
|
+/* |
|
+ * A PKCS#7 signedData message. |
|
+ * |
|
+ * We make no attempt to match intelligently, so we don't save any info about |
|
+ * the signer. We also support only 1 signerInfo, so we only store a single |
|
+ * MPI for the signature. |
|
+ */ |
|
+struct pkcs7_signedData |
|
+{ |
|
+ const gcry_md_spec_t *hash; |
|
+ gcry_mpi_t sig_mpi; |
|
+}; |
|
+ |
|
+ |
|
+/* 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 |
|
+certificate_import (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 (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);
|
|
|