| /* |
| * 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/arv_root.vbprivk as the root private key |
| * - tests/devkeys/arv_root.vbpubk as the root public key |
| * used for signing of the platform public key |
| * - tests/devkeys/arv_platform.vbprivk signing platform key |
| * - tests/devkeys/arv_platform.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/arv_platform.vbpubk \ |
| --signprivate tests/devkeys/arv_root.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/arv_platform.vbprivk -b 5a5a4352 \ |
| -r tests/devkeys/arv_root.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 \ |
| 3d74429f35be8d34bcb425d4397e2218e6961afed456a78ce30047f5b54ed158 |
| */ |
| |
| /* 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 mode) |
| { |
| int fd; |
| int rv; |
| |
| fd = open(file_name, mode); |
| 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, mode == O_RDWR ? MAP_RW : MAP_RO, |
| &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, O_RDONLY)) |
| 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)) { |
| ERROR("Keyblock not signed by root key\n"); |
| break; |
| } |
| |
| if (validate_gvd_signature(gvd, &kblock->data_key)) { |
| ERROR("GVD not signed by platform key\n"); |
| 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, O_RDWR)) |
| 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"); |