diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | futility/cmd_gscvd.c | 1050 | ||||
-rw-r--r-- | futility/cmd_pcr.c | 51 | ||||
-rw-r--r-- | futility/futility.h | 6 | ||||
-rw-r--r-- | futility/misc.c | 51 | ||||
-rw-r--r-- | host/lib/include/gsc_ro.h | 59 |
6 files changed, 1168 insertions, 53 deletions
@@ -656,6 +656,7 @@ FUTIL_SRCS = \ futility/cmd_dump_fmap.c \ futility/cmd_dump_kernel_config.c \ futility/cmd_gbb_utility.c \ + futility/cmd_gscvd.c \ futility/cmd_load_fmap.c \ futility/cmd_pcr.c \ futility/cmd_show.c \ @@ -665,9 +666,8 @@ FUTIL_SRCS = \ futility/cmd_vbutil_firmware.c \ futility/cmd_vbutil_firmware.c \ futility/cmd_vbutil_kernel.c \ - futility/cmd_vbutil_keyblock.c \ - futility/cmd_vbutil_key.c \ futility/cmd_vbutil_key.c \ + futility/cmd_vbutil_keyblock.c \ futility/file_type_bios.c \ futility/file_type.c \ futility/file_type_rwsig.c \ diff --git a/futility/cmd_gscvd.c b/futility/cmd_gscvd.c new file mode 100644 index 00000000..27eb16d8 --- /dev/null +++ b/futility/cmd_gscvd.c @@ -0,0 +1,1050 @@ +/* + * Copyright 2021 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. + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <openssl/bn.h> +#include <openssl/pem.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> + +#include "fmap.h" +#include "futility.h" +#include "gsc_ro.h" +#include "host_key21.h" +#include "host_keyblock.h" +#include "host_signature.h" + +/* + * for testing purposes let's use + * - tests/devkeys/kernel_subkey.vbprivk as the root private key + * - tests/devkeys/kernel_subkey.vbpubk as the root public key + * used for signing of the platform public key + * - tests/devkeys/firmware_data_key.vbprivk signing platform key + * - tests/devkeys/firmware_data_key.vbpubk - public key used for signature + * verification + *------------ + * Command to create the signed public key block in ~/tmp/packed: + * + ./build/futility/futility vbutil_keyblock --pack ~/tmp/packed \ + --datapubkey tests/devkeys/firmware_data_key.vbpubk \ + --signprivate tests/devkeys/kernel_subkey.vbprivk + *------------ + * Command to fill RO_GSCVD FMAP area in an AP firmware file. The input AP + * firmware file is ~/tmp/image-guybrush.serial.bin, the output signed + * AP firmware file is ~/tmp/guybrush-signed: + * + ./build/futility/futility gscvd --outfile ~/tmp/guybrush-signed \ + -R 818100:10000,f00000:100,f80000:2000,f8c000:1000,0x00804000:0x00000800 \ + -k ~/tmp/packed -p tests/devkeys/firmware_data_key.vbprivk -b 5a5a4352 \ + -r tests/devkeys/kernel_subkey.vbpubk ~/tmp/image-guybrush.serial.bin + *------------ + * Command to validate a previously signed AP firmware file. The hash is the + * sha256sum of tests/devkeys/kernel_subkey.vbpubk: + * + build/futility/futility gscvd ~/tmp/guybrush-signed \ + e432f23d811be795af8ddf6001d2a6c3e2675e3290bc024100e2a10d0fd9c6ee + */ + +/* Command line options processing support. */ +enum no_short_opts { + OPT_OUTFILE = 1000, +}; + +static const struct option long_opts[] = { + /* name hasarg *flag val */ + {"outfile", 1, NULL, OPT_OUTFILE}, + {"ranges", 1, NULL, 'R'}, + {"board_id", 1, NULL, 'b'}, + {"root_pub_key", 1, NULL, 'r'}, + {"keyblock", 1, NULL, 'k'}, + {"platform_priv", 1, NULL, 'p'}, + {"help", 0, NULL, 'h'}, + {} +}; + +static const char *short_opts = "R:b:hk:p:r:"; + +static const char usage[] = + "\n" + "This utility creates an RO verification space in the Chrome OS AP\n" + "firmware image or allows to validate a previously prepared image\n" + "containing the RO verification space.\n\n" + "Usage: " MYNAME " gscvd PARAMS <AP FIRMWARE FILE> [<root key hash>]\n" + "\n\nCreation of RO Verification space:\n\n" + "Required PARAMS:\n" + " -R|--ranges STRING Comma separated colon delimited\n" + " hex tuples <offset>:<size>, the\n" + " areas of the RO covered by the\n" + " signature\n" + " -b|--board_id <hex value> The Board ID of the board for which\n" + " the image is being signed\n" + " -r|--root_pub_key <file> The main public key, in .vbpubk\n" + " format, used to verify platform\n" + " key\n" + " -k|--keyblock <file> Signed platform public key in\n" + " .keyblock format, used for run\n" + " time RO verifcation\n" + " -p|--platform_priv <file> Private platform key in .vbprivk\n" + " format, used for signing RO\n" + " verification data\n" + "Optional PARAMS:\n" + " [--outfile] OUTFILE Output firmware image containing\n" + " RO verification information\n" + "\n\n" + "Validation of RO Verification space:\n\n" + " The only required parameter is <AP FIRMWARE FILE>, if optional\n" + " <root key hash> is given, it is compared to the hash\n" + " of the root key found in <AP_FIRMWARE_FILE>.\n" + "\n\n" + " -h|--help Print this message\n\n"; + +/* Structure helping to keep track of the file mapped into memory. */ +struct file_buf { + uint32_t len; + uint8_t *data; + int fd; + FmapAreaHeader *ro_gscvd; +}; + +/* + * Max number of RO ranges to cover. 32 is more than enough, this must be kept + * in sync with APRO_MAX_NUM_RANGES declaration in + * common/ap_ro_integrity_check.c in the Cr50 tree. + */ +#define MAX_RANGES 32 + +/* + * Container keeping track of the set of ranges to include in hash + * calculation. + */ +struct gscvd_ro_ranges { + size_t range_count; + struct gscvd_ro_range ranges[MAX_RANGES]; +}; + +/** + * Load the AP firmware file into memory. + * + * Map the requested file into memory, find RO_GSCVD area in the file, and + * cache the information in the passed in file_buf structure. + * + * @param file_name name of the AP firmware file + * @param file_buf pointer to the helper structure keeping information about + * the file + * + * @return 0 on success 1 on failure. + */ +static int load_ap_firmware(const char *file_name, struct file_buf *file) +{ + int fd; + int rv; + + fd = open(file_name, O_RDWR); + if (fd < 0) { + ERROR("Can't open %s: %s\n", file_name, + strerror(errno)); + return 1; + } + + file->fd = fd; + do { + rv = 1; + + if (futil_map_file(fd, MAP_RW, &file->data, &file->len)) { + file->data = NULL; + break; + } + + if (!fmap_find_by_name(file->data, file->len, NULL, "RO_GSCVD", + &file->ro_gscvd)) { + ERROR("Could not find RO_GSCVD in the FMAP\n"); + break; + } + rv = 0; + } while (false); + + return rv; +} + +/** + * Check if the passed in offset falls into the passed in FMAP area. + */ +static bool in_range(uint32_t offset, const FmapAreaHeader *ah) +{ + return (offset >= ah->area_offset) && + (offset <= (ah->area_offset + ah->area_size)); +} + +/** + * Check if the passed in range fits into the passed in FMAP area. + */ +static bool range_fits(const struct gscvd_ro_range *range, + const FmapAreaHeader *ah) +{ + if (in_range(range->offset, ah) && + in_range(range->offset + range->size, ah)) + return true; + + ERROR("Range %#x..+%#x does not fit in %s\n", range->offset, + range->size, ah->area_name); + + return false; +} + +/** + * Check if the passed in range overlaps with the area. + * + * @param range pointer to the range to check + * @param offset offset of the area to check against + * @param size size of the area to check against + * + * @return true if range overlaps with the area, false otherwise. + */ +static bool range_overlaps(const struct gscvd_ro_range *range, uint32_t offset, + size_t size) +{ + if (((range->offset + range->size) <= offset) || + (offset + size) <= range->offset) + return false; + + ERROR("Range %x..+%x overlaps with %x..+%zx\n", range->offset, + range->size, offset, size); + + return true; +} + +/* + * Check validity of the passed in ranges. + * + * All ranges must + * - fit into the WP_RO FMAP area + * - not overlap with the RO_GSCVD FMAP area + * - not overlap with each other + * + * @param ranges - pointer to the container of ranges to check + * @param file - pointer to the file layout descriptor + * + * @return zero on success, -1 on failures + */ +static int verify_ranges(const struct gscvd_ro_ranges *ranges, + const struct file_buf *file) +{ + size_t i; + FmapAreaHeader *wp_ro; + int errorcount; + + if (!fmap_find_by_name(file->data, file->len, NULL, "WP_RO", &wp_ro)) { + ERROR("Could not find WP_RO in the FMAP\n"); + return 1; + } + + errorcount = 0; + for (i = 0; i < ranges->range_count; i++) { + size_t j; + + /* Must fit into WP_RO. */ + if (!range_fits(ranges->ranges + i, wp_ro)) + errorcount++; + + /* Must not overlap with RO_GSCVD. */ + if (range_overlaps(ranges->ranges + i, + file->ro_gscvd->area_offset, + file->ro_gscvd->area_size)) + errorcount++; + + /* The last range is nothing to compare against. */ + if (i == ranges->range_count - 1) + break; + + /* Must not overlap with all following ranges. */ + for (j = i + 1; j < ranges->range_count; j++) + if (range_overlaps(ranges->ranges + i, + ranges->ranges[j].offset, + ranges->ranges[j].size)) + errorcount++; + } + + return errorcount ? -1 : 0; +} + +/** + * Parse range specification supplied by the user. + * + * The input is a string of the following format: + * <hex base>:<hex size>[,<hex base>:<hex size>[,...]] + * + * @param input user input, part of the command line + * @param output pointer to the ranges container + * + * @return zero on success, -1 on failure + */ +static int parse_ranges(const char *input, struct gscvd_ro_ranges *output) +{ + char *cursor; + char *delim; + char *str = strdup(input); + int rv = 0; + + if (!str) { + ERROR("Failed to allocate memory for ranges string copy!\n"); + return -1; + } + + output->range_count = 0; + cursor = str; + do { + char *colon; + char *e; + + if (output->range_count >= ARRAY_SIZE(output->ranges)) { + ERROR("Too many ranges!\n"); + rv = -1; + break; + } + + delim = strchr(cursor, ','); + if (delim) + *delim = '\0'; + colon = strchr(cursor, ':'); + if (!colon) { + rv = -1; + break; + } + *colon = '\0'; + + errno = 0; + output->ranges[output->range_count].offset = + strtol(cursor, &e, 16); + if (errno || *e) { + rv = -1; + break; + } + + output->ranges[output->range_count].size = + strtol(colon + 1, &e, 16); + if (errno || *e) { + rv = -1; + break; + } + + output->range_count++; + cursor = delim + 1; + /* Iterate until there is no more commas. */ + } while (delim); + + free(str); + if (rv) + ERROR("Misformatted ranges string\n"); + + return rv; +} + +/** + * Calculate hash of the RO ranges. + * + * @param ap_firmware_file pointer to the AP firmware file layout descriptor + * @param ranges pointer to the container of ranges to include in hash + * calculation + * @param hash_alg algorithm to use for hashing + * @param digest memory to copy the calculated hash to + * @param digest_ size requested size of the digest, padded with zeros if the + * SHA digest size is smaller than digest_size + * + * @return zero on success, -1 on failure. + */ +static int calculate_ranges_digest(const struct file_buf *ap_firmware_file, + const struct gscvd_ro_ranges *ranges, + enum vb2_hash_algorithm hash_alg, + void *digest, size_t digest_size) +{ + struct vb2_digest_context dc; + size_t i; + + /* Calculate the ranges digest. */ + if (vb2_digest_init(&dc, hash_alg) != VB2_SUCCESS) { + ERROR("Failed to init digest!\n"); + return 1; + } + + for (i = 0; i < ranges->range_count; i++) { + if (vb2_digest_extend(&dc, + ap_firmware_file->data + + ranges->ranges[i].offset, + ranges->ranges[i].size) != VB2_SUCCESS) { + ERROR("Failed to extend digest!\n"); + return -1; + } + } + + memset(digest, 0, digest_size); + if (vb2_digest_finalize(&dc, digest, digest_size) != VB2_SUCCESS) { + ERROR("Failed to finalize digest!\n"); + return -1; + } + + return 0; +} + +/** + * Build GSC verification data. + * + * Calculate size of the structure including the signature and the root key, + * allocate memory, fill up the structure, calculate AP RO ranges digest and + * then the GVD signature. + * + * @param ap_firmware_file pointer to the AP firmware file layout descriptor + * @param ranges pointer to the container of ranges to include in verification + * @param root_pubk pointer to the root pubk container + * @param privk pointer to the private key to use for signing + * @param board_id Board ID value to use. + * + * @return pointer to the created GVD (to be freed by the caller) on success, + * NULL on failure. + */ +static +struct gsc_verification_data *create_gvd(struct file_buf *ap_firmware_file, + struct gscvd_ro_ranges *ranges, + const struct vb2_packed_key *root_pubk, + const struct vb2_private_key *privk, + uint32_t board_id) +{ + struct gsc_verification_data *gvd; + size_t total_size; + size_t sig_size; + size_t ranges_size; + struct vb2_signature *sig; + const FmapHeader *fmh; + + sig_size = vb2_rsa_sig_size(privk->sig_alg); + ranges_size = ranges->range_count * sizeof(struct gscvd_ro_range); + total_size = sizeof(struct gsc_verification_data) + + root_pubk->key_size + sig_size + ranges_size; + + gvd = calloc(total_size, 1); + + if (!gvd) { + ERROR("Failed to allocate %zd bytes for gvd\n", total_size); + return NULL; + } + + gvd->gv_magic = GSC_VD_MAGIC; + gvd->size = total_size; + gvd->gsc_board_id = board_id; + gvd->rollback_counter = GSC_VD_ROLLBACK_COUNTER; + + /* Guaranteed to succeed. */ + fmh = fmap_find(ap_firmware_file->data, ap_firmware_file->len); + + gvd->fmap_location = (uintptr_t)fmh - (uintptr_t)ap_firmware_file->data; + + gvd->hash_alg = VB2_HASH_SHA256; + + if (calculate_ranges_digest(ap_firmware_file, ranges, gvd->hash_alg, + gvd->ranges_digest, + sizeof(gvd->ranges_digest))) { + free(gvd); + return NULL; + } + + /* Prepare signature header. */ + vb2_init_signature(&gvd->sig_header, + (uint8_t *)(gvd + 1) + ranges_size, + sig_size, + sizeof(struct gsc_verification_data) + ranges_size); + + /* Copy root key into the structure. */ + vb2_init_packed_key(&gvd->root_key_header, + (uint8_t *)(gvd + 1) + ranges_size + sig_size, + root_pubk->key_size); + vb2_copy_packed_key(&gvd->root_key_header, root_pubk); + + /* Copy ranges into the ranges section. */ + gvd->range_count = ranges->range_count; + memcpy(gvd->ranges, ranges->ranges, ranges_size); + + sig = vb2_calculate_signature((uint8_t *)gvd, + sizeof(struct gsc_verification_data) + + ranges_size, privk); + if (!sig) { + ERROR("Failed to calculate signature\n"); + free(gvd); + return NULL; + } + + /* Copy signature body into GVD after some basic checks. */ + if ((sig_size == sig->sig_size) && + (gvd->sig_header.data_size == sig->data_size)) { + vb2_copy_signature(&gvd->sig_header, sig); + } else { + ERROR("Inconsistent signature headers\n"); + free(sig); + free(gvd); + return NULL; + } + + free(sig); + + return gvd; +} + +/** + * Fill RO_GSCVD FMAP area. + * + * All trust chain components have been verified, AP RO sections digest + * calculated, and GVD signature created; put it all together in the dedicated + * FMAP area. + * + * @param ap_firmware_file pointer to the AP firmware file layout descriptor + * @param gvd pointer to the GVD header + * @param keyblock pointer to the keyblock container + * + * @return zero on success, -1 on failure + */ +static int fill_gvd_area(struct file_buf *ap_firmware_file, + struct gsc_verification_data *gvd, + struct vb2_keyblock *keyblock) +{ + size_t total; + uint8_t *cursor; + + /* How much room is needed for the whole thing? */ + total = gvd->size + keyblock->keyblock_size; + + if (total > ap_firmware_file->ro_gscvd->area_size) { + ERROR("GVD section does not fit, %zd > %d\n", + total, ap_firmware_file->ro_gscvd->area_size); + return -1; + } + + cursor = ap_firmware_file->data + + ap_firmware_file->ro_gscvd->area_offset; + + /* Copy GSC verification data */ + memcpy(cursor, gvd, gvd->size); + cursor += gvd->size; + + /* Keyblock, size includes everything. */ + memcpy(cursor, keyblock, keyblock->keyblock_size); + + return 0; +} + +/** + * Initialize a work buffer structure. + * + * Embedded vboot reference code does not use malloc/free, it uses the so + * called work buffer structure to provide a poor man's memory management + * tool. This program uses some of the embedded library functions, let's + * implement work buffer support to keep the embedded code happy. + * + * @param wb pointer to the workubffer structure to initialize + * @param size size of the buffer to allocate + * + * @return pointer to the allocated buffer on success, NULL on failure. + */ +static void *init_wb(struct vb2_workbuf *wb, size_t size) +{ + void *buf = malloc(size); + + if (!buf) + ERROR("Failed to allocate workblock of %zd\n", size); + else + vb2_workbuf_init(wb, buf, size); + + return buf; +} + +/** + * Validate that platform key keyblock was signed by the root key. + * + * This function performs the same step the GSC is supposed to perform: + * validate the platform key keyblock signature using the root public key. + * + * @param root_pubk pointer to the root public key container + * @param kblock pointer to the platform public key keyblock + * + * @return 0 on success, -1 on failure + */ +static int validate_pubk_signature(const struct vb2_packed_key *root_pubk, + struct vb2_keyblock *kblock) +{ + struct vb2_public_key pubk; + struct vb2_workbuf wb; + uint32_t kbsize; + int rv; + void *buf; + + if (vb2_unpack_key(&pubk, root_pubk) != VB2_SUCCESS) { + ERROR("Failed to unpack public key\n"); + return -1; + } + + /* Let's create an ample sized work buffer. */ + buf = init_wb(&wb, 8192); + if (!buf) + return -1; + + rv = -1; + do { + void *work; + + kbsize = kblock->keyblock_size; + work = vb2_workbuf_alloc(&wb, kbsize); + if (!work) { + ERROR("Failed to allocate workblock space %d\n", + kbsize); + break; + } + + memcpy(work, kblock, kbsize); + + if (vb2_verify_keyblock(work, kbsize, &pubk, &wb) != + VB2_SUCCESS) { + ERROR("Root and keyblock mismatch\n"); + break; + } + + rv = 0; + } while (false); + + free(buf); + + return rv; +} + +/** + * Validate that private and public parts of the platform key match. + * + * This is a fairly routine validation, the N components of the private and + * public RSA keys are compared. + * + * @param keyblock pointer to the keyblock containing the public key + * @param plat_privk pointer to the matching private key + * + * @return 0 on success, nonzero on failure + */ +static int validate_privk(struct vb2_keyblock *kblock, + struct vb2_private_key *plat_privk) +{ + const BIGNUM *privn; + BIGNUM *pubn; + struct vb2_public_key pubk; + int rv; + + privn = pubn = NULL; + + RSA_get0_key(plat_privk->rsa_private_key, &privn, NULL, NULL); + + if (vb2_unpack_key(&pubk, &kblock->data_key) != VB2_SUCCESS) { + ERROR("Failed to unpack public key\n"); + return -1; + } + + pubn = BN_new(); + pubn = BN_lebin2bn((uint8_t *)pubk.n, vb2_rsa_sig_size(pubk.sig_alg), + pubn); + rv = BN_cmp(pubn, privn); + if (rv) + ERROR("Public/private key N mismatch!\n"); + + BN_free(pubn); + return rv; +} + +/** + * Copy ranges from AP firmware file into gscvd_ro_ranges container + * + * While copying the ranges verify that they do not overlap. + * + * @param ap_firmware_file pointer to the AP firmware file layout descriptor + * @param gvd pointer to the GVD header followed by the ranges + * @param ranges pointer to the ranges container to copy ranges to + * + * @return 0 on successful copy nonzero on errors. + */ +static int copy_ranges(const struct file_buf *ap_firmware_file, + const struct gsc_verification_data *gvd, + struct gscvd_ro_ranges *ranges) +{ + ranges->range_count = gvd->range_count; + memcpy(ranges->ranges, gvd->ranges, + sizeof(ranges->ranges[0]) * ranges->range_count); + + return verify_ranges(ranges, ap_firmware_file); +} + +/** + * Basic validation of GVD included in a AP firmware file. + * + * This is not a cryptographic verification, just a check that the structure + * makes sense and the expected values are found in certain fields. + * + * @param gvd pointer to the GVD header followed by the ranges + * @param ap_firmware_file pointer to the AP firmware file layout descriptor + * + * @return zero on success, -1 on failure. + */ +static int validate_gvd(const struct gsc_verification_data *gvd, + const struct file_buf *ap_firmware_file) +{ + const FmapHeader *fmh; + + if (gvd->gv_magic != GSC_VD_MAGIC) { + ERROR("Incorrect gscvd magic %x\n", gvd->gv_magic); + return -1; + } + + if (!gvd->range_count || (gvd->range_count > MAX_RANGES)) { + ERROR("Incorrect gscvd range count %d\n", gvd->range_count); + return -1; + } + + /* Guaranteed to succeed. */ + fmh = fmap_find(ap_firmware_file->data, ap_firmware_file->len); + + if (gvd->fmap_location != + ((uintptr_t)fmh - (uintptr_t)ap_firmware_file->data)) { + ERROR("Incorrect gscvd fmap offset %x\n", gvd->fmap_location); + return -1; + } + + /* Make sure signature and root key fit. */ + if (vb2_verify_signature_inside(gvd, gvd->size, &gvd->sig_header) != + VB2_SUCCESS) { + ERROR("Corrupted signature header in GVD\n"); + return -1; + } + + if (vb2_verify_packed_key_inside(gvd, gvd->size, + &gvd->root_key_header) != + VB2_SUCCESS) { + ERROR("Corrupted root key header in GVD\n"); + return -1; + } + + return 0; +} + +/** + * Validate GVD signature. + * + * Given the entire GVD space (header plus ranges array), the signature and + * the public key, verify that the signature matches. + * + * @param gvd pointer to gsc_verification_data followed by the ranges array + * @param gvd_signature pointer to the vb2 signature container + * @param packedk pointer to the keyblock containing the public key + * + * @return zero on success, non-zero on failure + */ +static int validate_gvd_signature(struct gsc_verification_data *gvd, + const struct vb2_packed_key *packedk) +{ + struct vb2_workbuf wb; + void *buf; + int rv; + struct vb2_public_key pubk; + size_t signed_size; + + /* Extract public key from the public key keyblock. */ + if (vb2_unpack_key(&pubk, packedk) != VB2_SUCCESS) { + ERROR("Failed to unpack public key\n"); + return -1; + } + + /* Let's create an ample sized work buffer. */ + buf = init_wb(&wb, 8192); + if (!buf) + return -1; + + signed_size = sizeof(struct gsc_verification_data) + + gvd->range_count * sizeof(gvd->ranges[0]); + + rv = vb2_verify_data((const uint8_t *)gvd, signed_size, + &gvd->sig_header, + &pubk, &wb); + + free(buf); + return rv; +} + +/* + * Validate GVD of the passed in AP firmware file and possibly the root key hash + * + * The input parameters are the subset of the command line, the first argv + * string is the AP firmware file name, the second string, if present, is the + * hash of the root public key included in the RO_GSCVD area of the AP + * firmware file. + * + * @return zero on success, -1 on failure. + */ +static int validate_gscvd(int argc, char *argv[]) +{ + struct file_buf ap_firmware_file; + int rv; + struct gscvd_ro_ranges ranges; + struct gsc_verification_data *gvd; + const char *file_name; + uint8_t digest[sizeof(gvd->ranges_digest)]; + struct vb2_hash root_key_digest = { .algo = VB2_HASH_SHA256 }; + + /* Guaranteed to be available. */ + file_name = argv[0]; + + if (argc > 1) + parse_digest_or_die(root_key_digest.sha256, + sizeof(root_key_digest.sha256), + argv[1]); + + do { + struct vb2_keyblock *kblock; + + rv = -1; /* Speculative, will be cleared on success. */ + + if (load_ap_firmware(file_name, &ap_firmware_file)) + break; + + /* Copy ranges from gscvd to local structure. */ + gvd = (struct gsc_verification_data + *)(ap_firmware_file.data + + ap_firmware_file.ro_gscvd->area_offset); + + if (validate_gvd(gvd, &ap_firmware_file)) + break; + + if (copy_ranges(&ap_firmware_file, gvd, &ranges)) + break; + + if (calculate_ranges_digest(&ap_firmware_file, &ranges, + gvd->hash_alg, digest, + sizeof(digest))) + break; + + if (memcmp(digest, gvd->ranges_digest, sizeof(digest))) { + ERROR("Ranges digest mismatch\n"); + break; + } + + /* Find the keyblock. */ + kblock = (struct vb2_keyblock *)((uintptr_t)gvd + gvd->size); + + if ((argc > 1) && (vb2_hash_verify + (vb2_packed_key_data(&gvd->root_key_header), + gvd->root_key_header.key_size, + &root_key_digest) != VB2_SUCCESS)) { + ERROR("Sha256 mismatch\n"); + break; + } + + if (validate_pubk_signature(&gvd->root_key_header, + kblock)) + break; + + if (validate_gvd_signature(gvd, &kblock->data_key)) + break; + + rv = 0; + } while (false); + + return rv; +} + +/** + * Calculate and report sha256 hash of the public key body. + * + * The hash will be incorporated into GVC firmware to allow it to validate the + * root key. + * + * @param pubk pointer to the public key to process. + */ +static void dump_pubk_hash(const struct vb2_packed_key *pubk) +{ + struct vb2_hash hash; + size_t i; + + vb2_hash_calculate(vb2_packed_key_data(pubk), pubk->key_size, + VB2_HASH_SHA256, &hash); + + printf("Root key body sha256 hash:\n"); + + for (i = 0; i < sizeof(hash.sha256); i++) + printf("%02x", hash.sha256[i]); + + printf("\n"); +} + +/** + * The main function of this futilty option. + * + * See the usage string for input details. + * + * @return zero on success, nonzero on failure. + */ +static int do_gscvd(int argc, char *argv[]) +{ + int i; + int longindex; + char *infile = NULL; + char *outfile = NULL; + char *work_file = NULL; + struct gscvd_ro_ranges ranges; + int errorcount = 0; + struct vb2_packed_key *root_pubk = NULL; + struct vb2_keyblock *kblock = NULL; + struct vb2_private_key *plat_privk = NULL; + struct gsc_verification_data *gvd = NULL; + struct file_buf ap_firmware_file = { .fd = -1 }; + uint32_t board_id = UINT32_MAX; + int rv = 0; + + ranges.range_count = 0; + + while ((i = getopt_long(argc, argv, short_opts, long_opts, + &longindex)) != -1) { + switch (i) { + case OPT_OUTFILE: + outfile = optarg; + break; + case 'R': + if (parse_ranges(optarg, &ranges)) { + ERROR("Could not parse ranges\n"); + /* Error message has been already printed. */ + errorcount++; + } + break; + case 'b': { + char *e; + long long bid; + + bid = strtoull(optarg, &e, 16); + if (*e || (bid >= UINT32_MAX)) { + ERROR("Board ID value '%s' is invalid\n", + optarg); + errorcount++; + } else { + board_id = (uint32_t)bid; + } + break; + } + case 'r': + root_pubk = vb2_read_packed_key(optarg); + if (!root_pubk) { + ERROR("Could not read %s\n", optarg); + errorcount++; + } + break; + case 'k': + kblock = vb2_read_keyblock(optarg); + if (!kblock) { + ERROR("Could not read %s\n", optarg); + errorcount++; + } + break; + case 'p': + plat_privk = vb2_read_private_key(optarg); + if (!plat_privk) { + ERROR("Could not read %s\n", optarg); + errorcount++; + } + break; + case 'h': + printf("%s", usage); + return 0; + case '?': + if (optopt) + ERROR("Unrecognized option: -%c\n", optopt); + else + ERROR("Unrecognized option: %s\n", + argv[optind - 1]); + errorcount++; + break; + case ':': + ERROR("Missing argument to -%c\n", optopt); + errorcount++; + break; + case 0: /* handled option */ + break; + default: + FATAL("Unrecognized getopt output: %d\n", i); + } + } + + if ((optind == 1) && (argc > 1)) + /* This must be a validation request. */ + return validate_gscvd(argc - 1, argv + 1); + + if (optind != (argc - 1)) { + ERROR("Misformatted command line\n%s\n", usage); + return 1; + } + + if (errorcount || !ranges.range_count || !root_pubk || !kblock || + !plat_privk || (board_id == UINT32_MAX)) { + /* Error message(s) should have been printed by now. */ + ERROR("%s\n", usage); + return 1; + } + + infile = argv[optind]; + + if (outfile) { + futil_copy_file_or_die(infile, outfile); + work_file = outfile; + } else { + work_file = infile; + } + + do { + rv = 1; /* Speculative, will be cleared on success. */ + + if (validate_pubk_signature(root_pubk, kblock)) + break; + + if (validate_privk(kblock, plat_privk)) + break; + + if (load_ap_firmware(work_file, &ap_firmware_file)) + break; + + if (verify_ranges(&ranges, &ap_firmware_file)) + break; + + gvd = create_gvd(&ap_firmware_file, &ranges, + root_pubk, plat_privk, board_id); + if (!gvd) + break; + + if (fill_gvd_area(&ap_firmware_file, gvd, kblock)) + break; + + dump_pubk_hash(root_pubk); + + rv = 0; + } while (false); + + free(gvd); + free(root_pubk); + free(kblock); + vb2_private_key_free(plat_privk); + + /* Now flush the file. */ + if (ap_firmware_file.data) { + rv |= futil_unmap_file(ap_firmware_file.fd, true, + ap_firmware_file.data, + ap_firmware_file.len); + } + + if (ap_firmware_file.fd != -1) + close(ap_firmware_file.fd); + + return rv; +} + +DECLARE_FUTIL_COMMAND(gscvd, do_gscvd, VBOOT_VERSION_2_1, + "Create RO verification structure"); diff --git a/futility/cmd_pcr.c b/futility/cmd_pcr.c index dc4c3ffc..e267b24a 100644 --- a/futility/cmd_pcr.c +++ b/futility/cmd_pcr.c @@ -40,57 +40,6 @@ static void print_help(int argc, char *argv[]) printf(usage, argv[0], argv[0], argv[0]); } -static int parse_hex(uint8_t *val, const char *str) -{ - uint8_t v = 0; - char c; - int digit; - - for (digit = 0; digit < 2; digit++) { - c = *str; - if (!c) - return 0; - if (!isxdigit(c)) - return 0; - c = tolower(c); - if (c >= '0' && c <= '9') - v += c - '0'; - else - v += 10 + c - 'a'; - if (!digit) - v <<= 4; - str++; - } - - *val = v; - return 1; -} - -static void parse_digest_or_die(uint8_t *buf, int len, const char *str) -{ - const char *s = str; - int i; - - for (i = 0; i < len; i++) { - /* skip whitespace */ - while (*s && isspace(*s)) - s++; - if (!*s) - break; - if (!parse_hex(buf, s)) - break; - - /* on to the next byte */ - s += 2; - buf++; - } - - if (i != len) { - fprintf(stderr, "Invalid DIGEST \"%s\"\n", str); - exit(1); - } -} - static void print_digest(const uint8_t *buf, int len) { int i; diff --git a/futility/futility.h b/futility/futility.h index 1f34713e..9d40ba7e 100644 --- a/futility/futility.h +++ b/futility/futility.h @@ -140,6 +140,12 @@ enum futil_file_err futil_map_file(int fd, int writeable, enum futil_file_err futil_unmap_file(int fd, int writeable, uint8_t *buf, uint32_t len); +/* + * Parse input string as a hex representation of size len, exit with error if + * the string is not a valid hex string or is of a wrongs size. + */ +void parse_digest_or_die(uint8_t *buf, int len, const char *str); + /* The CPU architecture is occasionally important */ enum arch_t { ARCH_UNSPECIFIED, diff --git a/futility/misc.c b/futility/misc.c index f3e7748e..76b8ad1e 100644 --- a/futility/misc.c +++ b/futility/misc.c @@ -352,3 +352,54 @@ enum futil_file_type ft_recognize_gpt(uint8_t *buf, uint32_t len) return FILE_TYPE_CHROMIUMOS_DISK; } + +static int parse_hex(uint8_t *val, const char *str) +{ + uint8_t v = 0; + char c; + int digit; + + for (digit = 0; digit < 2; digit++) { + c = *str; + if (!c) + return 0; + if (!isxdigit(c)) + return 0; + c = tolower(c); + if (c >= '0' && c <= '9') + v += c - '0'; + else + v += 10 + c - 'a'; + if (!digit) + v <<= 4; + str++; + } + + *val = v; + return 1; +} + +void parse_digest_or_die(uint8_t *buf, int len, const char *str) +{ + const char *s = str; + int i; + + for (i = 0; i < len; i++) { + /* skip whitespace */ + while (*s && isspace(*s)) + s++; + if (!*s) + break; + if (!parse_hex(buf, s)) + break; + + /* on to the next byte */ + s += 2; + buf++; + } + + if ((i != len) || *s) { + fprintf(stderr, "Invalid DIGEST \"%s\"\n", str); + exit(1); + } +} diff --git a/host/lib/include/gsc_ro.h b/host/lib/include/gsc_ro.h new file mode 100644 index 00000000..00a40111 --- /dev/null +++ b/host/lib/include/gsc_ro.h @@ -0,0 +1,59 @@ +/* + * Copyright 2021 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. + */ + +#ifndef __VBOOT_REFERENCE_HOST_LIB_INCLUDE_GSC_RO_H +#define __VBOOT_REFERENCE_HOST_LIB_INCLUDE_GSC_RO_H + +#include <stddef.h> +#include <stdint.h> + +#include "2sha.h" + +struct gscvd_ro_range { + uint32_t offset; + uint32_t size; /* Use uint32_t as opposed to size_to be portable. */ +}; + +#define GSC_VD_MAGIC 0x65666135 /* Little endian '5 a f e' */ +#define GSC_VD_ROLLBACK_COUNTER 1 + +struct gsc_verification_data { + uint32_t gv_magic; + /* + * Size of this structure in bytes, including the ranges array, + * signature and root key bodies. + */ + uint16_t size; + uint16_t major_version; /* Version of this struct layout. Starts at 0 */ + uint16_t minor_version; + /* + * GSC will cache the counter value and will not accept verification + * data blobs with a lower value. + */ + uint16_t rollback_counter; + uint32_t gsc_board_id; /* Locks blob to certain platform. */ + uint32_t gsc_flags; /* A field for future enhancements. */ + /* + * The location of fmap that points to this blob. This location must + * also be in one of the verified sections, expressed as offset in + * flash + */ + uint32_t fmap_location; + uint32_t hash_alg; /* one of enum vb2_hash_algorithm alg. */ + struct vb2_signature sig_header; + struct vb2_packed_key root_key_header; + /* + * SHAxxx(ranges[0].offset..ranges[0].size || ... || + * ranges[n].offset..ranges[n].size) + * + * Let the digest space allow to accommodate the largest possible one. + */ + uint8_t ranges_digest[VB2_SHA512_DIGEST_SIZE]; + uint32_t range_count; /* Number of gscvd_ro_range entries. */ + struct gscvd_ro_range ranges[0]; +}; + +#endif /* ! __VBOOT_REFERENCE_HOST_LIB_INCLUDE_GSC_RO_H */ |