| /* |
| * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com> |
| * |
| * This program 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. |
| * |
| * This program 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 this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
| * USA. |
| * |
| * In addition, as a special exception, the copyright holders give |
| * permission to link the code of portions of this program with the OpenSSL |
| * library under certain conditions as described in each individual source file, |
| * and distribute linked combinations including the two. |
| * |
| * You must obey the GNU General Public License in all respects for all |
| * of the code used other than OpenSSL. If you modify file(s) with this |
| * exception, you may extend this exception to your version of the |
| * file(s), but you are not obligated to do so. If you do not wish to do |
| * so, delete this exception statement from your version. If you delete |
| * this exception statement from all source files in the program, then |
| * also delete it here. |
| */ |
| #define _GNU_SOURCE |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <sys/statfs.h> |
| #include <sys/types.h> |
| |
| #include <getopt.h> |
| |
| #include <ccan/list/list.h> |
| #include <ccan/array_size/array_size.h> |
| #include <ccan/talloc/talloc.h> |
| |
| #include <openssl/conf.h> |
| #include <openssl/x509.h> |
| #include <openssl/err.h> |
| |
| #include "fileio.h" |
| #include "efivars.h" |
| |
| #define EFIVARS_MOUNTPOINT "/sys/firmware/efi/efivars" |
| #define PSTORE_FSTYPE 0x6165676C |
| #define EFIVARS_FSTYPE 0xde5e81e4 |
| |
| #define EFI_IMAGE_SECURITY_DATABASE_GUID \ |
| { 0xd719b2cb, 0x3d3a, 0x4596, \ |
| { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f } } |
| |
| static const char *toolname = "sbkeysync"; |
| |
| static const uint32_t sigdb_attrs = EFI_VARIABLE_NON_VOLATILE | |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | |
| EFI_VARIABLE_RUNTIME_ACCESS | |
| EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | |
| EFI_VARIABLE_APPEND_WRITE; |
| |
| struct key_database_type { |
| const char *name; |
| EFI_GUID guid; |
| }; |
| |
| struct key_database_type keydb_types[] = { |
| { "PK", EFI_GLOBAL_VARIABLE }, |
| { "KEK", EFI_GLOBAL_VARIABLE }, |
| { "db", EFI_IMAGE_SECURITY_DATABASE_GUID }, |
| { "dbx", EFI_IMAGE_SECURITY_DATABASE_GUID }, |
| }; |
| |
| enum keydb_type { |
| KEYDB_PK = 0, |
| KEYDB_KEK = 1, |
| KEYDB_DB = 2, |
| KEYDB_DBX = 3, |
| }; |
| |
| static const char *default_keystore_dirs[] = { |
| "/etc/secureboot/keys", |
| "/usr/share/secureboot/keys", |
| }; |
| |
| |
| struct key { |
| EFI_GUID type; |
| int id_len; |
| uint8_t *id; |
| |
| char *description; |
| |
| struct list_node list; |
| |
| /* set for keys loaded from a filesystem keystore */ |
| struct fs_keystore_entry *keystore_entry; |
| }; |
| |
| typedef int (*key_parse_func)(struct key *, uint8_t *, size_t); |
| |
| struct cert_type { |
| EFI_GUID guid; |
| key_parse_func parse; |
| }; |
| |
| struct key_database { |
| const struct key_database_type *type; |
| struct list_head keys; |
| }; |
| |
| struct keyset { |
| struct key_database pk; |
| struct key_database kek; |
| struct key_database db; |
| struct key_database dbx; |
| }; |
| |
| struct fs_keystore_entry { |
| const struct key_database_type *type; |
| const char *root; |
| const char *name; |
| uint8_t *data; |
| size_t len; |
| struct list_node keystore_list; |
| struct list_node new_list; |
| }; |
| |
| struct fs_keystore { |
| struct list_head keys; |
| }; |
| |
| struct sync_context { |
| const char *efivars_dir; |
| struct keyset *filesystem_keys; |
| struct keyset *firmware_keys; |
| struct fs_keystore *fs_keystore; |
| const char **keystore_dirs; |
| unsigned int n_keystore_dirs; |
| struct list_head new_keys; |
| bool verbose; |
| bool dry_run; |
| bool set_pk; |
| }; |
| |
| |
| #define GUID_STRLEN (8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1) |
| static void guid_to_str(const EFI_GUID *guid, char *str) |
| { |
| snprintf(str, GUID_STRLEN, |
| "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
| guid->Data1, guid->Data2, guid->Data3, |
| guid->Data4[0], guid->Data4[1], |
| guid->Data4[2], guid->Data4[3], |
| guid->Data4[4], guid->Data4[5], |
| guid->Data4[6], guid->Data4[7]); |
| } |
| |
| static int sha256_key_parse(struct key *key, uint8_t *data, size_t len) |
| { |
| const unsigned int sha256_id_size = 256 / 8; |
| unsigned int i; |
| |
| if (len != sha256_id_size) |
| return -1; |
| |
| key->id = talloc_memdup(key, data, sha256_id_size); |
| key->id_len = sha256_id_size; |
| |
| key->description = talloc_array(key, char, len * 2 + 1); |
| for (i = 0; i < len; i++) |
| snprintf(&key->description[i*2], 3, "%02x", data[i]); |
| key->description[len*2] = '\0'; |
| |
| return 0; |
| } |
| |
| static int x509_key_parse(struct key *key, uint8_t *data, size_t len) |
| { |
| const int description_len = 160; |
| ASN1_INTEGER *serial; |
| const uint8_t *tmp; |
| X509 *x509; |
| int rc; |
| |
| rc = -1; |
| |
| tmp = data; |
| |
| x509 = d2i_X509(NULL, &tmp, len); |
| if (!x509) |
| return -1; |
| |
| /* we use the X509 serial number as the key ID */ |
| serial = X509_get_serialNumber(x509); |
| if (!serial) |
| goto out; |
| |
| key->id_len = ASN1_STRING_length(serial); |
| key->id = talloc_memdup(key, ASN1_STRING_data(serial), key->id_len); |
| |
| key->description = talloc_array(key, char, description_len); |
| X509_NAME_oneline(X509_get_subject_name(x509), |
| key->description, description_len); |
| |
| rc = 0; |
| |
| out: |
| X509_free(x509); |
| return rc; |
| } |
| |
| struct cert_type cert_types[] = { |
| { EFI_CERT_SHA256_GUID, sha256_key_parse }, |
| { EFI_CERT_X509_GUID, x509_key_parse }, |
| }; |
| |
| static int guidcmp(const EFI_GUID *a, const EFI_GUID *b) |
| { |
| return memcmp(a, b, sizeof(EFI_GUID)); |
| } |
| |
| static int key_parse(struct key *key, const EFI_GUID *type, |
| uint8_t *data, size_t len) |
| { |
| char guid_str[GUID_STRLEN]; |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(cert_types); i++) { |
| if (guidcmp(&cert_types[i].guid, type)) |
| continue; |
| |
| return cert_types[i].parse(key, data, len); |
| } |
| |
| guid_to_str(type, guid_str); |
| printf("warning: unknown signature type found:\n %s\n", |
| guid_str); |
| return -1; |
| |
| } |
| |
| typedef int (*sigdata_fn)(EFI_SIGNATURE_DATA *, int, const EFI_GUID *, void *); |
| |
| /** |
| * Iterates an buffer of EFI_SIGNATURE_LISTs (at db_data, of length len), |
| * and calls fn on each EFI_SIGNATURE_DATA item found. |
| * |
| * fn is passed the EFI_SIGNATURE_DATA pointer, and the length of the |
| * signature data (including GUID header), the type of the signature list, |
| * and a context pointer. |
| */ |
| static int sigdb_iterate(void *db_data, size_t len, |
| sigdata_fn fn, void *arg) |
| { |
| EFI_SIGNATURE_LIST *siglist; |
| EFI_SIGNATURE_DATA *sigdata; |
| unsigned int i, j; |
| int rc = 0; |
| |
| if (len == 0) |
| return 0; |
| |
| if (len < sizeof(*siglist)) |
| return -1; |
| |
| for (i = 0, siglist = db_data + i; |
| i + sizeof(*siglist) <= len && |
| i + siglist->SignatureListSize > i && |
| i + siglist->SignatureListSize <= len && !rc; |
| i += siglist->SignatureListSize, |
| siglist = db_data + i) { |
| |
| /* ensure that the header & sig sizes are sensible */ |
| if (siglist->SignatureHeaderSize > siglist->SignatureListSize) |
| continue; |
| |
| if (siglist->SignatureSize > siglist->SignatureListSize) |
| continue; |
| |
| if (siglist->SignatureSize < sizeof(*sigdata)) |
| continue; |
| |
| /* iterate through the (constant-sized) signature data blocks */ |
| for (j = sizeof(*siglist) + siglist->SignatureHeaderSize; |
| j < siglist->SignatureListSize && !rc; |
| j += siglist->SignatureSize) |
| { |
| sigdata = (void *)(siglist) + j; |
| |
| rc = fn(sigdata, siglist->SignatureSize, |
| &siglist->SignatureType, arg); |
| |
| } |
| |
| } |
| |
| return rc; |
| } |
| |
| struct keydb_add_ctx { |
| struct fs_keystore_entry *ke; |
| struct key_database *kdb; |
| struct keyset *keyset; |
| }; |
| |
| static int keydb_add_key(EFI_SIGNATURE_DATA *sigdata, int len, |
| const EFI_GUID *type, void *arg) |
| { |
| struct keydb_add_ctx *add_ctx = arg; |
| struct key *key; |
| int rc; |
| |
| key = talloc(add_ctx->keyset, struct key); |
| |
| rc = key_parse(key, type, sigdata->SignatureData, |
| len - sizeof(*sigdata)); |
| |
| if (rc) { |
| talloc_free(key); |
| return 0; |
| } |
| key->keystore_entry = add_ctx->ke; |
| key->type = *type; |
| |
| /* add a reference to the keystore entry: we don't want it to be |
| * deallocated if the keystore is deallocated before the |
| * struct key. */ |
| if (key->keystore_entry) |
| talloc_reference(key, key->keystore_entry); |
| |
| list_add(&add_ctx->kdb->keys, &key->list); |
| |
| return 0; |
| } |
| |
| static int read_firmware_keydb(struct sync_context *ctx, |
| struct key_database *kdb) |
| { |
| struct keydb_add_ctx add_ctx; |
| char guid_str[GUID_STRLEN]; |
| char *filename; |
| uint8_t *buf; |
| int rc = -1; |
| size_t len; |
| |
| add_ctx.keyset = ctx->firmware_keys; |
| add_ctx.kdb = kdb; |
| add_ctx.ke = NULL; |
| |
| guid_to_str(&kdb->type->guid, guid_str); |
| |
| filename = talloc_asprintf(ctx->firmware_keys, "%s/%s-%s", |
| ctx->efivars_dir, kdb->type->name, guid_str); |
| |
| buf = NULL; |
| rc = fileio_read_file_noerror(ctx->firmware_keys, filename, &buf, &len); |
| if (rc) |
| goto out; |
| |
| /* efivars files start with a 32-bit attribute block */ |
| if (len < sizeof(uint32_t)) |
| goto out; |
| |
| buf += sizeof(uint32_t); |
| len -= sizeof(uint32_t); |
| |
| rc = 0; |
| sigdb_iterate(buf, len, keydb_add_key, &add_ctx); |
| |
| out: |
| if (rc) |
| talloc_free(buf); |
| talloc_free(filename); |
| |
| return rc; |
| } |
| |
| static void __attribute__((format(printf, 2, 3))) print_keystore_key_error( |
| struct fs_keystore_entry *ke, const char *fmt, ...) |
| { |
| char *errstr; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| errstr = talloc_vasprintf(ke, fmt, ap); |
| |
| fprintf(stderr, "Invalid key %s/%s\n - %s\n", ke->root, ke->name, |
| errstr); |
| |
| talloc_free(errstr); |
| va_end(ap); |
| } |
| |
| static int read_filesystem_keydb(struct sync_context *ctx, |
| struct key_database *kdb) |
| { |
| EFI_GUID cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID; |
| EFI_VARIABLE_AUTHENTICATION_2 *auth; |
| struct keydb_add_ctx add_ctx; |
| struct fs_keystore_entry *ke; |
| int rc; |
| |
| add_ctx.keyset = ctx->filesystem_keys; |
| add_ctx.kdb = kdb; |
| |
| list_for_each(&ctx->fs_keystore->keys, ke, keystore_list) { |
| unsigned int len; |
| void *buf; |
| |
| if (ke->len == 0) |
| continue; |
| |
| if (ke->type != kdb->type) |
| continue; |
| |
| /* parse the three data structures: |
| * EFI_VARIABLE_AUTHENTICATION_2 token |
| * EFI_SIGNATURE_LIST |
| * EFI_SIGNATURE_DATA |
| * ensuring that we have enough data for each |
| */ |
| |
| buf = ke->data; |
| len = ke->len; |
| |
| if (len < sizeof(*auth)) { |
| print_keystore_key_error(ke, "does not contain an " |
| "EFI_VARIABLE_AUTHENTICATION_2 descriptor"); |
| continue; |
| } |
| |
| auth = buf; |
| |
| if (guidcmp(&auth->AuthInfo.CertType, &cert_type_pkcs7)) { |
| print_keystore_key_error(ke, "unknown cert type"); |
| continue; |
| } |
| |
| if (auth->AuthInfo.Hdr.dwLength > len) { |
| print_keystore_key_error(ke, |
| "invalid WIN_CERTIFICATE length"); |
| continue; |
| } |
| |
| /* the dwLength field includes the size of the WIN_CERTIFICATE, |
| * but not the other data in the EFI_VARIABLE_AUTHENTICATION_2 |
| * descriptor */ |
| buf += sizeof(*auth) - sizeof(auth->AuthInfo) + |
| auth->AuthInfo.Hdr.dwLength; |
| len -= sizeof(*auth) - sizeof(auth->AuthInfo) + |
| auth->AuthInfo.Hdr.dwLength; |
| |
| add_ctx.ke = ke; |
| rc = sigdb_iterate(buf, len, keydb_add_key, &add_ctx); |
| if (rc) { |
| print_keystore_key_error(ke, "error parsing " |
| "EFI_SIGNATURE_LIST"); |
| continue; |
| } |
| |
| } |
| |
| return 0; |
| } |
| |
| static int read_keysets(struct sync_context *ctx) |
| { |
| read_firmware_keydb(ctx, &ctx->firmware_keys->pk); |
| read_firmware_keydb(ctx, &ctx->firmware_keys->kek); |
| read_firmware_keydb(ctx, &ctx->firmware_keys->db); |
| read_firmware_keydb(ctx, &ctx->firmware_keys->dbx); |
| |
| read_filesystem_keydb(ctx, &ctx->filesystem_keys->pk); |
| read_filesystem_keydb(ctx, &ctx->filesystem_keys->kek); |
| read_filesystem_keydb(ctx, &ctx->filesystem_keys->db); |
| read_filesystem_keydb(ctx, &ctx->filesystem_keys->dbx); |
| |
| return 0; |
| } |
| |
| static int check_pk(struct sync_context *ctx) |
| { |
| struct key *key; |
| int i = 0; |
| |
| list_for_each(&ctx->filesystem_keys->pk.keys, key, list) |
| i++; |
| |
| return (i <= 1) ? 0 : 1; |
| } |
| |
| static void print_keyset(struct keyset *keyset, const char *name) |
| { |
| struct key_database *kdbs[] = |
| { &keyset->pk, &keyset->kek, &keyset->db, &keyset->dbx }; |
| struct key *key; |
| unsigned int i; |
| |
| printf("%s keys:\n", name); |
| |
| for (i = 0; i < ARRAY_SIZE(kdbs); i++) { |
| printf(" %s:\n", kdbs[i]->type->name); |
| |
| list_for_each(&kdbs[i]->keys, key, list) { |
| printf(" %s\n", key->description); |
| if (key->keystore_entry) |
| printf(" from %s/%s\n", |
| key->keystore_entry->root, |
| key->keystore_entry->name); |
| } |
| } |
| } |
| |
| static int check_efivars_mount(const char *mountpoint) |
| { |
| struct statfs statbuf; |
| int rc; |
| |
| rc = statfs(mountpoint, &statbuf); |
| if (rc) |
| return -1; |
| |
| if (statbuf.f_type != EFIVARS_FSTYPE && statbuf.f_type != PSTORE_FSTYPE) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int keystore_entry_read(struct fs_keystore_entry *ke) |
| { |
| const char *path; |
| int rc; |
| |
| path = talloc_asprintf(ke, "%s/%s", ke->root, ke->name); |
| |
| rc = fileio_read_file(ke, path, &ke->data, &ke->len); |
| |
| talloc_free(path); |
| |
| return rc; |
| } |
| |
| static bool keystore_contains_file(struct fs_keystore *keystore, |
| const char *filename) |
| { |
| struct fs_keystore_entry *ke; |
| |
| list_for_each(&keystore->keys, ke, keystore_list) { |
| if (!strcmp(ke->name, filename)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int update_keystore(struct fs_keystore *keystore, const char *root) |
| { |
| struct fs_keystore_entry *ke; |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(keydb_types); i++) { |
| const char *filename, *dirname; |
| struct dirent *dirent; |
| DIR *dir; |
| |
| dirname = talloc_asprintf(keystore, "%s/%s", root, |
| keydb_types[i].name); |
| |
| dir = opendir(dirname); |
| if (!dir) |
| continue; |
| |
| for (dirent = readdir(dir); dirent; dirent = readdir(dir)) { |
| |
| if (dirent->d_name[0] == '.') |
| continue; |
| |
| filename = talloc_asprintf(dirname, "%s/%s", |
| keydb_types[i].name, |
| dirent->d_name); |
| |
| if (keystore_contains_file(keystore, filename)) |
| continue; |
| |
| ke = talloc(keystore, struct fs_keystore_entry); |
| ke->name = filename; |
| ke->root = root; |
| ke->type = &keydb_types[i]; |
| talloc_steal(ke, ke->name); |
| |
| if (keystore_entry_read(ke)) |
| talloc_free(ke); |
| else |
| list_add(&keystore->keys, &ke->keystore_list); |
| } |
| |
| closedir(dir); |
| talloc_free(dirname); |
| } |
| |
| return 0; |
| } |
| |
| static int read_keystore(struct sync_context *ctx) |
| { |
| struct fs_keystore *keystore; |
| unsigned int i; |
| |
| keystore = talloc(ctx, struct fs_keystore); |
| list_head_init(&keystore->keys); |
| |
| for (i = 0; i < ctx->n_keystore_dirs; i++) { |
| update_keystore(keystore, ctx->keystore_dirs[i]); |
| } |
| |
| ctx->fs_keystore = keystore; |
| |
| return 0; |
| } |
| |
| static void print_keystore(struct fs_keystore *keystore) |
| { |
| struct fs_keystore_entry *ke; |
| |
| printf("Filesystem keystore:\n"); |
| |
| list_for_each(&keystore->keys, ke, keystore_list) |
| printf(" %s/%s [%zd bytes]\n", ke->root, ke->name, ke->len); |
| } |
| |
| static int key_cmp(struct key *a, struct key *b) |
| { |
| if (a->id_len != b->id_len) |
| return a->id_len - b->id_len; |
| |
| return memcmp(a->id, b->id, a->id_len); |
| } |
| |
| /** |
| * Finds the set-difference of the filesystem and firmware keys, and |
| * populates ctx->new_keys with the keystore_entries that should be |
| * inserted into firmware |
| */ |
| static int find_new_keys(struct sync_context *ctx) |
| { |
| struct { |
| struct key_database *fs_kdb, *fw_kdb; |
| } kdbs[] = { |
| { &ctx->filesystem_keys->pk, &ctx->firmware_keys->pk }, |
| { &ctx->filesystem_keys->kek, &ctx->firmware_keys->kek }, |
| { &ctx->filesystem_keys->db, &ctx->firmware_keys->db }, |
| { &ctx->filesystem_keys->dbx, &ctx->firmware_keys->dbx }, |
| }; |
| unsigned int i; |
| int n = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(kdbs); i++ ) { |
| struct fs_keystore_entry *ke; |
| struct key *fs_key, *fw_key; |
| bool found; |
| |
| list_for_each(&kdbs[i].fs_kdb->keys, fs_key, list) { |
| found = false; |
| list_for_each(&kdbs[i].fw_kdb->keys, fw_key, list) { |
| if (!key_cmp(fs_key, fw_key)) { |
| found = true; |
| break; |
| } |
| } |
| if (found) |
| continue; |
| |
| /* add the keystore entry if it's not already present */ |
| found = false; |
| list_for_each(&ctx->new_keys, ke, new_list) { |
| if (fs_key->keystore_entry == ke) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (found) |
| continue; |
| |
| list_add(&ctx->new_keys, |
| &fs_key->keystore_entry->new_list); |
| n++; |
| } |
| } |
| |
| return n; |
| } |
| |
| static void print_new_keys(struct sync_context *ctx) |
| { |
| struct fs_keystore_entry *ke; |
| |
| printf("New keys in filesystem:\n"); |
| |
| list_for_each(&ctx->new_keys, ke, new_list) |
| printf(" %s/%s\n", ke->root, ke->name); |
| } |
| |
| static int insert_key(struct sync_context *ctx, struct fs_keystore_entry *ke) |
| { |
| char guid_str[GUID_STRLEN]; |
| char *efivars_filename; |
| unsigned int buf_len; |
| uint8_t *buf; |
| int fd, rc; |
| |
| fd = -1; |
| rc = -1; |
| |
| if (ctx->verbose) |
| printf("Inserting key update %s/%s into %s\n", |
| ke->root, ke->name, ke->type->name); |
| |
| /* we create a contiguous buffer of attributes & key data, so that |
| * we write to the efivars file in a single syscall */ |
| buf_len = sizeof(sigdb_attrs) + ke->len; |
| buf = talloc_array(ke, uint8_t, buf_len); |
| memcpy(buf, &sigdb_attrs, sizeof(sigdb_attrs)); |
| memcpy(buf + sizeof(sigdb_attrs), ke->data, ke->len); |
| |
| guid_to_str(&ke->type->guid, guid_str); |
| |
| efivars_filename = talloc_asprintf(ke, "%s/%s-%s", ctx->efivars_dir, |
| ke->type->name, guid_str); |
| |
| fd = open(efivars_filename, O_WRONLY | O_CREAT, 0600); |
| if (fd < 0) { |
| fprintf(stderr, "Can't create key file %s: %s\n", |
| efivars_filename, strerror(errno)); |
| goto out; |
| } |
| |
| rc = write(fd, buf, buf_len); |
| if (rc <= 0) { |
| fprintf(stderr, "Error writing key update: %s\n", |
| strerror(errno)); |
| goto out; |
| } |
| |
| if (rc != (int)buf_len) { |
| fprintf(stderr, "Partial write during key update: " |
| "wrote %d bytes, expecting %d\n", |
| rc, buf_len); |
| goto out; |
| } |
| |
| rc = 0; |
| |
| out: |
| if (fd >= 0) |
| close(fd); |
| talloc_free(efivars_filename); |
| talloc_free(buf); |
| if (rc) |
| fprintf(stderr, "Error syncing keystore file %s/%s\n", |
| ke->root, ke->name); |
| return rc; |
| } |
| |
| static int insert_new_keys(struct sync_context *ctx) |
| { |
| struct fs_keystore_entry *ke, *ke_pk; |
| int pks, rc; |
| |
| rc = 0; |
| pks = 0; |
| ke_pk = NULL; |
| |
| list_for_each(&ctx->new_keys, ke, new_list) { |
| |
| /* we handle PK last */ |
| if (ke->type == &keydb_types[KEYDB_PK]) { |
| ke_pk = ke; |
| pks++; |
| continue; |
| } |
| |
| if (insert_key(ctx, ke)) |
| rc = -1; |
| } |
| |
| if (rc) |
| return rc; |
| |
| if (pks == 0 || !ctx->set_pk) |
| return 0; |
| |
| if (pks > 1) { |
| fprintf(stderr, "Skipping PK update due to mutiple PKs\n"); |
| return -1; |
| } |
| |
| rc = insert_key(ctx, ke_pk); |
| |
| return rc; |
| } |
| |
| static struct keyset *init_keyset(struct sync_context *ctx) |
| { |
| struct keyset *keyset; |
| |
| keyset = talloc(ctx, struct keyset); |
| |
| list_head_init(&keyset->pk.keys); |
| keyset->pk.type = &keydb_types[KEYDB_PK]; |
| |
| list_head_init(&keyset->kek.keys); |
| keyset->kek.type = &keydb_types[KEYDB_KEK]; |
| |
| list_head_init(&keyset->db.keys); |
| keyset->db.type = &keydb_types[KEYDB_DB]; |
| |
| list_head_init(&keyset->dbx.keys); |
| keyset->dbx.type = &keydb_types[KEYDB_DBX]; |
| |
| return keyset; |
| } |
| |
| static struct option options[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "version", no_argument, NULL, 'V' }, |
| { "efivars-path", required_argument, NULL, 'e' }, |
| { "verbose", no_argument, NULL, 'v' }, |
| { "dry-run", no_argument, NULL, 'n' }, |
| { "pk", no_argument, NULL, 'p' }, |
| { "no-default-keystores", no_argument, NULL, 'd' }, |
| { "keystore", required_argument, NULL, 'k' }, |
| { NULL, 0, NULL, 0 }, |
| }; |
| |
| static void usage(void) |
| { |
| printf("Usage: %s [options]\n" |
| "Update EFI key databases from the filesystem\n" |
| "\n" |
| "Options:\n" |
| "\t--efivars-path <dir> Path to efivars mountpoint\n" |
| "\t (or regular directory for testing)\n" |
| "\t--verbose Print verbose progress information\n" |
| "\t--dry-run Don't update firmware key databases\n" |
| "\t--pk Set PK\n" |
| "\t--keystore <dir> Read keys from <dir>/{db,dbx,KEK}/*\n" |
| "\t (can be specified multiple times,\n" |
| "\t first dir takes precedence)\n" |
| "\t--no-default-keystores\n" |
| "\t Don't read keys from the default\n" |
| "\t keystore dirs\n", |
| toolname); |
| } |
| |
| static void version(void) |
| { |
| printf("%s %s\n", toolname, VERSION); |
| } |
| |
| static void add_keystore_dir(struct sync_context *ctx, const char *dir) |
| { |
| ctx->keystore_dirs = talloc_realloc(ctx, ctx->keystore_dirs, |
| const char *, ++ctx->n_keystore_dirs); |
| |
| ctx->keystore_dirs[ctx->n_keystore_dirs - 1] = |
| talloc_strdup(ctx->keystore_dirs, dir); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| bool use_default_keystore_dirs; |
| struct sync_context *ctx; |
| |
| use_default_keystore_dirs = true; |
| ctx = talloc_zero(NULL, struct sync_context); |
| list_head_init(&ctx->new_keys); |
| |
| for (;;) { |
| int idx, c; |
| c = getopt_long(argc, argv, "e:dpkvhV", options, &idx); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'e': |
| ctx->efivars_dir = optarg; |
| break; |
| case 'd': |
| use_default_keystore_dirs = false; |
| break; |
| case 'k': |
| add_keystore_dir(ctx, optarg); |
| break; |
| case 'p': |
| ctx->set_pk = true; |
| break; |
| case 'v': |
| ctx->verbose = true; |
| break; |
| case 'n': |
| ctx->dry_run = true; |
| break; |
| case 'V': |
| version(); |
| return EXIT_SUCCESS; |
| case 'h': |
| usage(); |
| return EXIT_SUCCESS; |
| } |
| } |
| |
| if (argc != optind) { |
| usage(); |
| return EXIT_FAILURE; |
| } |
| |
| ERR_load_crypto_strings(); |
| OpenSSL_add_all_digests(); |
| OpenSSL_add_all_ciphers(); |
| OPENSSL_config(NULL); |
| /* here we may get highly unlikely failures or we'll get a |
| * complaint about FIPS signatures (usually becuase the FIPS |
| * module isn't present). In either case ignore the errors |
| * (malloc will cause other failures out lower down */ |
| ERR_clear_error(); |
| |
| ctx->filesystem_keys = init_keyset(ctx); |
| ctx->firmware_keys = init_keyset(ctx); |
| |
| if (!ctx->efivars_dir) { |
| ctx->efivars_dir = EFIVARS_MOUNTPOINT; |
| if (check_efivars_mount(ctx->efivars_dir)) { |
| fprintf(stderr, "Can't access efivars filesystem " |
| "at %s, aborting\n", ctx->efivars_dir); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if (use_default_keystore_dirs) { |
| unsigned int i; |
| for (i = 0; i < ARRAY_SIZE(default_keystore_dirs); i++) |
| add_keystore_dir(ctx, default_keystore_dirs[i]); |
| } |
| |
| |
| read_keystore(ctx); |
| |
| if (ctx->verbose) |
| print_keystore(ctx->fs_keystore); |
| |
| read_keysets(ctx); |
| if (ctx->verbose) { |
| print_keyset(ctx->firmware_keys, "firmware"); |
| print_keyset(ctx->filesystem_keys, "filesystem"); |
| } |
| |
| if (check_pk(ctx)) |
| fprintf(stderr, "WARNING: multiple PKs found in filesystem\n"); |
| |
| find_new_keys(ctx); |
| |
| if (ctx->verbose) |
| print_new_keys(ctx); |
| |
| if (!ctx->dry_run) |
| insert_new_keys(ctx); |
| |
| talloc_free(ctx); |
| |
| return EXIT_SUCCESS; |
| } |