/* Copyright 2016 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Common boot flow utility */ #include #include #include #include "2sysincludes.h" #include "2common.h" #include "bdb.h" #include "bdb_struct.h" #include "futility.h" #include "host.h" static long int version; /* Command line options */ enum { /* mode options */ OPT_MODE_NONE, OPT_MODE_ADD, OPT_MODE_CREATE, OPT_MODE_RESIGN, OPT_MODE_VERIFY, /* file options */ OPT_BDBKEY_PRI, OPT_BDBKEY_PUB, OPT_DATAKEY_PRI, OPT_DATAKEY_PUB, OPT_DATA, OPT_KEY_DIGEST, /* versions */ OPT_BDBKEY_VERSION, OPT_DATAKEY_VERSION, OPT_DATA_VERSION, /* integer options */ OPT_OFFSET, OPT_PARTITION, OPT_TYPE, OPT_LOAD_ADDRESS, /* Misc. options */ OPT_IGNORE_KEY_DIGEST, OPT_VERSION, OPT_HELP, }; static const struct option long_opts[] = { {"add", 1, 0, OPT_MODE_ADD}, {"create", 1, 0, OPT_MODE_CREATE}, {"resign", 1, 0, OPT_MODE_RESIGN}, {"verify", 1, 0, OPT_MODE_VERIFY}, {"bdbkey_pri", 1, 0, OPT_BDBKEY_PRI}, {"bdbkey_pub", 1, 0, OPT_BDBKEY_PUB}, {"datakey_pri", 1, 0, OPT_DATAKEY_PRI}, {"datakey_pub", 1, 0, OPT_DATAKEY_PUB}, {"bdbkey_version", 1, 0, OPT_BDBKEY_VERSION}, {"datakey_version", 1, 0, OPT_DATAKEY_VERSION}, {"data_version", 1, 0, OPT_DATA_VERSION}, {"data", 1, 0, OPT_DATA}, {"key_digest", 1, 0, OPT_KEY_DIGEST}, {"offset", 1, 0, OPT_OFFSET}, {"partition", 1, 0, OPT_PARTITION}, {"type", 1, 0, OPT_TYPE}, {"load_address", 1, 0, OPT_LOAD_ADDRESS}, {"ignore_key_digest", 0, 0, OPT_IGNORE_KEY_DIGEST}, {"version", 1, 0, OPT_VERSION}, {"help", 0, 0, OPT_HELP}, {NULL, 0, 0, 0} }; /** * Add hash entry to BDB * * This adds a hash entry to a BDB. It does not change the signature. Hence, * the produced BDB needs to be resigned using the resign sub-command. */ static int do_add(const char *bdb_filename, const char *data_filename, uint64_t offset, uint8_t partition, uint8_t type, uint64_t load_address) { uint8_t *bdb, *data, *new_bdb = NULL; uint32_t bdb_size, data_size; struct bdb_header *bdb_header; struct bdb_data *data_header; struct bdb_hash *new_hash; int rv = -1; bdb = read_file(bdb_filename, &bdb_size); data = read_file(data_filename, &data_size); if (!bdb || !data) { fprintf(stderr, "Unable to load BDB or data\n"); goto exit; } /* Create a copy of BDB */ new_bdb = calloc(1, bdb_size + sizeof(*new_hash)); if (!new_bdb) { fprintf(stderr, "Unable to allocate memory\n"); goto exit; } /* Copy up to the end of hashes. This implicitly clears the data * sig because it's not copied. */ memcpy(new_bdb, bdb, vb2_offset_of(bdb, bdb_get_data_sig(bdb))); /* Update new BDB header */ bdb_header = (struct bdb_header *)bdb_get_header(new_bdb); bdb_header->bdb_size += sizeof(*new_hash); data_header = (struct bdb_data *)bdb_get_data(new_bdb); /* Update new hash. We're overwriting the data signature, which * is already invalid anyway. */ new_hash = (struct bdb_hash *)((uint8_t *)data_header + data_header->signed_size); new_hash->size = data_size; new_hash->type = type; new_hash->load_address = load_address; new_hash->partition = partition; new_hash->offset = offset; if (bdb_sha256(new_hash->digest, data, data_size)) { fprintf(stderr, "Unable to calculate hash\n"); goto exit; } /* Update data header */ data_header->num_hashes++; data_header->signed_size += sizeof(*new_hash); rv = write_file(bdb_filename, bdb_header, bdb_header->bdb_size); if (rv) { fprintf(stderr, "Unable to write BDB\n"); goto exit; } fprintf(stderr, "Hash is added to BDB successfully. Resign required\n"); exit: free(bdb); free(data); free(new_bdb); return rv; } /** * Create a new BDB * * This creates a new BDB using a pair of BDB keys and a pair of data keys. * A private data key is needed even with no hash entries. */ static int do_create(const char *bdb_filename, const char *bdbkey_pri_filename, const char *bdbkey_pub_filename, uint32_t bdbkey_version, const char *datakey_pri_filename, const char *datakey_pub_filename, uint32_t datakey_version, uint64_t load_address) { struct bdb_key *bdbkey; struct bdb_key *datakey; struct rsa_st *bdbkey_pri; struct rsa_st *datakey_pri; struct bdb_create_params params; struct bdb_header *header; int rv = -1; /* Check arguments */ if (!bdb_filename || !bdbkey_pri_filename || !bdbkey_pub_filename || !datakey_pri_filename || !datakey_pub_filename) { fprintf(stderr, "Missing filenames\n"); return rv; } /* Load keys */ bdbkey = bdb_create_key(bdbkey_pub_filename, bdbkey_version, NULL); bdbkey_pri = read_pem(bdbkey_pri_filename); datakey = bdb_create_key(datakey_pub_filename, datakey_version, NULL); datakey_pri = read_pem(datakey_pri_filename); if (!bdbkey || !bdbkey_pri || !datakey || !datakey_pri) { fprintf(stderr, "Unable to load keys\n"); goto exit; } memset(¶ms, 0, sizeof(params)); params.bdb_load_address = load_address; params.bdbkey = bdbkey; params.datakey = datakey; params.private_bdbkey = bdbkey_pri; params.private_datakey = datakey_pri; params.num_hashes = 0; header = bdb_create(¶ms); if (!header) { fprintf(stderr, "Unable to create BDB\n"); goto exit; } rv = write_file(bdb_filename, header, header->bdb_size); if (rv) { fprintf(stderr, "Unable to write BDB\n"); goto exit; } fprintf(stderr, "BDB is created successfully\n"); exit: /* Free keys and buffers */ free(bdbkey); free(datakey); RSA_free(bdbkey_pri); RSA_free(datakey_pri); return rv; } static int install_bdbkey(uint8_t **bdb, const struct bdb_key *new_key) { struct bdb_header *header; const struct bdb_key *key; uint8_t *p, *q; uint8_t *new_bdb; size_t new_size; size_t l; header = (struct bdb_header *)bdb_get_header(*bdb); key = bdb_get_bdbkey(*bdb); new_size = bdb_size_of(*bdb) + new_key->struct_size - key->struct_size; new_bdb = calloc(1, new_size); if (!new_bdb) { fprintf(stderr, "Unable to allocate memory\n"); return -1; } /* copy BDB header */ p = *bdb; q = new_bdb; l = header->struct_size; memcpy(q, p, l); /* copy new BDB key */ p += l; q += l; memcpy(q, new_key, new_key->struct_size); /* copy the rest */ p += key->struct_size; q += new_key->struct_size; l = bdb_size_of(*bdb) - vb2_offset_of(*bdb, p); memcpy(q, p, l); /* update size */ header = (struct bdb_header *)bdb_get_header(new_bdb); header->bdb_size = new_size; free(*bdb); *bdb = new_bdb; return 0; } static int install_datakey(uint8_t **bdb, const struct bdb_key *new_key) { struct bdb_header *header; struct bdb_key *key; uint8_t *p, *q; uint8_t *new_bdb; size_t new_size; uint32_t l; key = (struct bdb_key *)bdb_get_datakey(*bdb); new_size = bdb_size_of(*bdb) + new_key->struct_size - key->struct_size; new_bdb = calloc(1, new_size); if (!new_bdb) { fprintf(stderr, "Unable to allocate memory\n"); return -1; } /* copy the stuff up to datakey */ p = *bdb; q = new_bdb; l = bdb_offset_of_datakey(*bdb); memcpy(q, p, l); /* copy new data key */ p += l; q += l; memcpy(q, new_key, new_key->struct_size); /* copy the rest */ p += key->struct_size; q += new_key->struct_size; l = bdb_size_of(*bdb) - vb2_offset_of(*bdb, p); memcpy(q, p, l); /* update size */ header = (struct bdb_header *)bdb_get_header(new_bdb); header->bdb_size = new_size; header->signed_size = header->oem_area_0_size + new_key->struct_size; free(*bdb); *bdb = new_bdb; return 0; } /** * Resign a BDB using new keys * * It first installs given public keys to the BDB, then, runs verification. * If verification fails due to an invalid signature, it tries to 'fix' it * by resigning it using a given private key, then runs verification again. * Whether a key is required or not depends on which signature is invalid. * If a private key is required but not provided, it returns an error. */ static int do_resign(const char *bdb_filename, const char *bdbkey_pri_filename, const char *bdbkey_pub_filename, uint32_t bdbkey_version, const char *datakey_pri_filename, const char *datakey_pub_filename, uint32_t datakey_version, uint32_t data_version) { uint8_t *bdb = NULL; struct rsa_st *bdbkey_pri = NULL; struct rsa_st *datakey_pri = NULL; uint32_t bdb_size; int resigned = 0; int rv = -1; if (!bdb_filename) { fprintf(stderr, "BDB file must be specified\n"); goto exit; } bdb = read_file(bdb_filename, &bdb_size); if (!bdb) { fprintf(stderr, "Unable to read %s\n", bdb_filename); goto exit; } if (data_version != -1) { struct bdb_data *data = (struct bdb_data *)bdb_get_data(bdb); data->data_version = data_version; } if (bdbkey_pub_filename) { struct bdb_key *key = bdb_create_key(bdbkey_pub_filename, bdbkey_version, NULL); if (!key) { fprintf(stderr, "Unable to read BDB key\n"); goto exit; } if (install_bdbkey(&bdb, key)) { fprintf(stderr, "Unable to install new BDB key\n"); goto exit; } } if (datakey_pub_filename) { struct bdb_key *key = bdb_create_key(datakey_pub_filename, datakey_version, NULL); if (!key) { fprintf(stderr, "Unable to read data key\n"); goto exit; } if (install_datakey(&bdb, key)) { fprintf(stderr, "Unable to install new data key\n"); goto exit; } } /* Check validity for the new bdb key */ rv = bdb_verify(bdb, bdb_size_of(bdb), NULL); if (rv == BDB_ERROR_HEADER_SIG) { /* This is expected failure if we installed a new BDB key. * Let's resign to fix it. */ resigned = 1; fprintf(stderr, "Data key signature is invalid. Need to resign " "the key.\n"); if (!bdbkey_pri_filename) { fprintf(stderr, "Private BDB key is required but not " "provided.\n"); goto exit; } bdbkey_pri = read_pem(bdbkey_pri_filename); rv = bdb_sign_datakey(&bdb, bdbkey_pri); if (rv) { fprintf(stderr, "Failed to resign data key: %d\n", rv); goto exit; } fprintf(stderr, "Data key is resigned.\n"); } else { fprintf(stderr, "Resigning data key is not required.\n"); } /* Check validity for the new data key */ rv = bdb_verify(bdb, bdb_size_of(bdb), NULL); switch (rv) { case BDB_ERROR_DATA_SIG: case BDB_ERROR_DATA_CHECK_SIG: /* This is expected failure if we installed a new data key * or sig is corrupted, which happens when a new hash is added * by 'add' sub-command. Let's resign the data */ resigned = 1; fprintf(stderr, "Data signature is invalid. Need to resign data.\n"); if (!datakey_pri_filename) { fprintf(stderr, "Private data key is required but not " "provided.\n"); goto exit; } datakey_pri = read_pem(datakey_pri_filename); rv = bdb_sign_data(&bdb, datakey_pri); if (rv) { fprintf(stderr, "Failed to resign hashes: %d\n", rv); goto exit; } fprintf(stderr, "Data is resigned.\n"); break; case BDB_GOOD_OTHER_THAN_KEY: case BDB_SUCCESS: fprintf(stderr, "Resigning the data is not required.\n"); break; default: fprintf(stderr, "Verifying BDB failed unexpectedly: %d\n", rv); goto exit; } if (!resigned) goto exit; /* Check validity one last time */ rv = bdb_verify(bdb, bdb_size_of(bdb), NULL); if (rv && rv != BDB_GOOD_OTHER_THAN_KEY) { /* This is not expected. We installed new keys and resigned * BDB but it's still invalid. */ fprintf(stderr, "BDB is resigned but it's invalid: %d\n", rv); goto exit; } rv = write_file(bdb_filename, bdb, bdb_size_of(bdb)); if (rv) { fprintf(stderr, "Unable to write BDB.\n"); goto exit; } fprintf(stderr, "Successfully resigned BDB.\n"); exit: free(bdb); RSA_free(bdbkey_pri); RSA_free(datakey_pri); return rv; } static int do_verify(const char *bdb_filename, const char *key_digest_filename, int ignore_key_digest) { uint8_t *bdb = NULL; uint8_t *key_digest = NULL; uint32_t bdb_size, key_digest_size; int rv = -1; bdb = read_file(bdb_filename, &bdb_size); if (!bdb) { fprintf(stderr, "Unable to load BDB\n"); goto exit; } if (key_digest_filename) { key_digest = read_file(key_digest_filename, &key_digest_size); if (!key_digest) { fprintf(stderr, "Unable to read key digest\n"); goto exit; } if (key_digest_size != BDB_SHA256_DIGEST_SIZE) { fprintf(stderr, "Invalid digest size: %d\n", key_digest_size); goto exit; } } rv = bdb_verify(bdb, bdb_size, key_digest); switch (rv) { case BDB_SUCCESS: fprintf(stderr, "BDB is successfully verified.\n"); break; case BDB_GOOD_OTHER_THAN_KEY: fprintf(stderr, "BDB is valid."); if (ignore_key_digest) { rv = BDB_SUCCESS; fprintf(stderr, " Key digest doesn't match but ignored.\n"); } else { fprintf(stderr, " Key digest doesn't match.\n"); } break; default: /* TODO: Probably nice to print translation of the error code */ fprintf(stderr, "BDB is invalid: %d.\n", rv); } exit: free(bdb); free(key_digest); return rv; } /* Print help and return error */ static void print_help(int argc, char *argv[]) { printf("\nUsage: " MYNAME " %s <--create|--add|--resign|--verify>\n" "\n" "Utility for managing boot descriptor blocks (BDBs).\n" "\n" "For '--add [OPTIONS]', required OPTIONS are:\n" " --data Data to be added\n" " --offset Offset\n" " --partition Partition number\n" " --type Data type\n" " --load_address Data load address\n" "\n" "For '--create [OPTIONS]', required OPTIONS are:\n" " --bdbkey_pri BDB key in .pem format\n" " --bdbkey_pub BDB key in .keyb format\n" " --datakey_pri Data key in .pem format\n" " --datakey_pub Data key in .keyb format\n" " --load_address BDB load address\n" "\n" "For '--resign [OPTIONS]', optional OPTIONS are:\n" " --bdbkey_pri New BDB key in .pem format\n" " --bdbkey_pub New BDB key in .keyb format\n" " --datakey_pri New data key in .pem format\n" " --datakey_pub New data key in .keyb format\n" " --data_version Data version\n" "\n" "For '--verify [OPTIONS]', optional OPTIONS are:\n" " --key_digest BDB key digest\n" " --ignore_key_digest Ignore key digest mismatch\n" "\n", argv[0]); } static int do_bdb(int argc, char *argv[]) { int mode = 0; const char *bdb_filename = NULL; const char *bdbkey_pri_filename = NULL; const char *bdbkey_pub_filename = NULL; const char *datakey_pri_filename = NULL; const char *datakey_pub_filename = NULL; const char *data_filename = NULL; const char *key_digest_filename = NULL; uint32_t bdbkey_version = 0; uint32_t datakey_version = 0; uint32_t data_version = -1; uint64_t offset = 0; uint8_t partition = 0; uint8_t type = 0; uint64_t load_address = -1; int ignore_key_digest = 0; int parse_error = 0; char *e; int i; while ((i = getopt_long(argc, argv, "", long_opts, NULL)) != -1) { switch (i) { case '?': /* Unhandled option */ fprintf(stderr, "Unknown option or missing value\n"); parse_error = 1; break; case OPT_HELP: print_help(argc, argv); return !!parse_error; case OPT_MODE_CREATE: mode = i; bdb_filename = optarg; break; case OPT_MODE_ADD: mode = i; bdb_filename = optarg; break; case OPT_MODE_RESIGN: mode = i; bdb_filename = optarg; break; case OPT_MODE_VERIFY: mode = i; bdb_filename = optarg; break; case OPT_BDBKEY_PRI: bdbkey_pri_filename = optarg; break; case OPT_BDBKEY_PUB: bdbkey_pub_filename = optarg; break; case OPT_DATAKEY_PRI: datakey_pri_filename = optarg; break; case OPT_DATAKEY_PUB: datakey_pub_filename = optarg; break; case OPT_DATA: data_filename = optarg; break; case OPT_KEY_DIGEST: key_digest_filename = optarg; break; case OPT_BDBKEY_VERSION: bdbkey_version = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --bdbkey_version\n"); parse_error = 1; } break; case OPT_DATAKEY_VERSION: datakey_version = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --datakey_version\n"); parse_error = 1; } break; case OPT_DATA_VERSION: data_version = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --data_version\n"); parse_error = 1; } break; case OPT_OFFSET: offset = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --offset\n"); parse_error = 1; } break; case OPT_PARTITION: partition = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --partition\n"); parse_error = 1; } break; case OPT_TYPE: type = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --type\n"); parse_error = 1; } break; case OPT_LOAD_ADDRESS: load_address = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --load_address\n"); parse_error = 1; } break; case OPT_IGNORE_KEY_DIGEST: ignore_key_digest = 1; break; case OPT_VERSION: version = strtoul(optarg, &e, 0); if (!*optarg || (e && *e)) { fprintf(stderr, "Invalid --version\n"); parse_error = 1; } break; } } if (parse_error) { print_help(argc, argv); return 1; } switch (mode) { case OPT_MODE_ADD: return do_add(bdb_filename, data_filename, offset, partition, type, load_address); case OPT_MODE_CREATE: return do_create(bdb_filename, bdbkey_pri_filename, bdbkey_pub_filename, bdbkey_version, datakey_pri_filename, datakey_pub_filename, datakey_version, load_address); case OPT_MODE_RESIGN: return do_resign(bdb_filename, bdbkey_pri_filename, bdbkey_pub_filename, bdbkey_version, datakey_pri_filename, datakey_pub_filename, datakey_version, data_version); case OPT_MODE_VERIFY: return do_verify(bdb_filename, key_digest_filename, ignore_key_digest); case OPT_MODE_NONE: default: fprintf(stderr, "Must specify a mode.\n"); print_help(argc, argv); return 1; } } DECLARE_FUTIL_COMMAND(bdb, do_bdb, VBOOT_VERSION_1_0, "Common boot flow utility");