From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from list by lists.gnu.org with archive (Exim 4.90_1) id 1m5ec3-0002EB-KG for mharc-grub-devel@gnu.org; Mon, 19 Jul 2021 21:31:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33942) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1m5ec1-0002BE-QJ for grub-devel@gnu.org; Mon, 19 Jul 2021 21:31:57 -0400 Received: from mx0b-001b2d01.pphosted.com ([148.163.158.5]:42410 helo=mx0a-001b2d01.pphosted.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1m5eby-0003ko-9C for grub-devel@gnu.org; Mon, 19 Jul 2021 21:31:57 -0400 Received: from pps.filterd (m0098413.ppops.net [127.0.0.1]) by mx0b-001b2d01.pphosted.com (8.16.0.43/8.16.0.43) with SMTP id 16K14fbf057671; Mon, 19 Jul 2021 21:31:51 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=subject : to : cc : references : from : message-id : date : in-reply-to : content-type : content-transfer-encoding : mime-version; s=pp1; bh=zxUlehw9/XgQFm0MRtALwfu6AiXY0IcbPT9E9SgX/Xo=; b=jvC8vq5NELw7dG5OERXiNFub3YNyi+A9ij4tWNP0dldsQ2DlbEwPFt51r+9u9wrB4/3f GLpqHStTCnBhtsffjQVvP1QuG3s9TngliOkaOofpscp+z6Qgg0JkVKRkOOS6wrOFwcGu AfcgTnORsnI7KnIvKDsBCMAgWGNGFPSEv+Kqs/JSaWLtJHtZQ9ybQH6cSFwRMpbB13XN HTqHK6lZoTyoHPKB5UGQInYftwysw/WFRpVUEqitqrZsFxnu6xVyBEnfywoTMIc1xugE Pjxpi40Ou0/l2kohTHXKhnS+zOquVP1/76e4qb8Ly55bj9y4TxH9rGQv5vGf+Nu6tesZ wQ== Received: from pps.reinject (localhost [127.0.0.1]) by mx0b-001b2d01.pphosted.com with ESMTP id 39wmgt0fxw-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 19 Jul 2021 21:31:51 -0400 Received: from m0098413.ppops.net (m0098413.ppops.net [127.0.0.1]) by pps.reinject (8.16.0.43/8.16.0.43) with SMTP id 16K15wVv064154; Mon, 19 Jul 2021 21:31:51 -0400 Received: from ppma02dal.us.ibm.com (a.bd.3ea9.ip4.static.sl-reverse.com [169.62.189.10]) by mx0b-001b2d01.pphosted.com with ESMTP id 39wmgt0fxn-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 19 Jul 2021 21:31:51 -0400 Received: from pps.filterd (ppma02dal.us.ibm.com [127.0.0.1]) by ppma02dal.us.ibm.com (8.16.1.2/8.16.1.2) with SMTP id 16K1R5XN010346; Tue, 20 Jul 2021 01:31:50 GMT Received: from b03cxnp08025.gho.boulder.ibm.com (b03cxnp08025.gho.boulder.ibm.com [9.17.130.17]) by ppma02dal.us.ibm.com with ESMTP id 39vuk4ksjq-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 20 Jul 2021 01:31:50 +0000 Received: from b03ledav006.gho.boulder.ibm.com (b03ledav006.gho.boulder.ibm.com [9.17.130.237]) by b03cxnp08025.gho.boulder.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 16K1VnpA47645146 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Tue, 20 Jul 2021 01:31:49 GMT Received: from b03ledav006.gho.boulder.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 19F84C6059; Tue, 20 Jul 2021 01:31:49 +0000 (GMT) Received: from b03ledav006.gho.boulder.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id A22FAC6057; Tue, 20 Jul 2021 01:31:48 +0000 (GMT) Received: from [9.47.158.152] (unknown [9.47.158.152]) by b03ledav006.gho.boulder.ibm.com (Postfix) with ESMTP; Tue, 20 Jul 2021 01:31:48 +0000 (GMT) Subject: Re: [PATCH v2 19/22] appended signatures: support verifying appended signatures To: The development of GNU GRUB , Daniel Axtens Cc: rashmica.g@gmail.com, alastair@d-silva.org, nayna@linux.ibm.com References: <20210630084031.2663622-1-dja@axtens.net> <20210630084031.2663622-20-dja@axtens.net> From: Stefan Berger Message-ID: <15efc318-bdcd-5a49-7844-beb92cc3e1c3@linux.ibm.com> Date: Mon, 19 Jul 2021 21:31:48 -0400 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.11.0 In-Reply-To: <20210630084031.2663622-20-dja@axtens.net> Content-Type: text/plain; charset=utf-8; format=flowed Content-Language: en-US X-TM-AS-GCONF: 00 X-Proofpoint-GUID: ucYxkM9DOsYotVf8XRAvHoWl_6eg-z8V X-Proofpoint-ORIG-GUID: MUkV3V4OvvkNQm3gRrEPXeR4KV8Bgrqy Content-Transfer-Encoding: 7bit X-Proofpoint-UnRewURL: 0 URL was un-rewritten MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.391, 18.0.790 definitions=2021-07-19_12:2021-07-19, 2021-07-19 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 phishscore=0 impostorscore=0 priorityscore=1501 lowpriorityscore=0 mlxscore=0 adultscore=0 malwarescore=0 mlxlogscore=999 clxscore=1015 bulkscore=0 spamscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2104190000 definitions=main-2107200005 Received-SPF: pass client-ip=148.163.158.5; envelope-from=stefanb@linux.ibm.com; helo=mx0a-001b2d01.pphosted.com X-Spam_score_int: -19 X-Spam_score: -2.0 X-Spam_bar: -- X-Spam_report: (-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, NICE_REPLY_A=-0.001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: grub-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: The development of GNU GRUB List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 20 Jul 2021 01:31:58 -0000 On 6/30/21 4:40 AM, Daniel Axtens wrote: > Building on the parsers and the ability to embed x509 certificates, as > well as the existing gcrypt functionality, add a module for verifying > appended signatures. > > This includes a verifier that requires that Linux kernels and grub modules > have appended signatures, and commands to manage the list of trusted > certificates for verification. > > Verification must be enabled by setting check_appended_signatures. If > GRUB is locked down when the module is loaded, verification will be > enabled and locked automatically. > > As with the PGP verifier, it is not a complete secure-boot solution: > other mechanisms, such as a password or lockdown, must be used to ensure > that a user cannot drop to the grub shell and disable verification. > > Signed-off-by: Daniel Axtens > > --- > > v2 changes: > > - Improve x509 parser function name > - Constify data parameters in function signatures > - Support multiple signers > - Use an enum rather than 0, 1 and 2 for various signature > enforcement states. > - Spin out a file reading function that was duplicated. > - Fix some code style and clarity issues. > > Thanks to Nayna Jain and Stefan Berger for their reviews. > > Revert "fixups so that you can build pkcs7 without posixly" > > This reverts commit 676a19fa8a7f9cca7a58ce2180110f609185b2bd. > --- > grub-core/Makefile.core.def | 14 + > grub-core/commands/appendedsig/appendedsig.c | 669 +++++++++++++++++++ > include/grub/file.h | 2 + > 3 files changed, 685 insertions(+) > create mode 100644 grub-core/commands/appendedsig/appendedsig.c > > diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def > index d63f216111ca..beeedd8ca356 100644 > --- a/grub-core/Makefile.core.def > +++ b/grub-core/Makefile.core.def > @@ -946,6 +946,20 @@ module = { > cppflags = '-I$(srcdir)/lib/posix_wrap'; > }; > > +module = { > + name = appendedsig; > + common = commands/appendedsig/appendedsig.c; > + common = commands/appendedsig/x509.c; > + common = commands/appendedsig/pkcs7.c; > + common = commands/appendedsig/asn1util.c; > + common = commands/appendedsig/gnutls_asn1_tab.c; > + common = commands/appendedsig/pkix_asn1_tab.c; > + > + // posix wrapper required for gcry to get sys/types.h > + cflags = '$(CFLAGS_POSIX)'; > + cppflags = '-I$(srcdir)/lib/posix_wrap'; > +}; > + > module = { > name = hdparm; > common = commands/hdparm.c; > diff --git a/grub-core/commands/appendedsig/appendedsig.c b/grub-core/commands/appendedsig/appendedsig.c > new file mode 100644 > index 000000000000..e63ad1ac645a > --- /dev/null > +++ b/grub-core/commands/appendedsig/appendedsig.c > @@ -0,0 +1,669 @@ > +/* > + * GRUB -- GRand Unified Bootloader > + * Copyright (C) 2020-2021 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "appendedsig.h" > + > +GRUB_MOD_LICENSE ("GPLv3+"); > + > +const char magic[] = "~Module signature appended~\n"; > + > +/* > + * This structure is extracted from scripts/sign-file.c in the linux kernel > + * source. It was licensed as LGPLv2.1+, which is GPLv3+ compatible. > + */ > +struct module_signature > +{ > + grub_uint8_t algo; /* Public-key crypto algorithm [0] */ > + grub_uint8_t hash; /* Digest algorithm [0] */ > + grub_uint8_t id_type; /* Key identifier type [PKEY_ID_PKCS7] */ > + grub_uint8_t signer_len; /* Length of signer's name [0] */ > + grub_uint8_t key_id_len; /* Length of key identifier [0] */ > + grub_uint8_t __pad[3]; > + grub_uint32_t sig_len; /* Length of signature data */ > +} GRUB_PACKED; > + > + > +/* This represents an entire, parsed, appended signature */ > +struct grub_appended_signature > +{ > + grub_size_t signature_len; /* Length of PKCS#7 data + > + * metadata + magic */ > + > + struct module_signature sig_metadata; /* Module signature metadata */ > + struct pkcs7_signedData pkcs7; /* Parsed PKCS#7 data */ > +}; > + > +/* Trusted certificates for verifying appended signatures */ > +struct x509_certificate *grub_trusted_key; > + > +/* > + * Force gcry_rsa to be a module dependency. > + * > + * If we use grub_crypto_pk_rsa, then then the gcry_rsa module won't be built > + * in if you add 'appendedsig' to grub-install --modules. You would need to > + * add 'gcry_rsa' too. That's confusing and seems suboptimal, especially when > + * we only support RSA. > + * > + * Dynamic loading also causes some concerns. We can't load gcry_rsa from the > + * the filesystem after we install the verifier - we won't be able to verify > + * it without having it already present. We also shouldn't load it before we > + * install the verifier, because that would mean it wouldn't be verified - an > + * attacker could insert any code they wanted into the module. > + * > + * So instead, reference the internal symbol from gcry_rsa. That creates a > + * direct dependency on gcry_rsa, so it will be built in when this module > + * is built in. Being built in (assuming the core image is itself signed!) > + * also resolves our concerns about loading from the filesystem. > + */ > +extern gcry_pk_spec_t _gcry_pubkey_spec_rsa; > + > +static enum > +{ check_sigs_no = 0, nit: newline after '{' > + check_sigs_enforce = 1, > + check_sigs_forced = 2 > +} check_sigs = check_sigs_no; What does 'forced' mean? > + > +static const char * > +grub_env_read_sec (struct grub_env_var *var __attribute__((unused)), > + const char *val __attribute__((unused))) > +{ > + if (check_sigs == check_sigs_forced) > + return "forced"; > + else if (check_sigs == check_sigs_enforce) > + return "enforce"; > + else > + return "no"; > +} > + > +static char * > +grub_env_write_sec (struct grub_env_var *var __attribute__((unused)), > + const char *val) > +{ > + /* Do not allow the value to be changed if set to forced */ > + if (check_sigs == check_sigs_forced) > + return grub_strdup ("forced"); > + > + if ((*val == '2') || (*val == 'f')) > + check_sigs = check_sigs_forced; > + else if ((*val == '1') || (*val == 'e')) > + check_sigs = check_sigs_enforce; > + else if ((*val == '0') || (*val == 'n')) > + check_sigs = check_sigs_no; > + > + return grub_strdup (grub_env_read_sec (NULL, NULL)); > +} > + > +static grub_err_t > +file_read_all (grub_file_t file, grub_uint8_t **buf, grub_size_t *len) > +{ > + grub_off_t full_file_size; > + grub_size_t file_size, total_read_size = 0; > + grub_ssize_t read_size; > + > + full_file_size = grub_file_size (file); > + if (full_file_size == GRUB_FILE_SIZE_UNKNOWN) > + return grub_error (GRUB_ERR_BAD_ARGUMENT, > + N_("Cannot read a file of unknown size into a buffer")); > + > + if (full_file_size > GRUB_SIZE_MAX) > + return grub_error (GRUB_ERR_OUT_OF_RANGE, > + N_("File is too large to read: %" PRIuGRUB_UINT64_T > + " bytes"), full_file_size); > + > + file_size = (grub_size_t) full_file_size; > + > + *buf = grub_malloc (file_size); > + if (!*buf) > + return grub_error (GRUB_ERR_OUT_OF_MEMORY, > + N_("Could not allocate file data buffer size %" > + PRIuGRUB_SIZE), file_size); > + > + while (total_read_size < file_size) > + { > + read_size = > + grub_file_read (file, *buf + total_read_size, > + file_size - total_read_size); > + > + if (read_size < 0) > + { > + grub_free (*buf); > + return grub_errno; > + } > + else if (read_size == 0) > + { > + grub_free (*buf); > + return grub_error (GRUB_ERR_IO, > + N_("Could not read full file size (%" > + PRIuGRUB_SIZE "), only %" PRIuGRUB_SIZE > + " bytes read"), file_size, total_read_size); > + } > + > + total_read_size += read_size; > + } > + *len = file_size; > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +read_cert_from_file (grub_file_t f, struct x509_certificate *certificate) > +{ > + grub_err_t err; > + grub_uint8_t *buf; > + grub_size_t file_size; > + > + err = file_read_all (f, &buf, &file_size); > + if (err != GRUB_ERR_NONE) > + return err; > + > + err = parse_x509_certificate (buf, file_size, certificate); > + if (err != GRUB_ERR_NONE) > + { > + grub_free (buf); > + return err; > + } forgot grub-free(buf) ? > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +extract_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize, > + struct grub_appended_signature *sig) > +{ > + grub_err_t err; > + grub_size_t pkcs7_size; > + grub_size_t remaining_len; > + const grub_uint8_t *appsigdata = buf + bufsize - grub_strlen (magic); > + > + if (bufsize < grub_strlen (magic)) > + return grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("File too short for signature magic")); > + > + if (grub_memcmp (appsigdata, (grub_uint8_t *) magic, grub_strlen (magic))) > + return grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("Missing or invalid signature magic")); > + > + remaining_len = bufsize - grub_strlen (magic); > + > + if (remaining_len < sizeof (struct module_signature)) > + return grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("File too short for signature metadata")); > + > + appsigdata -= sizeof (struct module_signature); > + > + /* extract the metadata */ > + grub_memcpy (&(sig->sig_metadata), appsigdata, > + sizeof (struct module_signature)); > + > + remaining_len -= sizeof (struct module_signature); > + > + if (sig->sig_metadata.id_type != 2) > + return grub_error (GRUB_ERR_BAD_SIGNATURE, N_("Wrong signature type")); > + > + pkcs7_size = grub_be_to_cpu32 (sig->sig_metadata.sig_len); > + > + if (pkcs7_size > remaining_len) > + return grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("File too short for PKCS#7 message")); > + > + grub_dprintf ("appendedsig", "sig len %" PRIuGRUB_SIZE "\n", pkcs7_size); > + > + sig->signature_len = > + grub_strlen (magic) + sizeof (struct module_signature) + pkcs7_size; > + > + /* rewind pointer and parse pkcs7 data */ > + appsigdata -= pkcs7_size; > + > + err = parse_pkcs7_signedData (appsigdata, pkcs7_size, &sig->pkcs7); > + if (err != GRUB_ERR_NONE) > + return err; > + > + return GRUB_ERR_NONE; just 'return parse_pkcs7_signedData (...)' ? > +} > + > +static grub_err_t > +grub_verify_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize) > +{ > + grub_err_t err = GRUB_ERR_NONE; > + grub_size_t datasize; > + void *context; > + unsigned char *hash; > + gcry_mpi_t hashmpi; > + gcry_err_code_t rc; > + struct x509_certificate *pk; > + struct grub_appended_signature sig; > + struct pkcs7_signerInfo *si; > + int i; > + > + if (!grub_trusted_key) > + return grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("No trusted keys to verify against")); > + > + err = extract_appended_signature (buf, bufsize, &sig); > + if (err != GRUB_ERR_NONE) > + return err; > + > + datasize = bufsize - sig.signature_len; > + > + for (i = 0; i < sig.pkcs7.signerInfo_count; i++) > + { > + /* This could be optimised in a couple of ways: > + - we could only compute hashes once per hash type > + - we could track signer information and only verify where IDs match > + For now we do the naive O(trusted keys * pkcs7 signers) approach. > + */ > + si = &sig.pkcs7.signerInfos[i]; > + context = grub_zalloc (si->hash->contextsize); > + if (!context) > + return grub_errno; > + > + si->hash->init (context); > + si->hash->write (context, buf, datasize); > + si->hash->final (context); > + hash = si->hash->read (context); > + > + grub_dprintf ("appendedsig", > + "data size %" PRIxGRUB_SIZE ", signer %d hash %02x%02x%02x%02x...\n", > + datasize, i, hash[0], hash[1], hash[2], hash[3]); > + > + err = GRUB_ERR_BAD_SIGNATURE; > + for (pk = grub_trusted_key; pk; pk = pk->next) > + { > + rc = grub_crypto_rsa_pad (&hashmpi, hash, si->hash, pk->mpis[0]); > + if (rc) > + { > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("Error padding hash for RSA verification: %d"), > + rc); > + grub_free (context); > + goto cleanup; > + } > + > + rc = _gcry_pubkey_spec_rsa.verify (0, hashmpi, &si->sig_mpi, > + pk->mpis, NULL, NULL); > + gcry_mpi_release (hashmpi); > + > + if (rc == 0) > + { > + grub_dprintf ("appendedsig", > + "verify signer %d with key '%s' succeeded\n", i, > + pk->subject); > + err = GRUB_ERR_NONE; > + break; > + } > + > + grub_dprintf ("appendedsig", > + "verify signer %d with key '%s' failed with %d\n", i, > + pk->subject, rc); > + } > + > + grub_free (context); > + > + if (err == GRUB_ERR_NONE) > + break; > + } > + > + /* If we didn't verify, provide a neat message */ > + if (err != GRUB_ERR_NONE) > + err = grub_error (GRUB_ERR_BAD_SIGNATURE, > + N_("Failed to verify signature against a trusted key")); > + > +cleanup: > + pkcs7_signedData_release (&sig.pkcs7); > + > + return err; > +} > + > +static grub_err_t > +grub_cmd_verify_signature (grub_command_t cmd __attribute__((unused)), > + int argc, char **args) > +{ > + grub_file_t f; > + grub_err_t err = GRUB_ERR_NONE; > + grub_uint8_t *data; > + grub_size_t file_size; > + > + if (argc < 1) > + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); > + > + grub_dprintf ("appendedsig", "verifying %s\n", args[0]); > + > + f = grub_file_open (args[0], GRUB_FILE_TYPE_VERIFY_SIGNATURE); > + if (!f) > + { > + err = grub_errno; > + goto cleanup; > + } > + > + err = file_read_all (f, &data, &file_size); > + if (err != GRUB_ERR_NONE) > + goto cleanup; > + > + err = grub_verify_appended_signature (data, file_size); > + > + grub_free (data); > + > +cleanup: > + if (f) > + grub_file_close (f); > + return err; > +} > + > +static grub_err_t > +grub_cmd_distrust (grub_command_t cmd __attribute__((unused)), > + int argc, char **args) > +{ > + unsigned long cert_num, i; > + struct x509_certificate *cert, *prev; > + > + if (argc != 1) > + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("One argument expected")); > + > + grub_errno = GRUB_ERR_NONE; > + cert_num = grub_strtoul (args[0], NULL, 10); > + if (grub_errno != GRUB_ERR_NONE) > + return grub_errno; > + > + if (cert_num < 1) > + return grub_error (GRUB_ERR_BAD_ARGUMENT, > + N_("Certificate number too small - numbers start at 1")); > + > + if (cert_num == 1) > + { > + cert = grub_trusted_key; > + grub_trusted_key = cert->next; > + > + certificate_release (cert); > + grub_free (cert); > + return GRUB_ERR_NONE; > + } > + i = 2; > + prev = grub_trusted_key; > + cert = grub_trusted_key->next; > + while (cert) > + { > + if (i == cert_num) > + { > + prev->next = cert->next; > + certificate_release (cert); > + grub_free (cert); > + return GRUB_ERR_NONE; > + } > + i++; > + prev = cert; > + cert = cert->next; > + } > + > + return grub_error (GRUB_ERR_BAD_ARGUMENT, > + N_("No certificate number %lu found - only %lu certificates in the store"), > + cert_num, i - 1); > +} > + > +static grub_err_t > +grub_cmd_trust (grub_command_t cmd __attribute__((unused)), > + int argc, char **args) > +{ > + grub_file_t certf; > + struct x509_certificate *cert = NULL; > + grub_err_t err; > + > + if (argc != 1) > + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); > + > + certf = grub_file_open (args[0], > + GRUB_FILE_TYPE_CERTIFICATE_TRUST > + | GRUB_FILE_TYPE_NO_DECOMPRESS); > + if (!certf) > + return grub_errno; > + > + > + cert = grub_zalloc (sizeof (struct x509_certificate)); > + if (!cert) > + return grub_error (GRUB_ERR_OUT_OF_MEMORY, > + N_("Could not allocate memory for certificate")); > + > + err = read_cert_from_file (certf, cert); > + grub_file_close (certf); > + if (err != GRUB_ERR_NONE) > + { > + grub_free (cert); > + return err; > + } > + grub_dprintf ("appendedsig", "Loaded certificate with CN: %s\n", > + cert->subject); > + > + cert->next = grub_trusted_key; > + grub_trusted_key = cert; > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +grub_cmd_list (grub_command_t cmd __attribute__((unused)), > + int argc __attribute__((unused)), > + char **args __attribute__((unused))) > +{ > + struct x509_certificate *cert; > + int cert_num = 1; > + grub_size_t i; > + > + for (cert = grub_trusted_key; cert; cert = cert->next) > + { > + grub_printf (N_("Certificate %d:\n"), cert_num); > + > + grub_printf (N_("\tSerial: ")); > + for (i = 0; i < cert->serial_len - 1; i++) > + { > + grub_printf ("%02x:", cert->serial[i]); > + } > + grub_printf ("%02x\n", cert->serial[cert->serial_len - 1]); > + > + grub_printf ("\tCN: %s\n\n", cert->subject); > + cert_num++; > + > + } > + > + return GRUB_ERR_NONE; > +} > + > +static grub_err_t > +appendedsig_init (grub_file_t io __attribute__((unused)), > + enum grub_file_type type, > + void **context __attribute__((unused)), > + enum grub_verify_flags *flags) > +{ > + if (check_sigs == check_sigs_no) > + { > + *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION; > + return GRUB_ERR_NONE; > + } > + > + switch (type & GRUB_FILE_TYPE_MASK) > + { > + case GRUB_FILE_TYPE_CERTIFICATE_TRUST: > + /* > + * This is a certificate to add to trusted keychain. > + * > + * This needs to be verified or blocked. Ideally we'd write an x509 > + * verifier, but we lack the hubris required to take this on. Instead, > + * require that it have an appended signature. > + */ > + > + /* Fall through */ > + > + case GRUB_FILE_TYPE_LINUX_KERNEL: > + case GRUB_FILE_TYPE_GRUB_MODULE: > + /* > + * Appended signatures are only defined for ELF binaries. > + * Out of an abundance of caution, we only verify Linux kernels and > + * GRUB modules at this point. > + */ > + *flags = GRUB_VERIFY_FLAGS_SINGLE_CHUNK; > + return GRUB_ERR_NONE; > + > + case GRUB_FILE_TYPE_ACPI_TABLE: > + case GRUB_FILE_TYPE_DEVICE_TREE_IMAGE: > + /* > + * It is possible to use appended signature verification without > + * lockdown - like the PGP verifier. When combined with an embedded > + * config file in a signed grub binary, this could still be a meaningful > + * secure-boot chain - so long as it isn't subverted by something like a > + * rouge ACPI table or DT image. Defer them explicitly. > + */ > + *flags = GRUB_VERIFY_FLAGS_DEFER_AUTH; > + return GRUB_ERR_NONE; > + > + default: > + *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION; > + return GRUB_ERR_NONE; > + } > +} > + > +static grub_err_t > +appendedsig_write (void *ctxt __attribute__((unused)), > + void *buf, grub_size_t size) > +{ > + return grub_verify_appended_signature (buf, size); > +} > + > +struct grub_file_verifier grub_appendedsig_verifier = { > + .name = "appendedsig", > + .init = appendedsig_init, > + .write = appendedsig_write, > +}; > + > +static grub_ssize_t > +pseudo_read (struct grub_file *file, char *buf, grub_size_t len) > +{ > + grub_memcpy (buf, (grub_uint8_t *) file->data + file->offset, len); > + return len; > +} > + > +/* Filesystem descriptor. */ > +static struct grub_fs pseudo_fs = { > + .name = "pseudo", > + .fs_read = pseudo_read > +}; > + > +static grub_command_t cmd_verify, cmd_list, cmd_distrust, cmd_trust; > + > +GRUB_MOD_INIT (appendedsig) > +{ > + int rc; > + struct grub_module_header *header; > + > + /* If in lockdown, immediately enter forced mode */ > + if (grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED) > + check_sigs = check_sigs_forced; > + > + grub_trusted_key = NULL; > + > + grub_register_variable_hook ("check_appended_signatures", > + grub_env_read_sec, grub_env_write_sec); > + grub_env_export ("check_appended_signatures"); > + > + rc = asn1_init (); > + if (rc) > + grub_fatal ("Error initing ASN.1 data structures: %d: %s\n", rc, > + asn1_strerror (rc)); > + > + FOR_MODULES (header) > + { > + struct grub_file pseudo_file; > + struct x509_certificate *pk = NULL; > + grub_err_t err; > + > + /* Not an ELF module, skip. */ > + if (header->type != OBJ_TYPE_X509_PUBKEY) > + continue; Is that comment correct? What does an ELF module have to do with type OBJ_TYPE_X509_PUBKEY? > + > + grub_memset (&pseudo_file, 0, sizeof (pseudo_file)); > + pseudo_file.fs = &pseudo_fs; > + pseudo_file.size = header->size - sizeof (struct grub_module_header); > + pseudo_file.data = (char *) header + sizeof (struct grub_module_header); > + > + grub_dprintf ("appendedsig", > + "Found an x509 key, size=%" PRIuGRUB_UINT64_T "\n", > + pseudo_file.size); > + > + pk = grub_zalloc (sizeof (struct x509_certificate)); > + if (!pk) > + { > + grub_fatal ("Out of memory loading initial certificates"); > + } > + > + err = read_cert_from_file (&pseudo_file, pk); > + if (err != GRUB_ERR_NONE) > + grub_fatal ("Error loading initial key: %s", grub_errmsg); > + > + grub_dprintf ("appendedsig", "loaded certificate CN='%s'\n", pk->subject); > + > + pk->next = grub_trusted_key; > + grub_trusted_key = pk; > + } > + > + cmd_trust = > + grub_register_command ("trust_certificate", grub_cmd_trust, > + N_("X509_CERTIFICATE"), > + N_("Add X509_CERTIFICATE to trusted certificates.")); > + cmd_list = > + grub_register_command ("list_certificates", grub_cmd_list, 0, > + N_("Show the list of trusted x509 certificates.")); > + cmd_verify = > + grub_register_command ("verify_appended", grub_cmd_verify_signature, > + N_("FILE"), > + N_("Verify FILE against the trusted x509 certificates.")); > + cmd_distrust = > + grub_register_command ("distrust_certificate", grub_cmd_distrust, > + N_("CERT_NUMBER"), > + N_("Remove CERT_NUMBER (as listed by list_certificates) from trusted certificates.")); > + > + grub_verifier_register (&grub_appendedsig_verifier); > + grub_dl_set_persistent (mod); > +} > + > +GRUB_MOD_FINI (appendedsig) > +{ > + /* > + * grub_dl_set_persistent should prevent this from actually running, but > + * it does still run under emu. > + */ > + > + grub_verifier_unregister (&grub_appendedsig_verifier); > + grub_unregister_command (cmd_verify); > + grub_unregister_command (cmd_list); > + grub_unregister_command (cmd_trust); > + grub_unregister_command (cmd_distrust); > +} > diff --git a/include/grub/file.h b/include/grub/file.h > index 31567483ccfc..96827a4f8961 100644 > --- a/include/grub/file.h > +++ b/include/grub/file.h > @@ -80,6 +80,8 @@ enum grub_file_type > GRUB_FILE_TYPE_PUBLIC_KEY, > /* File holding public key to add to trused keys. */ > GRUB_FILE_TYPE_PUBLIC_KEY_TRUST, > + /* File holding x509 certificiate to add to trusted keys. */ > + GRUB_FILE_TYPE_CERTIFICATE_TRUST, > /* File of which we intend to print a blocklist to the user. */ > GRUB_FILE_TYPE_PRINT_BLOCKLIST, > /* File we intend to use for test loading or testing speed. */