Introducing the following GRUB commands to manage certificate/binary
hashes.
1. append_list_dbx:
Show the list of distrusted certificates and binary/certificate
hashes from the dbx list.
2. append_add_db_hash:
Add the trusted binary hash to the db list.
3. append_add_dbx_hash:
Add the distrusted certificate/binary hash to the dbx list.
Note that if signature verification (check_appended_signatures) is set to yes,
the append_add_db_hash and append_add_dbx_hash commands only accept the file
‘hash_file’ that is signed with an appended signature.
Signed-off-by: Sudhakar Kuppusamy <sudhakar@linux.ibm.com>
Tested-by: Sridhar Markonda <sridharm@linux.ibm.com>
Reviewed-by: Avnish Chouhan <avnish@linux.ibm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
1717 lines
57 KiB
C
1717 lines
57 KiB
C
/*
|
||
* GRUB -- GRand Unified Bootloader
|
||
* Copyright (C) 2020, 2021, 2022 Free Software Foundation, Inc.
|
||
* Copyright (C) 2020, 2021, 2022, 2025 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/types.h>
|
||
#include <grub/misc.h>
|
||
#include <grub/mm.h>
|
||
#include <grub/err.h>
|
||
#include <grub/dl.h>
|
||
#include <grub/file.h>
|
||
#include <grub/command.h>
|
||
#include <grub/crypto.h>
|
||
#include <grub/i18n.h>
|
||
#include <grub/gcrypt/gcrypt.h>
|
||
#include <grub/kernel.h>
|
||
#include <grub/extcmd.h>
|
||
#include <grub/verify.h>
|
||
#include <libtasn1.h>
|
||
#include <grub/env.h>
|
||
#include <grub/lockdown.h>
|
||
#include <grub/powerpc/ieee1275/platform_keystore.h>
|
||
#include <grub/efi/pks.h>
|
||
|
||
#include "appendedsig.h"
|
||
|
||
GRUB_MOD_LICENSE ("GPLv3+");
|
||
|
||
/* Public key type. */
|
||
#define PKEY_ID_PKCS7 2
|
||
|
||
/* Appended signature magic string and size. */
|
||
#define SIG_MAGIC "~Module signature appended~\n"
|
||
#define SIG_MAGIC_SIZE ((sizeof(SIG_MAGIC) - 1))
|
||
|
||
/* SHA256, SHA384 and SHA512 hash sizes. */
|
||
#define SHA256_HASH_SIZE 32
|
||
#define SHA384_HASH_SIZE 48
|
||
#define SHA512_HASH_SIZE 64
|
||
|
||
#define OPTION_BINARY_HASH 0
|
||
#define OPTION_CERT_HASH 1
|
||
|
||
/*
|
||
* 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;
|
||
|
||
#define SIG_METADATA_SIZE (sizeof (struct module_signature))
|
||
#define APPENDED_SIG_SIZE(pkcs7_data_size) \
|
||
(pkcs7_data_size + SIG_MAGIC_SIZE + SIG_METADATA_SIZE)
|
||
|
||
/* This represents an entire, parsed, appended signature. */
|
||
struct appended_signature
|
||
{
|
||
struct module_signature sig_metadata; /* Module signature metadata. */
|
||
grub_pkcs7_data_t pkcs7; /* Parsed PKCS#7 data. */
|
||
grub_size_t signature_len; /* Length of PKCS#7 data + metadata + magic. */
|
||
};
|
||
typedef struct appended_signature sb_appendedsig_t;
|
||
|
||
/* This represents a trusted certificates. */
|
||
struct sb_database
|
||
{
|
||
grub_x509_cert_t *certs; /* Certificates. */
|
||
grub_uint32_t cert_entries; /* Number of certificates. */
|
||
grub_uint8_t **hashes; /* Certificate/binary hashes. */
|
||
grub_size_t *hash_sizes; /* Sizes of certificate/binary hashes. */
|
||
grub_uint32_t hash_entries; /* Number of certificate/binary hashes. */
|
||
bool is_db; /* Flag to indicate the db/dbx list. */
|
||
};
|
||
typedef struct sb_database sb_database_t;
|
||
|
||
/* The db list is used to validate appended signatures. */
|
||
static sb_database_t db = {.certs = NULL, .cert_entries = 0, .hashes = NULL,
|
||
.hash_sizes = NULL, .hash_entries = 0, .is_db = true};
|
||
/*
|
||
* The dbx list is used to ensure that the distrusted certificates or GRUB
|
||
* modules/kernel binaries are rejected during appended signatures/hashes
|
||
* validation.
|
||
*/
|
||
static sb_database_t dbx = {.certs = NULL, .cert_entries = 0, .hashes = NULL,
|
||
.hash_sizes = NULL, .hash_entries = 0, .is_db = false};
|
||
|
||
/*
|
||
* Signature verification flag (check_sigs).
|
||
* check_sigs: false
|
||
* - No signature verification. This is the default.
|
||
* check_sigs: true
|
||
* - Enforce signature verification, and if signature verification fails, post
|
||
* the errors and stop the boot.
|
||
*/
|
||
static bool check_sigs = false;
|
||
|
||
/*
|
||
* append_key_mgmt: Key Management Modes
|
||
* False: Static key management (use built-in Keys). This is default.
|
||
* True: Dynamic key management (use Platform KeySotre).
|
||
*/
|
||
static bool append_key_mgmt = false;
|
||
|
||
/* Platform KeyStore db and dbx. */
|
||
static grub_pks_t *pks_keystore;
|
||
|
||
/* Appended signature size. */
|
||
static grub_size_t append_sig_len = 0;
|
||
|
||
static const struct grub_arg_option options[] =
|
||
{
|
||
{"binary-hash", 'b', 0, N_("hash file of the binary."), 0, ARG_TYPE_PATHNAME},
|
||
{"cert-hash", 'c', 1, N_("hash file of the certificate."), 0, ARG_TYPE_PATHNAME},
|
||
{0, 0, 0, 0, 0, 0}
|
||
};
|
||
|
||
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
|
||
};
|
||
|
||
/*
|
||
* We cannot use hexdump() to display hash data because it is typically displayed
|
||
* in hexadecimal format, along with an ASCII representation of the same data.
|
||
*
|
||
* Example: sha256 hash data
|
||
* 00000000 52 b5 90 49 64 de 22 d7 4e 5f 4f b4 1b 51 9c 34 |R..Id.".N_O..Q.4|
|
||
* 00000010 b1 96 21 7c 91 78 a5 0d 20 8c e9 5c 22 54 53 f7 |..!|.x.. ..\"TS.|
|
||
*
|
||
* An appended signature only required to display the hexadecimal of the hash data
|
||
* by separating each byte with ":". So, we introduced a new method hexdump_colon
|
||
* to display it.
|
||
*
|
||
* Example: Sha256 hash data
|
||
* 52:b5:90:49:64:de:22:d7:4e:5f:4f:b4:1b:51:9c:34:
|
||
* b1:96:21:7c:91:78:a5:0d:20:8c:e9:5c:22:54:53:f7
|
||
*/
|
||
static void
|
||
hexdump_colon (const grub_uint8_t *data, const grub_size_t length)
|
||
{
|
||
grub_size_t i, count = 0;
|
||
|
||
for (i = 0; i < length - 1; i++)
|
||
{
|
||
grub_printf ("%02x:", data[i]);
|
||
count++;
|
||
if (count == 16)
|
||
{
|
||
grub_printf ("\n ");
|
||
count = 0;
|
||
}
|
||
}
|
||
|
||
grub_printf ("%02x\n", data[i]);
|
||
}
|
||
|
||
static void
|
||
print_certificate (const grub_x509_cert_t *cert, const grub_uint32_t cert_num)
|
||
{
|
||
grub_uint32_t i;
|
||
|
||
grub_printf ("\nCertificate: %u\n", cert_num);
|
||
grub_printf (" Data:\n");
|
||
grub_printf (" Version: %u (0x%u)\n", cert->version + 1, cert->version);
|
||
grub_printf (" Serial Number:\n ");
|
||
|
||
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 (" Issuer: %s\n", cert->issuer);
|
||
grub_printf (" Subject: %s\n", cert->subject);
|
||
grub_printf (" Subject Public Key Info:\n");
|
||
grub_printf (" Public Key Algorithm: rsaEncryption\n");
|
||
grub_printf (" RSA Public-Key: (%d bit)\n", cert->modulus_size);
|
||
grub_printf (" Fingerprint: sha256\n ");
|
||
hexdump_colon (&cert->fingerprint[GRUB_FINGERPRINT_SHA256][0],
|
||
grub_strlen ((char *) cert->fingerprint[GRUB_FINGERPRINT_SHA256]));
|
||
}
|
||
|
||
/*
|
||
* GUID can be used to determine the hashing function and generate the hash using
|
||
* determined hashing function.
|
||
*/
|
||
static grub_err_t
|
||
get_hash (const grub_packed_guid_t *guid, const grub_uint8_t *data, const grub_size_t data_size,
|
||
grub_uint8_t *hash, grub_size_t *hash_size)
|
||
{
|
||
gcry_md_spec_t *hash_func = NULL;
|
||
|
||
if (guid == NULL)
|
||
return grub_error (GRUB_ERR_OUT_OF_RANGE, "GUID is not available");
|
||
|
||
if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0)
|
||
hash_func = &_gcry_digest_spec_sha256;
|
||
else if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0)
|
||
hash_func = &_gcry_digest_spec_sha384;
|
||
else if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0)
|
||
hash_func = &_gcry_digest_spec_sha512;
|
||
else
|
||
return grub_error (GRUB_ERR_OUT_OF_RANGE, "unsupported GUID hash");
|
||
|
||
grub_crypto_hash (hash_func, hash, data, data_size);
|
||
*hash_size = hash_func->mdlen;
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
static grub_err_t
|
||
generate_cert_hash (const grub_size_t cert_hash_size, const grub_uint8_t *data,
|
||
const grub_size_t data_size, grub_uint8_t *hash, grub_size_t *hash_size)
|
||
{
|
||
grub_packed_guid_t guid = { 0 };
|
||
|
||
/* support SHA256, SHA384 and SHA512 for certificate hash */
|
||
if (cert_hash_size == SHA256_HASH_SIZE)
|
||
grub_memcpy (&guid, &GRUB_PKS_CERT_X509_SHA256_GUID, GRUB_PACKED_GUID_SIZE);
|
||
else if (cert_hash_size == SHA384_HASH_SIZE)
|
||
grub_memcpy (&guid, &GRUB_PKS_CERT_X509_SHA384_GUID, GRUB_PACKED_GUID_SIZE);
|
||
else if (cert_hash_size == SHA512_HASH_SIZE)
|
||
grub_memcpy (&guid, &GRUB_PKS_CERT_X509_SHA512_GUID, GRUB_PACKED_GUID_SIZE);
|
||
else
|
||
{
|
||
grub_dprintf ("appendedsig", "unsupported hash type (%" PRIuGRUB_SIZE ") and "
|
||
"skipped\n", cert_hash_size);
|
||
return GRUB_ERR_UNKNOWN_COMMAND;
|
||
}
|
||
|
||
return get_hash (&guid, data, data_size, hash, hash_size);
|
||
}
|
||
|
||
/* Check the hash presence in the db/dbx list. */
|
||
static bool
|
||
check_hash_presence (grub_uint8_t *const hash, const grub_size_t hash_size,
|
||
const sb_database_t *sb_database)
|
||
{
|
||
grub_uint32_t i;
|
||
|
||
for (i = 0; i < sb_database->hash_entries; i++)
|
||
{
|
||
if (sb_database->hashes[i] == NULL)
|
||
continue;
|
||
|
||
if (hash_size == sb_database->hash_sizes[i] &&
|
||
grub_memcmp (sb_database->hashes[i], hash, hash_size) == 0)
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Add the certificate/binary hash into the db/dbx list. */
|
||
static grub_err_t
|
||
add_hash (grub_uint8_t *const data, const grub_size_t data_size, sb_database_t *sb_database)
|
||
{
|
||
grub_uint8_t **hashes;
|
||
grub_size_t *hash_sizes;
|
||
|
||
if (data == NULL || data_size == 0)
|
||
return grub_error (GRUB_ERR_OUT_OF_RANGE, "certificate/binary-hash data or size is not available");
|
||
|
||
if (sb_database->is_db == true)
|
||
{
|
||
if (check_hash_presence (data, data_size, &dbx) == true)
|
||
{
|
||
grub_dprintf ("appendedsig",
|
||
"cannot add a hash (%02x%02x%02x%02x), as it is present in the dbx list\n",
|
||
data[0], data[1], data[2], data[3]);
|
||
return GRUB_ERR_ACCESS_DENIED;
|
||
}
|
||
}
|
||
|
||
if (check_hash_presence (data, data_size, sb_database) == true)
|
||
{
|
||
grub_dprintf ("appendedsig",
|
||
"cannot add a hash (%02x%02x%02x%02x), as it is present in the %s list\n",
|
||
data[0], data[1], data[2], data[3], ((sb_database->is_db == true) ? "db" : "dbx"));
|
||
return GRUB_ERR_EXISTS;
|
||
}
|
||
|
||
hashes = grub_realloc (sb_database->hashes, sizeof (grub_uint8_t *) * (sb_database->hash_entries + 1));
|
||
if (hashes == NULL)
|
||
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
|
||
hash_sizes = grub_realloc (sb_database->hash_sizes, sizeof (grub_size_t) * (sb_database->hash_entries + 1));
|
||
if (hash_sizes == NULL)
|
||
{
|
||
/* Allocated memory will be freed by free_db_list()/free_dbx_list(). */
|
||
hashes[sb_database->hash_entries] = NULL;
|
||
sb_database->hashes = hashes;
|
||
sb_database->hash_entries++;
|
||
|
||
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
}
|
||
|
||
hashes[sb_database->hash_entries] = grub_malloc (data_size);
|
||
if (hashes[sb_database->hash_entries] == NULL)
|
||
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
|
||
grub_dprintf ("appendedsig",
|
||
"added the hash %02x%02x%02x%02x... with size of %" PRIuGRUB_SIZE " to the %s list\n",
|
||
data[0], data[1], data[2], data[3], data_size,
|
||
((sb_database->is_db == true) ? "db" : "dbx"));
|
||
|
||
grub_memcpy (hashes[sb_database->hash_entries], data, data_size);
|
||
hash_sizes[sb_database->hash_entries] = data_size;
|
||
sb_database->hash_sizes = hash_sizes;
|
||
sb_database->hashes = hashes;
|
||
sb_database->hash_entries++;
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
static bool
|
||
is_hash (const grub_packed_guid_t *guid)
|
||
{
|
||
/* GUID type of the binary hash. */
|
||
if (grub_memcmp (guid, &GRUB_PKS_CERT_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0)
|
||
return true;
|
||
|
||
/* GUID type of the certificate hash. */
|
||
if (grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA256_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA384_GUID, GRUB_PACKED_GUID_SIZE) == 0 ||
|
||
grub_memcmp (guid, &GRUB_PKS_CERT_X509_SHA512_GUID, GRUB_PACKED_GUID_SIZE) == 0)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
static bool
|
||
is_x509 (const grub_packed_guid_t *guid)
|
||
{
|
||
if (grub_memcmp (guid, &GRUB_PKS_CERT_X509_GUID, GRUB_PACKED_GUID_SIZE) == 0)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
static bool
|
||
is_cert_match (const grub_x509_cert_t *cert1, const grub_x509_cert_t *cert2)
|
||
{
|
||
if (grub_memcmp (cert1->subject, cert2->subject, cert2->subject_len) == 0
|
||
&& grub_memcmp (cert1->issuer, cert2->issuer, cert2->issuer_len) == 0
|
||
&& grub_memcmp (cert1->serial, cert2->serial, cert2->serial_len) == 0
|
||
&& grub_memcmp (cert1->mpis[GRUB_RSA_PK_MODULUS], cert2->mpis[GRUB_RSA_PK_MODULUS],
|
||
sizeof (cert2->mpis[GRUB_RSA_PK_MODULUS])) == 0
|
||
&& grub_memcmp (cert1->mpis[GRUB_RSA_PK_EXPONENT], cert2->mpis[GRUB_RSA_PK_EXPONENT],
|
||
sizeof (cert2->mpis[GRUB_RSA_PK_EXPONENT])) == 0
|
||
&& grub_memcmp (cert1->fingerprint[GRUB_FINGERPRINT_SHA256],
|
||
cert2->fingerprint[GRUB_FINGERPRINT_SHA256],
|
||
grub_strlen ((char *) cert2->fingerprint[GRUB_FINGERPRINT_SHA256])) == 0)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Check the certificate hash presence in the dbx list. */
|
||
static bool
|
||
is_cert_hash_present_in_dbx (const grub_uint8_t *data, const grub_size_t data_size)
|
||
{
|
||
grub_err_t rc;
|
||
grub_uint32_t i;
|
||
grub_size_t cert_hash_size = 0;
|
||
grub_uint8_t cert_hash[GRUB_MAX_HASH_LEN] = { 0 };
|
||
|
||
for (i = 0; i < dbx.hash_entries; i++)
|
||
{
|
||
if (dbx.hashes[i] == NULL)
|
||
continue;
|
||
|
||
rc = generate_cert_hash (dbx.hash_sizes[i], data, data_size, cert_hash, &cert_hash_size);
|
||
if (rc != GRUB_ERR_NONE)
|
||
continue;
|
||
|
||
if (cert_hash_size == dbx.hash_sizes[i] &&
|
||
grub_memcmp (dbx.hashes[i], cert_hash, cert_hash_size) == 0)
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Check the certificate presence in the db/dbx list. */
|
||
static bool
|
||
check_cert_presence (const grub_x509_cert_t *cert_in, const sb_database_t *sb_database)
|
||
{
|
||
grub_x509_cert_t *cert;
|
||
|
||
for (cert = sb_database->certs; cert != NULL; cert = cert->next)
|
||
if (is_cert_match (cert, cert_in) == true)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
* Add the certificate into the db list if it is not present in the dbx and db
|
||
* list when is_db is true. Add the certificate into the dbx list when is_db is
|
||
* false.
|
||
*/
|
||
static grub_err_t
|
||
add_certificate (const grub_uint8_t *data, const grub_size_t data_size,
|
||
sb_database_t *sb_database)
|
||
{
|
||
grub_err_t rc;
|
||
grub_x509_cert_t *cert;
|
||
|
||
if (data == NULL || data_size == 0)
|
||
return grub_error (GRUB_ERR_OUT_OF_RANGE, "certificate data or size is not available");
|
||
|
||
cert = grub_zalloc (sizeof (grub_x509_cert_t));
|
||
if (cert == NULL)
|
||
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
|
||
rc = grub_x509_cert_parse (data, data_size, cert);
|
||
if (rc != GRUB_ERR_NONE)
|
||
{
|
||
grub_dprintf ("appendedsig", "cannot add a certificate CN='%s' to the %s list\n",
|
||
cert->subject, (sb_database->is_db == true) ? "db" : "dbx");
|
||
grub_free (cert);
|
||
return rc;
|
||
}
|
||
|
||
/*
|
||
* Only checks the certificate against dbx if is_db is true when dynamic key
|
||
* management is enabled.
|
||
*/
|
||
if (append_key_mgmt == true)
|
||
{
|
||
if (sb_database->is_db == true)
|
||
{
|
||
if (is_cert_hash_present_in_dbx (data, data_size) == true ||
|
||
check_cert_presence (cert, &dbx) == true)
|
||
{
|
||
grub_dprintf ("appendedsig",
|
||
"cannot add a certificate CN='%s', as it is present in the dbx list",
|
||
cert->subject);
|
||
rc = GRUB_ERR_ACCESS_DENIED;
|
||
goto fail;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (check_cert_presence (cert, sb_database) == true)
|
||
{
|
||
grub_dprintf ("appendedsig",
|
||
"cannot add a certificate CN='%s', as it is present in the %s list",
|
||
cert->subject, ((sb_database->is_db == true) ? "db" : "dbx"));
|
||
rc = GRUB_ERR_EXISTS;
|
||
goto fail;
|
||
}
|
||
|
||
grub_dprintf ("appendedsig", "added a certificate CN='%s' to the %s list\n",
|
||
cert->subject, ((sb_database->is_db == true) ? "db" : "dbx"));
|
||
|
||
cert->next = sb_database->certs;
|
||
sb_database->certs = cert;
|
||
sb_database->cert_entries++;
|
||
|
||
return rc;
|
||
|
||
fail:
|
||
grub_x509_cert_release (cert);
|
||
grub_free (cert);
|
||
|
||
return rc;
|
||
}
|
||
|
||
static void
|
||
_remove_cert_from_db (const grub_x509_cert_t *cert)
|
||
{
|
||
grub_uint32_t i = 1;
|
||
grub_x509_cert_t *curr_cert, *prev_cert;
|
||
|
||
for (curr_cert = prev_cert = db.certs; curr_cert != NULL; curr_cert = curr_cert->next, i++)
|
||
{
|
||
if (is_cert_match (curr_cert, cert) == true)
|
||
{
|
||
if (i == 1) /* Match with first certificate in the db list. */
|
||
db.certs = curr_cert->next;
|
||
else
|
||
prev_cert->next = curr_cert->next;
|
||
|
||
grub_dprintf ("appendedsig",
|
||
"removed distrusted certificate with CN: %s from the db list\n",
|
||
curr_cert->subject);
|
||
curr_cert->next = NULL;
|
||
grub_x509_cert_release (curr_cert);
|
||
grub_free (curr_cert);
|
||
break;
|
||
}
|
||
else
|
||
prev_cert = curr_cert;
|
||
}
|
||
}
|
||
|
||
static grub_err_t
|
||
remove_cert_from_db (const grub_uint8_t *data, const grub_size_t data_size)
|
||
{
|
||
grub_err_t rc;
|
||
grub_x509_cert_t *cert;
|
||
|
||
if (data == NULL || data_size == 0)
|
||
return grub_error (GRUB_ERR_OUT_OF_RANGE, "certificate data or size is not available");
|
||
|
||
cert = grub_zalloc (sizeof (grub_x509_cert_t));
|
||
if (cert == NULL)
|
||
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
|
||
rc = grub_x509_cert_parse (data, data_size, cert);
|
||
if (rc != GRUB_ERR_NONE)
|
||
{
|
||
grub_dprintf ("appendedsig", "cannot remove an invalid certificate from the db list\n");
|
||
grub_free (cert);
|
||
return rc;
|
||
}
|
||
|
||
/* Remove certificate from the db list. */
|
||
_remove_cert_from_db (cert);
|
||
|
||
return rc;
|
||
}
|
||
|
||
static bool
|
||
cert_fingerprint_match (const grub_uint8_t *hash_data, const grub_size_t hash_data_size,
|
||
const grub_x509_cert_t *cert)
|
||
{
|
||
grub_int32_t type;
|
||
|
||
if (hash_data_size == SHA256_HASH_SIZE)
|
||
type = GRUB_FINGERPRINT_SHA256;
|
||
else if (hash_data_size == SHA384_HASH_SIZE)
|
||
type = GRUB_FINGERPRINT_SHA384;
|
||
else if (hash_data_size == SHA512_HASH_SIZE)
|
||
type = GRUB_FINGERPRINT_SHA512;
|
||
else
|
||
{
|
||
grub_dprintf ("appendedsig", "unsupported fingerprint hash type "
|
||
"(%" PRIuGRUB_SIZE ") \n", hash_data_size);
|
||
return false;
|
||
}
|
||
|
||
if (grub_memcmp (cert->fingerprint[type], hash_data, hash_data_size) == 0)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
static void
|
||
remove_hash_from_db (const grub_uint8_t *hash_data, const grub_size_t hash_data_size,
|
||
const bool bin_hash)
|
||
{
|
||
grub_uint32_t i;
|
||
grub_x509_cert_t *cert;
|
||
|
||
if (bin_hash == true)
|
||
{
|
||
for (i = 0; i < db.hash_entries; i++)
|
||
{
|
||
if (db.hashes[i] == NULL)
|
||
continue;
|
||
|
||
if (grub_memcmp (db.hashes[i], hash_data, hash_data_size) == 0)
|
||
{
|
||
grub_dprintf ("appendedsig", "removed distrusted hash %02x%02x%02x%02x.. from the db list\n",
|
||
db.hashes[i][0], db.hashes[i][1], db.hashes[i][2], db.hashes[i][3]);
|
||
grub_free (db.hashes[i]);
|
||
db.hashes[i] = NULL;
|
||
db.hash_sizes[i] = 0;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (cert = db.certs; cert != NULL; cert = cert->next)
|
||
{
|
||
if (cert_fingerprint_match (hash_data, hash_data_size, cert) == true)
|
||
{
|
||
_remove_cert_from_db (cert);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static grub_err_t
|
||
file_read_whole (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,
|
||
"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,
|
||
"file is too large to read: %" PRIuGRUB_OFFSET " bytes",
|
||
full_file_size);
|
||
|
||
file_size = (grub_size_t) full_file_size;
|
||
*buf = grub_malloc (file_size);
|
||
if (*buf == NULL)
|
||
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
|
||
"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,
|
||
"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
|
||
extract_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize,
|
||
sb_appendedsig_t *sig)
|
||
{
|
||
grub_size_t appendedsig_pkcs7_size;
|
||
grub_size_t signed_data_size = bufsize;
|
||
const grub_uint8_t *signed_data = buf;
|
||
|
||
if (signed_data_size < SIG_MAGIC_SIZE)
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature magic");
|
||
|
||
/* Fast-forwarding pointer and get signature magic string. */
|
||
signed_data += signed_data_size - SIG_MAGIC_SIZE;
|
||
if (grub_strncmp ((const char *) signed_data, SIG_MAGIC, SIG_MAGIC_SIZE))
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "missing or invalid signature magic");
|
||
|
||
signed_data_size -= SIG_MAGIC_SIZE;
|
||
if (signed_data_size < SIG_METADATA_SIZE)
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for signature metadata");
|
||
|
||
/* Rewind pointer and extract signature metadata. */
|
||
signed_data -= SIG_METADATA_SIZE;
|
||
grub_memcpy (&(sig->sig_metadata), signed_data, SIG_METADATA_SIZE);
|
||
|
||
if (sig->sig_metadata.id_type != PKEY_ID_PKCS7)
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "wrong signature type");
|
||
|
||
appendedsig_pkcs7_size = grub_be_to_cpu32 (sig->sig_metadata.sig_len);
|
||
|
||
signed_data_size -= SIG_METADATA_SIZE;
|
||
if (appendedsig_pkcs7_size > signed_data_size)
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "file too short for PKCS#7 message");
|
||
|
||
grub_dprintf ("appendedsig", "sig len %" PRIuGRUB_SIZE "\n", appendedsig_pkcs7_size);
|
||
|
||
/* Appended signature size. */
|
||
sig->signature_len = APPENDED_SIG_SIZE (appendedsig_pkcs7_size);
|
||
/* Rewind pointer and parse appended pkcs7 data. */
|
||
signed_data -= appendedsig_pkcs7_size;
|
||
|
||
return grub_pkcs7_data_parse (signed_data, appendedsig_pkcs7_size, &sig->pkcs7);
|
||
}
|
||
|
||
static grub_err_t
|
||
get_binary_hash (const grub_size_t binary_hash_size, const grub_uint8_t *data,
|
||
const grub_size_t data_size, grub_uint8_t *hash, grub_size_t *hash_size)
|
||
{
|
||
grub_packed_guid_t guid = { 0 };
|
||
|
||
/* support SHA256, SHA384 and SHA512 for binary hash */
|
||
if (binary_hash_size == SHA256_HASH_SIZE)
|
||
grub_memcpy (&guid, &GRUB_PKS_CERT_SHA256_GUID, GRUB_PACKED_GUID_SIZE);
|
||
else if (binary_hash_size == SHA384_HASH_SIZE)
|
||
grub_memcpy (&guid, &GRUB_PKS_CERT_SHA384_GUID, GRUB_PACKED_GUID_SIZE);
|
||
else if (binary_hash_size == SHA512_HASH_SIZE)
|
||
grub_memcpy (&guid, &GRUB_PKS_CERT_SHA512_GUID, GRUB_PACKED_GUID_SIZE);
|
||
else
|
||
{
|
||
grub_dprintf ("appendedsig", "unsupported hash type (%" PRIuGRUB_SIZE ") and "
|
||
"skipped\n", binary_hash_size);
|
||
return GRUB_ERR_UNKNOWN_COMMAND;
|
||
}
|
||
|
||
return get_hash (&guid, data, data_size, hash, hash_size);
|
||
}
|
||
|
||
/*
|
||
* Verify binary hash against the db and dbx list.
|
||
* The following errors can occur:
|
||
* - GRUB_ERR_BAD_SIGNATURE: indicates that the hash is in dbx list.
|
||
* - GRUB_ERR_EOF: the hash could not be found in the db and dbx list.
|
||
* - GRUB_ERR_NONE: the hash is found in db list.
|
||
*/
|
||
static grub_err_t
|
||
verify_binary_hash (const grub_uint8_t *data, const grub_size_t data_size)
|
||
{
|
||
grub_err_t rc = GRUB_ERR_NONE;
|
||
grub_uint32_t i;
|
||
grub_size_t hash_size = 0;
|
||
grub_uint8_t hash[GRUB_MAX_HASH_LEN] = { 0 };
|
||
|
||
for (i = 0; i < dbx.hash_entries; i++)
|
||
{
|
||
if (dbx.hashes[i] == NULL)
|
||
continue;
|
||
|
||
rc = get_binary_hash (dbx.hash_sizes[i], data, data_size, hash, &hash_size);
|
||
if (rc != GRUB_ERR_NONE)
|
||
continue;
|
||
|
||
if (hash_size == dbx.hash_sizes[i] &&
|
||
grub_memcmp (dbx.hashes[i], hash, hash_size) == 0)
|
||
{
|
||
grub_dprintf ("appendedsig", "the hash (%02x%02x%02x%02x) is present in the dbx list\n",
|
||
hash[0], hash[1], hash[2], hash[3]);
|
||
return GRUB_ERR_BAD_SIGNATURE;
|
||
}
|
||
}
|
||
|
||
for (i = 0; i < db.hash_entries; i++)
|
||
{
|
||
if (db.hashes[i] == NULL)
|
||
continue;
|
||
|
||
rc = get_binary_hash (db.hash_sizes[i], data, data_size, hash, &hash_size);
|
||
if (rc != GRUB_ERR_NONE)
|
||
continue;
|
||
|
||
if (hash_size == db.hash_sizes[i] &&
|
||
grub_memcmp (db.hashes[i], hash, hash_size) == 0)
|
||
{
|
||
grub_dprintf ("appendedsig", "verified with a trusted hash (%02x%02x%02x%02x)\n",
|
||
hash[0], hash[1], hash[2], hash[3]);
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
}
|
||
|
||
return GRUB_ERR_EOF;
|
||
}
|
||
|
||
/*
|
||
* Given a hash value 'hval', of hash specification 'hash', prepare the
|
||
* S-expressions (sexp) and perform the signature verification.
|
||
*/
|
||
static grub_err_t
|
||
verify_signature (const gcry_mpi_t *pkmpi, const gcry_mpi_t hmpi,
|
||
const gcry_md_spec_t *hash, const grub_uint8_t *hval)
|
||
{
|
||
gcry_sexp_t hsexp, pubkey, sig;
|
||
grub_size_t errof;
|
||
|
||
if (_gcry_sexp_build (&hsexp, &errof, "(data (flags %s) (hash %s %b))", "pkcs1",
|
||
hash->name, hash->mdlen, hval) != GPG_ERR_NO_ERROR)
|
||
return GRUB_ERR_BAD_SIGNATURE;
|
||
|
||
if (_gcry_sexp_build (&pubkey, &errof, "(public-key (dsa (n %M) (e %M)))",
|
||
pkmpi[0], pkmpi[1]) != GPG_ERR_NO_ERROR)
|
||
return GRUB_ERR_BAD_SIGNATURE;
|
||
|
||
if (_gcry_sexp_build (&sig, &errof, "(sig-val (rsa (s %M)))", hmpi) != GPG_ERR_NO_ERROR)
|
||
return GRUB_ERR_BAD_SIGNATURE;
|
||
|
||
_gcry_sexp_dump (sig);
|
||
_gcry_sexp_dump (hsexp);
|
||
_gcry_sexp_dump (pubkey);
|
||
|
||
if (grub_crypto_pk_rsa->verify (sig, hsexp, pubkey) != GPG_ERR_NO_ERROR)
|
||
return GRUB_ERR_BAD_SIGNATURE;
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_verify_appended_signature (const grub_uint8_t *buf, grub_size_t bufsize)
|
||
{
|
||
grub_err_t err;
|
||
grub_size_t datasize;
|
||
void *context;
|
||
grub_uint8_t *hash;
|
||
grub_x509_cert_t *pk;
|
||
sb_appendedsig_t sig;
|
||
grub_pkcs7_signer_t *si;
|
||
grub_int32_t i;
|
||
|
||
if (!db.cert_entries && !db.hash_entries)
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "no trusted keys to verify against");
|
||
|
||
err = extract_appended_signature (buf, bufsize, &sig);
|
||
if (err != GRUB_ERR_NONE)
|
||
return err;
|
||
|
||
append_sig_len = sig.signature_len;
|
||
datasize = bufsize - sig.signature_len;
|
||
|
||
/*
|
||
* If signature verification is enabled with dynamic key management mode,
|
||
* Verify binary hash against the db and dbx list.
|
||
*/
|
||
if (append_key_mgmt == true)
|
||
{
|
||
err = verify_binary_hash (buf, datasize);
|
||
if (err == GRUB_ERR_BAD_SIGNATURE)
|
||
{
|
||
grub_pkcs7_data_release (&sig.pkcs7);
|
||
return grub_error (err,
|
||
"failed to verify the binary hash against a trusted binary hash");
|
||
}
|
||
}
|
||
|
||
/* Verify signature using trusted keys from db list. */
|
||
for (i = 0; i < sig.pkcs7.signer_count; i++)
|
||
{
|
||
si = &sig.pkcs7.signers[i];
|
||
context = grub_zalloc (si->hash->contextsize);
|
||
if (context == NULL)
|
||
return grub_errno;
|
||
|
||
si->hash->init (context, 0);
|
||
si->hash->write (context, buf, datasize);
|
||
si->hash->final (context);
|
||
hash = si->hash->read (context);
|
||
|
||
grub_dprintf ("appendedsig", "data size %" PRIuGRUB_SIZE ", signer %d hash %02x%02x%02x%02x...\n",
|
||
datasize, i, hash[0], hash[1], hash[2], hash[3]);
|
||
|
||
for (pk = db.certs; pk != NULL; pk = pk->next)
|
||
{
|
||
err = verify_signature (pk->mpis, si->sig_mpi, si->hash, hash);
|
||
if (err == GRUB_ERR_NONE)
|
||
{
|
||
grub_dprintf ("appendedsig", "verify signer %d with key '%s' succeeded\n",
|
||
i, pk->subject);
|
||
break;
|
||
}
|
||
|
||
grub_dprintf ("appendedsig", "verify signer %d with key '%s' failed\n",
|
||
i, pk->subject);
|
||
}
|
||
|
||
grub_free (context);
|
||
if (err == GRUB_ERR_NONE)
|
||
break;
|
||
}
|
||
|
||
grub_pkcs7_data_release (&sig.pkcs7);
|
||
|
||
if (err != GRUB_ERR_NONE)
|
||
return grub_error (err, "failed to verify signature against a trusted key");
|
||
|
||
return err;
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_cmd_verify_signature (grub_command_t cmd __attribute__ ((unused)), int argc, char **args)
|
||
{
|
||
grub_file_t signed_file;
|
||
grub_err_t err;
|
||
grub_uint8_t *signed_data = NULL;
|
||
grub_size_t signed_data_size = 0;
|
||
|
||
if (argc != 1)
|
||
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
||
"a signed file is expected\nExample:\n\tappend_verify <SIGNED FILE>\n");
|
||
|
||
if (!grub_strlen (args[0]))
|
||
return grub_error (GRUB_ERR_BAD_FILENAME, "missing signed file");
|
||
|
||
grub_dprintf ("appendedsig", "verifying %s\n", args[0]);
|
||
|
||
signed_file = grub_file_open (args[0], GRUB_FILE_TYPE_VERIFY_SIGNATURE);
|
||
if (signed_file == NULL)
|
||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "could not open %s file", args[0]);
|
||
|
||
err = file_read_whole (signed_file, &signed_data, &signed_data_size);
|
||
if (err == GRUB_ERR_NONE)
|
||
{
|
||
err = grub_verify_appended_signature (signed_data, signed_data_size);
|
||
grub_free (signed_data);
|
||
}
|
||
|
||
grub_file_close (signed_file);
|
||
|
||
return err;
|
||
}
|
||
|
||
/*
|
||
* Checks the trusted certificate against dbx list if dynamic key management is
|
||
* enabled. And add it to the db list if it is not already present.
|
||
*
|
||
* Note: When signature verification is enabled, this command only accepts the
|
||
* trusted certificate that is signed with an appended signature.
|
||
* The signature is verified by the appendedsig module. If verification succeeds,
|
||
* the certificate is added to the db list. Otherwise, an error is posted and
|
||
* the certificate is not added.
|
||
* When signature verification is disabled, it accepts the trusted certificate
|
||
* without an appended signature and add it to the db list.
|
||
*
|
||
* Also, note that the adding of the trusted certificate using this command does
|
||
* not persist across reboots.
|
||
*/
|
||
static grub_err_t
|
||
grub_cmd_db_cert (grub_command_t cmd __attribute__ ((unused)), int argc, char **args)
|
||
{
|
||
grub_err_t err;
|
||
grub_file_t cert_file;
|
||
grub_uint8_t *cert_data = NULL;
|
||
grub_size_t cert_data_size = 0;
|
||
|
||
if (argc != 1)
|
||
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
||
"a trusted X.509 certificate file is expected in DER format\n"
|
||
"Example:\n\tappend_add_db_cert <X509_CERTIFICATE>\n");
|
||
|
||
if (!grub_strlen (args[0]))
|
||
return grub_error (GRUB_ERR_BAD_FILENAME, "missing trusted X.509 certificate file");
|
||
|
||
cert_file = grub_file_open (args[0],
|
||
GRUB_FILE_TYPE_CERTIFICATE_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
|
||
if (cert_file == NULL)
|
||
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "could not open %s file", args[0]);
|
||
|
||
err = file_read_whole (cert_file, &cert_data, &cert_data_size);
|
||
grub_file_close (cert_file);
|
||
if (err != GRUB_ERR_NONE)
|
||
return err;
|
||
|
||
/*
|
||
* If signature verification is enabled (check_sigs is set to true), obtain
|
||
* the actual certificate size by subtracting the appended signature size from
|
||
* the certificate size because the certificate has an appended signature, and
|
||
* this actual certificate size is used to get the X.509 certificate.
|
||
*/
|
||
if (check_sigs == true)
|
||
cert_data_size -= append_sig_len;
|
||
|
||
err = add_certificate (cert_data, cert_data_size, &db);
|
||
grub_free (cert_data);
|
||
|
||
return err;
|
||
}
|
||
|
||
/*
|
||
* Remove the distrusted certificate from the db list if it is already present.
|
||
* And add it to the dbx list if not present when dynamic key management is
|
||
* enabled.
|
||
*
|
||
* Note: When signature verification is enabled, this command only accepts the
|
||
* distrusted certificate that is signed with an appended signature.
|
||
* The signature is verified by the appended sig module. If verification
|
||
* succeeds, the certificate is removed from the db list. Otherwise, an error
|
||
* is posted and the certificate is not removed.
|
||
* When signature verification is disabled, it accepts the distrusted certificate
|
||
* without an appended signature and removes it from the db list.
|
||
*
|
||
* Also, note that the removal of the distrusted certificate using this command
|
||
* does not persist across reboots.
|
||
*/
|
||
static grub_err_t
|
||
grub_cmd_dbx_cert (grub_command_t cmd __attribute__ ((unused)), int argc, char **args)
|
||
{
|
||
grub_err_t err;
|
||
grub_file_t cert_file;
|
||
grub_uint8_t *cert_data = NULL;
|
||
grub_size_t cert_data_size = 0;
|
||
|
||
if (argc != 1)
|
||
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
||
"a distrusted X.509 certificate file is expected in DER format\n"
|
||
"Example:\n\tappend_add_dbx_cert <X509_CERTIFICATE>\n");
|
||
|
||
if (!grub_strlen (args[0]))
|
||
return grub_error (GRUB_ERR_BAD_FILENAME, "missing distrusted X.509 certificate file");
|
||
|
||
cert_file = grub_file_open (args[0],
|
||
GRUB_FILE_TYPE_CERTIFICATE_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
|
||
if (cert_file == NULL)
|
||
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "could not open %s file", args[0]);
|
||
|
||
err = file_read_whole (cert_file, &cert_data, &cert_data_size);
|
||
grub_file_close (cert_file);
|
||
if (err != GRUB_ERR_NONE)
|
||
return err;
|
||
|
||
/*
|
||
* If signature verification is enabled (check_sigs is set to true), obtain
|
||
* the actual certificate size by subtracting the appended signature size from
|
||
* the certificate size because the certificate has an appended signature, and
|
||
* this actual certificate size is used to get the X.509 certificate.
|
||
*/
|
||
if (check_sigs == true)
|
||
cert_data_size -= append_sig_len;
|
||
|
||
/* Remove distrusted certificate from the db list if present. */
|
||
err = remove_cert_from_db (cert_data, cert_data_size);
|
||
if (err != GRUB_ERR_NONE)
|
||
{
|
||
grub_free (cert_data);
|
||
return err;
|
||
}
|
||
|
||
/* Only add the certificate to the dbx list if dynamic key management is enabled. */
|
||
if (append_key_mgmt == true)
|
||
err = add_certificate (cert_data, cert_data_size, &dbx);
|
||
|
||
grub_free (cert_data);
|
||
|
||
return err;
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_cmd_list_db (grub_command_t cmd __attribute__ ((unused)), int argc __attribute__ ((unused)),
|
||
char **args __attribute__ ((unused)))
|
||
{
|
||
struct x509_certificate *cert;
|
||
grub_uint32_t i, cert_num = 1;
|
||
|
||
for (cert = db.certs; cert != NULL; cert = cert->next, cert_num++)
|
||
print_certificate (cert, cert_num);
|
||
|
||
if (append_key_mgmt == false)
|
||
return GRUB_ERR_NONE;
|
||
|
||
for (i = 0; i < db.hash_entries; i++)
|
||
{
|
||
if (db.hashes[i] != NULL)
|
||
{
|
||
grub_printf ("\nBinary hash: %u\n", i + 1);
|
||
grub_printf (" Hash: sha%" PRIuGRUB_SIZE "\n ", db.hash_sizes[i] * 8);
|
||
hexdump_colon (db.hashes[i], db.hash_sizes[i]);
|
||
}
|
||
}
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_cmd_list_dbx (grub_command_t cmd __attribute__((unused)),
|
||
int argc __attribute__((unused)), char **args __attribute__((unused)))
|
||
{
|
||
struct x509_certificate *cert;
|
||
grub_uint32_t i, cert_num = 1;
|
||
|
||
if (append_key_mgmt == false)
|
||
return grub_error (GRUB_ERR_ACCESS_DENIED,
|
||
"append_list_dbx command is unsupported in static key mode");
|
||
|
||
for (cert = dbx.certs; cert != NULL; cert = cert->next, cert_num++)
|
||
print_certificate (cert, cert_num);
|
||
|
||
for (i = 0; i < dbx.hash_entries; i++)
|
||
{
|
||
if (dbx.hashes[i] != NULL)
|
||
{
|
||
grub_printf ("\nCertificate/Binary hash: %u\n", i + 1);
|
||
grub_printf (" Hash: sha%" PRIuGRUB_SIZE "\n ", dbx.hash_sizes[i] * 8);
|
||
hexdump_colon (dbx.hashes[i], dbx.hash_sizes[i]);
|
||
}
|
||
}
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
/*
|
||
* Remove the trusted binary hash from the dbx list if present. And add them to
|
||
* the db list if it is not already present.
|
||
*
|
||
* Note: When signature verification is enabled, this command only accepts the
|
||
* binary hash file that is signed with an appended signature. The signature is
|
||
* verified by the appendedsig module. If verification succeeds, the binary hash
|
||
* is added to the db list. Otherwise, an error is posted and the binary hash is
|
||
* not added.
|
||
* When signature verification is disabled, it accepts the binary hash file
|
||
* without an appended signature and adds it to the db list.
|
||
*
|
||
* Also, note that the adding of the trusted binary hash using this command does
|
||
* not persist across reboots.
|
||
*/
|
||
static grub_err_t
|
||
grub_cmd_add_db_hash (grub_command_t cmd __attribute__((unused)), int argc, char**args)
|
||
{
|
||
grub_err_t rc;
|
||
grub_file_t hash_file;
|
||
grub_uint8_t *hash_data = NULL;
|
||
grub_size_t hash_data_size = 0;
|
||
|
||
if (append_key_mgmt == false)
|
||
return grub_error (GRUB_ERR_ACCESS_DENIED,
|
||
"append_add_db_hash command is unsupported in static key mode");
|
||
|
||
if (argc != 1)
|
||
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
||
"a trusted binary hash file is expected in binary format\n"
|
||
"Example:\n\tappend_add_db_hash <BINARY HASH FILE>\n");
|
||
|
||
if (!grub_strlen (args[0]))
|
||
return grub_error (GRUB_ERR_BAD_FILENAME, "missing trusted binary hash file");
|
||
|
||
hash_file = grub_file_open (args[0], GRUB_FILE_TYPE_HASH_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
|
||
if (hash_file == NULL)
|
||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "unable to open %s file", args[0]);
|
||
|
||
rc = file_read_whole (hash_file, &hash_data, &hash_data_size);
|
||
grub_file_close (hash_file);
|
||
if (rc != GRUB_ERR_NONE)
|
||
return rc;
|
||
|
||
/*
|
||
* If signature verification is enabled (check_sigs is set to true), obtain
|
||
* the actual hash data size by subtracting the appended signature size from
|
||
* the hash data size because the hash has an appended signature, and this
|
||
* actual hash data size is used to get the hash data.
|
||
*/
|
||
if (check_sigs == true)
|
||
hash_data_size -= append_sig_len;
|
||
|
||
grub_dprintf ("appendedsig",
|
||
"adding a trusted binary hash %02x%02x%02x%02x... with size of %" PRIuGRUB_SIZE "\n",
|
||
hash_data[0], hash_data[1], hash_data[2], hash_data[3], hash_data_size);
|
||
|
||
/* Only accept SHA256, SHA384 and SHA512 binary hash */
|
||
if (hash_data_size != SHA256_HASH_SIZE && hash_data_size != SHA384_HASH_SIZE &&
|
||
hash_data_size != SHA512_HASH_SIZE)
|
||
{
|
||
grub_free (hash_data);
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE, "unacceptable trusted binary hash type");
|
||
}
|
||
|
||
rc = add_hash (hash_data, hash_data_size, &db);
|
||
grub_free (hash_data);
|
||
|
||
return rc;
|
||
}
|
||
|
||
/*
|
||
* Remove the distrusted binary/certificate hash from the db list if present.
|
||
* And add them to the dbx list if it is not already present.
|
||
*
|
||
* Note: When signature verification is enabled, this command only accepts the
|
||
* binary/certificate hash file that is signed with an appended signature. The
|
||
* signature is verified by the appendedsig module. If verification succeeds,
|
||
* the binary/certificate hash is added to the dbx list. Otherwise, an error is
|
||
* posted and the binary/certificate hash is not added.
|
||
* When signature verification is disabled, it accepts the binary/certificate
|
||
* hash file without an appended signature and adds it to the dbx list.
|
||
*
|
||
* Also, note that the adding of the distrusted binary/certificate hash using
|
||
* this command does not persist across reboots.
|
||
*/
|
||
static grub_err_t
|
||
grub_cmd_add_dbx_hash (grub_extcmd_context_t ctxt, int argc __attribute__ ((unused)),
|
||
char **args __attribute__ ((unused)))
|
||
{
|
||
grub_err_t rc;
|
||
grub_file_t hash_file;
|
||
grub_uint8_t *hash_data = NULL;
|
||
grub_size_t hash_data_size = 0;
|
||
char *file_path;
|
||
|
||
if (append_key_mgmt == false)
|
||
return grub_error (GRUB_ERR_ACCESS_DENIED,
|
||
"append_add_dbx_hash command is unsupported in static key mode");
|
||
|
||
if (!ctxt->state[OPTION_BINARY_HASH].set && !ctxt->state[OPTION_CERT_HASH].set)
|
||
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
||
"a distrusted certificate/binary hash file is expected in binary format\n"
|
||
"Example:\n\tappend_add_dbx_hash [option] <FILE>\n"
|
||
"option:\n[-b|--binary-hash] FILE [BINARY HASH FILE]\n"
|
||
"[-c|--cert-hash] FILE [CERTFICATE HASH FILE]\n");
|
||
|
||
if (ctxt->state[OPTION_BINARY_HASH].arg == NULL && ctxt->state[OPTION_CERT_HASH].arg == NULL)
|
||
return grub_error (GRUB_ERR_BAD_FILENAME, "missing distrusted certificate/binary hash file");
|
||
|
||
if (ctxt->state[OPTION_BINARY_HASH].arg != NULL)
|
||
file_path = ctxt->state[OPTION_BINARY_HASH].arg;
|
||
else
|
||
file_path = ctxt->state[OPTION_CERT_HASH].arg;
|
||
|
||
hash_file = grub_file_open (file_path, GRUB_FILE_TYPE_HASH_TRUST | GRUB_FILE_TYPE_NO_DECOMPRESS);
|
||
if (hash_file == NULL)
|
||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "unable to open %s file", file_path);
|
||
|
||
rc = file_read_whole (hash_file, &hash_data, &hash_data_size);
|
||
grub_file_close (hash_file);
|
||
if (rc != GRUB_ERR_NONE)
|
||
return rc;
|
||
|
||
/*
|
||
* If signature verification is enabled (check_sigs is set to true), obtain
|
||
* the actual hash data size by subtracting the appended signature size from
|
||
* the hash data size because the hash has an appended signature, and this
|
||
* actual hash data size is used to get the hash data.
|
||
*/
|
||
if (check_sigs == true)
|
||
hash_data_size -= append_sig_len;
|
||
|
||
grub_dprintf ("appendedsig",
|
||
"adding a distrusted certificate/binary hash %02x%02x%02x%02x..."
|
||
" with size of %" PRIuGRUB_SIZE "\n", hash_data[0], hash_data[1],
|
||
hash_data[2], hash_data[3], hash_data_size);
|
||
|
||
if (ctxt->state[OPTION_BINARY_HASH].set || ctxt->state[OPTION_CERT_HASH].set)
|
||
{
|
||
/* Only accept SHA256, SHA384 and SHA512 certificate/binary hash */
|
||
if (hash_data_size != SHA256_HASH_SIZE && hash_data_size != SHA384_HASH_SIZE &&
|
||
hash_data_size != SHA512_HASH_SIZE)
|
||
{
|
||
grub_free (hash_data);
|
||
return grub_error (GRUB_ERR_BAD_SIGNATURE,
|
||
"unacceptable distrusted certificate/binary hash type");
|
||
}
|
||
}
|
||
|
||
/* Remove distrusted binary hash/certificate from the db list if present. */
|
||
remove_hash_from_db (hash_data, hash_data_size,
|
||
(ctxt->state[OPTION_BINARY_HASH].set) ? true : false);
|
||
rc = add_hash (hash_data, hash_data_size, &dbx);
|
||
grub_free (hash_data);
|
||
|
||
return rc;
|
||
}
|
||
|
||
/* Add the X.509 certificates/binary hash to the db list from PKS. */
|
||
static grub_err_t
|
||
load_pks2db (void)
|
||
{
|
||
grub_err_t rc;
|
||
grub_uint32_t i;
|
||
|
||
for (i = 0; i < pks_keystore->db_entries; i++)
|
||
{
|
||
if (is_hash (&pks_keystore->db[i].guid) == true)
|
||
{
|
||
rc = add_hash (pks_keystore->db[i].data,
|
||
pks_keystore->db[i].data_size, &db);
|
||
if (rc == GRUB_ERR_OUT_OF_MEMORY)
|
||
return rc;
|
||
}
|
||
else if (is_x509 (&pks_keystore->db[i].guid) == true)
|
||
{
|
||
rc = add_certificate (pks_keystore->db[i].data,
|
||
pks_keystore->db[i].data_size, &db);
|
||
if (rc == GRUB_ERR_OUT_OF_MEMORY)
|
||
return rc;
|
||
}
|
||
else
|
||
grub_dprintf ("appendedsig", "unsupported signature data type and "
|
||
"skipped (%u)\n", i + 1);
|
||
}
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
/* Add the certificates and certificate/binary hash to the dbx list from PKS. */
|
||
static grub_err_t
|
||
load_pks2dbx (void)
|
||
{
|
||
grub_err_t rc;
|
||
grub_uint32_t i;
|
||
|
||
for (i = 0; i < pks_keystore->dbx_entries; i++)
|
||
{
|
||
if (is_x509 (&pks_keystore->dbx[i].guid) == true)
|
||
{
|
||
rc = add_certificate (pks_keystore->dbx[i].data,
|
||
pks_keystore->dbx[i].data_size, &dbx);
|
||
if (rc == GRUB_ERR_OUT_OF_MEMORY)
|
||
return rc;
|
||
}
|
||
else if (is_hash (&pks_keystore->dbx[i].guid) == true)
|
||
{
|
||
rc = add_hash (pks_keystore->dbx[i].data,
|
||
pks_keystore->dbx[i].data_size, &dbx);
|
||
if (rc != GRUB_ERR_NONE)
|
||
return rc;
|
||
}
|
||
else
|
||
grub_dprintf ("appendedsig", "unsupported signature data type and "
|
||
"skipped (%u)\n", i + 1);
|
||
}
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
/*
|
||
* Extract the X.509 certificates from the ELF Note header, parse it, and add
|
||
* it to the db list.
|
||
*/
|
||
static void
|
||
load_elf2db (void)
|
||
{
|
||
grub_err_t err;
|
||
struct grub_module_header *header;
|
||
struct grub_file pseudo_file;
|
||
grub_uint8_t *cert_data = NULL;
|
||
grub_size_t cert_data_size = 0;
|
||
|
||
FOR_MODULES (header)
|
||
{
|
||
/* Not an X.509 certificate, skip. */
|
||
if (header->type != OBJ_TYPE_X509_PUBKEY)
|
||
continue;
|
||
|
||
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 X.509 certificate, size=%" PRIuGRUB_UINT64_T "\n",
|
||
pseudo_file.size);
|
||
|
||
err = file_read_whole (&pseudo_file, &cert_data, &cert_data_size);
|
||
if (err == GRUB_ERR_OUT_OF_MEMORY)
|
||
return;
|
||
else if (err != GRUB_ERR_NONE)
|
||
continue;
|
||
|
||
err = add_certificate (cert_data, cert_data_size, &db);
|
||
grub_free (cert_data);
|
||
if (err == GRUB_ERR_OUT_OF_MEMORY)
|
||
return;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Extract trusted and distrusted keys from PKS and store them in the db and
|
||
* dbx list.
|
||
*/
|
||
static void
|
||
create_dbs_from_pks (void)
|
||
{
|
||
grub_err_t err;
|
||
|
||
err = load_pks2dbx ();
|
||
if (err != GRUB_ERR_NONE)
|
||
grub_printf ("warning: dbx list might not be fully populated\n");
|
||
|
||
/*
|
||
* If db does not exist in the PKS storage, then read the static keys as a db
|
||
* default keys from the GRUB ELF Note and add them into the db list.
|
||
*/
|
||
if (pks_keystore->db_exists == false)
|
||
load_elf2db ();
|
||
else
|
||
{
|
||
err = load_pks2db ();
|
||
if (err != GRUB_ERR_NONE)
|
||
grub_printf ("warning: db list might not be fully populated\n");
|
||
}
|
||
|
||
grub_pks_free_data ();
|
||
grub_dprintf ("appendedsig", "the db list now has %u keys\n"
|
||
"the dbx list now has %u keys\n",
|
||
db.hash_entries + db.cert_entries,
|
||
dbx.hash_entries + dbx.cert_entries);
|
||
}
|
||
|
||
/* Free db list memory */
|
||
static void
|
||
free_db_list (void)
|
||
{
|
||
grub_x509_cert_t *cert;
|
||
grub_uint32_t i;
|
||
|
||
while (db.certs != NULL)
|
||
{
|
||
cert = db.certs;
|
||
db.certs = db.certs->next;
|
||
grub_x509_cert_release (cert);
|
||
grub_free (cert);
|
||
}
|
||
|
||
for (i = 0; i < db.hash_entries; i++)
|
||
grub_free (db.hashes[i]);
|
||
|
||
grub_free (db.hashes);
|
||
grub_free (db.hash_sizes);
|
||
grub_memset (&db, 0, sizeof (sb_database_t));
|
||
}
|
||
|
||
/* Free dbx list memory */
|
||
static void
|
||
free_dbx_list (void)
|
||
{
|
||
grub_x509_cert_t *cert;
|
||
grub_uint32_t i;
|
||
|
||
while (dbx.certs != NULL)
|
||
{
|
||
cert = dbx.certs;
|
||
dbx.certs = dbx.certs->next;
|
||
grub_x509_cert_release (cert);
|
||
grub_free (cert);
|
||
}
|
||
|
||
for (i = 0; i < dbx.hash_entries; i++)
|
||
grub_free (dbx.hashes[i]);
|
||
|
||
grub_free (dbx.hashes);
|
||
grub_free (dbx.hash_sizes);
|
||
grub_memset (&dbx, 0, sizeof (sb_database_t));
|
||
}
|
||
|
||
static const char *
|
||
grub_env_read_sec (struct grub_env_var *var __attribute__ ((unused)),
|
||
const char *val __attribute__ ((unused)))
|
||
{
|
||
if (check_sigs == true)
|
||
return "yes";
|
||
|
||
return "no";
|
||
}
|
||
|
||
static char *
|
||
grub_env_write_sec (struct grub_env_var *var __attribute__ ((unused)), const char *val)
|
||
{
|
||
char *ret;
|
||
|
||
/*
|
||
* Do not allow the value to be changed if signature verification is enabled
|
||
* (check_sigs is set to true) and GRUB is locked down.
|
||
*/
|
||
if (check_sigs == true && grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
|
||
{
|
||
ret = grub_strdup ("yes");
|
||
if (ret == NULL)
|
||
grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not duplicate a string enforce");
|
||
|
||
return ret;
|
||
}
|
||
|
||
if (grub_strcmp (val, "yes") == 0)
|
||
check_sigs = true;
|
||
else if (grub_strcmp (val, "no") == 0)
|
||
check_sigs = false;
|
||
|
||
ret = grub_strdup (grub_env_read_sec (NULL, NULL));
|
||
if (ret == NULL)
|
||
grub_error (GRUB_ERR_OUT_OF_MEMORY, "could not duplicate a string %s",
|
||
grub_env_read_sec (NULL, NULL));
|
||
|
||
return ret;
|
||
}
|
||
|
||
static const char *
|
||
grub_env_read_key_mgmt (struct grub_env_var *var __attribute__ ((unused)),
|
||
const char *val __attribute__ ((unused)))
|
||
{
|
||
if (append_key_mgmt == true)
|
||
return "dynamic";
|
||
|
||
return "static";
|
||
}
|
||
|
||
static char *
|
||
grub_env_write_key_mgmt (struct grub_env_var *var __attribute__ ((unused)), const char *val)
|
||
{
|
||
char *ret;
|
||
|
||
/*
|
||
* Do not allow the value to be changed if signature verification is enabled
|
||
* (check_sigs is set to true) and GRUB is locked down.
|
||
*/
|
||
if (check_sigs == true && grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
|
||
{
|
||
ret = grub_strdup (grub_env_read_key_mgmt (NULL, NULL));
|
||
if (ret == NULL)
|
||
grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
|
||
return ret;
|
||
}
|
||
|
||
if (grub_strcmp (val, "dynamic") == 0)
|
||
append_key_mgmt = true;
|
||
else if (grub_strcmp (val, "static") == 0)
|
||
append_key_mgmt = false;
|
||
|
||
ret = grub_strdup (grub_env_read_key_mgmt (NULL, NULL));
|
||
if (ret == NULL)
|
||
grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
|
||
|
||
return ret;
|
||
}
|
||
|
||
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 == false)
|
||
{
|
||
*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.
|
||
*/
|
||
case GRUB_FILE_TYPE_HASH_TRUST:
|
||
/*
|
||
* This is a certificate/binary hash to add to db/dbx. This needs to be
|
||
* verified or blocked.
|
||
*/
|
||
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_command_t cmd_verify, cmd_list_db, cmd_dbx_cert, cmd_db_cert;
|
||
static grub_command_t cmd_list_dbx, cmd_db_hash;
|
||
static grub_extcmd_t cmd_dbx_hash;
|
||
|
||
GRUB_MOD_INIT (appendedsig)
|
||
{
|
||
grub_int32_t rc;
|
||
|
||
/*
|
||
* If secure boot is enabled with enforce mode and GRUB is locked down, enable
|
||
* signature verification.
|
||
*/
|
||
if (grub_is_lockdown () == GRUB_LOCKDOWN_ENABLED)
|
||
check_sigs = true;
|
||
|
||
/* If PKS keystore is available, use dynamic key management. */
|
||
pks_keystore = grub_pks_get_keystore ();
|
||
if (pks_keystore != NULL)
|
||
append_key_mgmt = true;
|
||
|
||
/*
|
||
* This is appended signature verification environment variable. It is
|
||
* automatically set to either "no" or "yes" based on the ’ibm,secure-boot’
|
||
* device tree property.
|
||
*
|
||
* "no": No signature verification. This is the default.
|
||
*
|
||
* "yes": Enforce signature verification. When GRUB is locked down, user cannot
|
||
* change the value by setting the check_appended_signatures variable
|
||
* back to ‘no’
|
||
*/
|
||
grub_register_variable_hook ("check_appended_signatures", grub_env_read_sec, grub_env_write_sec);
|
||
grub_env_export ("check_appended_signatures");
|
||
|
||
/*
|
||
* This is appended signature key management environment variable. It is
|
||
* automatically set to either "static" or "dynamic" based on the
|
||
* Platform KeyStore.
|
||
*
|
||
* "static": Enforce static key management signature verification. This is
|
||
* the default. When the GRUB is locked down, user cannot change
|
||
* the value by setting the appendedsig_key_mgmt variable back to
|
||
* "dynamic".
|
||
*
|
||
* "dynamic": Enforce dynamic key management signature verification. When the
|
||
* GRUB is locked down, user cannot change the value by setting the
|
||
* appendedsig_key_mgmt variable back to "static".
|
||
*/
|
||
grub_register_variable_hook ("appendedsig_key_mgmt", grub_env_read_key_mgmt, grub_env_write_key_mgmt);
|
||
grub_env_export ("appendedsig_key_mgmt");
|
||
|
||
rc = grub_asn1_init ();
|
||
if (rc != ASN1_SUCCESS)
|
||
grub_fatal ("error initing ASN.1 data structures: %d: %s\n", rc, asn1_strerror (rc));
|
||
|
||
/*
|
||
* If signature verification is enabled with the dynamic key management,
|
||
* extract trusted and distrusted keys from PKS and store them in the db
|
||
* and dbx list.
|
||
*/
|
||
if (append_key_mgmt == true)
|
||
create_dbs_from_pks ();
|
||
/*
|
||
* If signature verification is enabled with the static key management,
|
||
* extract trusted keys from ELF Note and store them in the db list.
|
||
*/
|
||
else
|
||
{
|
||
load_elf2db ();
|
||
grub_dprintf ("appendedsig", "the db list now has %u static keys\n",
|
||
db.cert_entries);
|
||
}
|
||
|
||
cmd_verify = grub_register_command ("append_verify", grub_cmd_verify_signature, N_("<SIGNED_FILE>"),
|
||
N_("Verify SIGNED_FILE against the trusted X.509 certificates in the db list"));
|
||
cmd_list_db = grub_register_command ("append_list_db", grub_cmd_list_db, 0,
|
||
N_("Show the list of trusted X.509 certificates from the db list"));
|
||
cmd_db_cert = grub_register_command ("append_add_db_cert", grub_cmd_db_cert, N_("<X509_CERTIFICATE>"),
|
||
N_("Add trusted X509_CERTIFICATE to the db list"));
|
||
cmd_dbx_cert = grub_register_command ("append_add_dbx_cert", grub_cmd_dbx_cert, N_("<X509_CERTIFICATE>"),
|
||
N_("Add distrusted X509_CERTIFICATE to the dbx list"));
|
||
|
||
cmd_list_dbx = grub_register_command ("append_list_dbx", grub_cmd_list_dbx, 0,
|
||
N_("Show the list of distrusted certificates and"
|
||
" certificate/binary hashes from the dbx list"));
|
||
cmd_db_hash = grub_register_command ("append_add_db_hash", grub_cmd_add_db_hash, N_("BINARY HASH FILE"),
|
||
N_("Add trusted BINARY HASH to the db list."));
|
||
cmd_dbx_hash = grub_register_extcmd ("append_add_dbx_hash", grub_cmd_add_dbx_hash, 0,
|
||
N_("[-b|--binary-hash] FILE [BINARY HASH FILE]\n"
|
||
"[-c|--cert-hash] FILE [CERTFICATE HASH FILE]"),
|
||
N_("Add distrusted CERTFICATE/BINARY HASH to the dbx list."), options);
|
||
|
||
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.
|
||
*/
|
||
|
||
free_db_list ();
|
||
free_dbx_list ();
|
||
grub_register_variable_hook ("check_appended_signatures", NULL, NULL);
|
||
grub_env_unset ("check_appended_signatures");
|
||
grub_register_variable_hook ("appendedsig_key_mgmt", NULL, NULL);
|
||
grub_env_unset ("appendedsig_key_mgmt");
|
||
grub_verifier_unregister (&grub_appendedsig_verifier);
|
||
grub_unregister_command (cmd_verify);
|
||
grub_unregister_command (cmd_list_db);
|
||
grub_unregister_command (cmd_db_cert);
|
||
grub_unregister_command (cmd_dbx_cert);
|
||
grub_unregister_command (cmd_list_dbx);
|
||
grub_unregister_command (cmd_db_hash);
|
||
grub_unregister_extcmd (cmd_dbx_hash);
|
||
}
|