summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2021-09-21 19:53:34 -0700
committerCommit Bot <commit-bot@chromium.org>2021-10-18 22:11:41 +0000
commit06780662c89b05684a934ea3a1538df02f512e1c (patch)
tree24a1292be3f5812886fcf4706be96301923cd235
parent2ffa62d35c7be7217287151ab9de14266ee6df66 (diff)
downloadvboot-06780662c89b05684a934ea3a1538df02f512e1c.tar.gz
futility: add subcommand for creating and verifying the RO_GSCVD area
The help text of the new subcommand is as follows: ------- This utility creates an RO verification space in the Chrome OS AP firmware image or allows to validate a previously prepared image containing the RO verification space. Usage: futilitygscvd PARAMS <AP FIRMWARE FILE> [<root key hash>] Creation of RO Verification space: Required PARAMS: -R|--ranges STRING Comma separated colon delimited hex tuples <offset>:<size>, the areas of the RO covered by the signature -r|--root_pub_key <file> The main public key, in .vbpubk format, used to verify platform key -k|--keyblock <file> Signed platform public key in .keyblock format, used for run time RO verifcation -p|--platform_priv <file> Private platform key in .vbprivk format, used for signing RO verification data Optional PARAMS: [--outfile] OUTFILE Output firmware image containing RO verification information Validation of RO Verification space: The only required parameter is <AP FIRMWARE FILE>, if optional <root key hash> is given, it is compared to the hash of the body of the root key found in <AP_FIRMWARE_FILE>. -h|--help Print this message ------- When creating GVD section, the sha256 hash of the root public key payload is printed on stdout, this is the hash to include in the GSC image to for the root key verification. Code converting ASCII hex string into binary is refactored into a misc function. BRANCH=none BUG=b:141191727 TEST=testing included the following steps: . modified guybrush coreboot to allocate an 8KB RO_GSCVD area in FMAP and built a guybrush BIOS image . filled GVD space as described in the source file comments . verified the created space as described in the source file comments . verified AP RO integrity on the GSC size using crrev.com/c/3172256 Change-Id: I51a80be5007a32d5286b93499f71da84f41b3d81 Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3174570 Reviewed-by: Julius Werner <jwerner@chromium.org>
-rw-r--r--Makefile4
-rw-r--r--futility/cmd_gscvd.c1050
-rw-r--r--futility/cmd_pcr.c51
-rw-r--r--futility/futility.h6
-rw-r--r--futility/misc.c51
-rw-r--r--host/lib/include/gsc_ro.h59
6 files changed, 1168 insertions, 53 deletions
diff --git a/Makefile b/Makefile
index 9e41123d..7d074b42 100644
--- a/Makefile
+++ b/Makefile
@@ -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 */