futility: Support for signing RO+RW firmware

This adds the "rwsig" type, with initial support for RO+RW
firmware images that need to verify themselves instead of using
software sync. This uses our vb2 structs instead of raw binary
blobs. That will help us locate, identify, and verify the keys
and signatures in the signed firmware images.

BUG=chrome-os-partner:46254
BRANCH=smaug,ToT
TEST=make runtests

I also hacked up a test board with the EC-side signature
verification routines from a preliminary CL and tested this
signing scheme with that. It works.

Additional work is needed to make this seamless, but you can try
it out like so:

  futility create ./tests/testkeys/key_rsa2048.pem foo

  futility sign --type rwsig --prikey foo.vbprik2 --pubkey foo.vbpubk2 ec.bin

Change-Id: I876ab312a2b0b36411c5f739fe3252529728d034
Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/305394
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/Makefile b/Makefile
index 225097f..40d2422 100644
--- a/Makefile
+++ b/Makefile
@@ -632,6 +632,7 @@
 	futility/cmd_vbutil_keyblock.c \
 	futility/file_type.c \
 	futility/file_type_bios.c \
+	futility/file_type_rwsig.c \
 	futility/file_type_usbpd1.c \
 	futility/vb1_helper.c \
 	futility/vb2_helper.c
diff --git a/futility/cmd_sign.c b/futility/cmd_sign.c
index 3152091..e6184f4 100644
--- a/futility/cmd_sign.c
+++ b/futility/cmd_sign.c
@@ -43,6 +43,8 @@
 	.rw_size = 0xffffffff,
 	.ro_offset = 0xffffffff,
 	.rw_offset = 0xffffffff,
+	.pkey_offset = 0xffffffff,
+	.sig_offset = 0xffffffff,
 };
 
 /* Helper to complain about invalid args. Returns num errors discovered */
@@ -462,6 +464,49 @@
 	       "\n");
 }
 
+/* The rwsig help is the same as the usbpd1 help, for now anyway. */
+static void print_help_rwsig(int argc, char *argv[])
+{
+	printf("\n"
+	       "Usage:  " MYNAME " %s --type %s [options] INFILE [OUTFILE]\n"
+	       "\n"
+	       "This signs a %s.\n"
+	       "\n"
+	       "The INPUT is assumed to consist of equal-sized RO and RW"
+	       " sections.\n"
+	       "Signing the RW image will put the signature in the RW half."
+	       " If the public\n"
+	       "key is provided, it will be copied to the RO half.\n"
+	       "\n"
+	       "Options:\n"
+	       "\n"
+	       "  --prikey         FILE.vbprik2"
+	       "           Private key in vb2 format\n"
+	       "  --pubkey         FILE.vbpubk2"
+	       "           Public key in vb2 format\n"
+	       "\n"
+	       "The size and offset assumptions can be overridden. "
+	       "All numbers are in bytes.\n"
+	       "Specify a size of 0 to ignore that section.\n"
+	       "\n"
+	       "  --rw_size        NUM"
+	       "           Size of the RW section (default half)\n"
+	       "  --rw_offset      NUM"
+	       "           Start of the RW section (default half)\n"
+	       "  --sig_offset     NUM"
+	       "           Where to place the signature (default is\n"
+	       "                      "
+	       "             near the end of the RW image)\n"
+	       "  --pkey_offset    NUM"
+	       "           Where to place the public key (default is\n"
+	       "                      "
+	       "             near the end of the RO image)\n"
+	       "\n",
+	       argv[0],
+	       futil_file_type_name(FILE_TYPE_RWSIG),
+	       futil_file_type_desc(FILE_TYPE_RWSIG));
+}
+
 static void (*help_type[NUM_FILE_TYPES])(int argc, char *argv[]) = {
 	[FILE_TYPE_PUBKEY] = &print_help_pubkey,
 	[FILE_TYPE_RAW_FIRMWARE] = &print_help_raw_firmware,
@@ -469,6 +514,7 @@
 	[FILE_TYPE_RAW_KERNEL] = &print_help_raw_kernel,
 	[FILE_TYPE_KERN_PREAMBLE] = &print_help_kern_preamble,
 	[FILE_TYPE_USBPD1] = &print_help_usbpd1,
+	[FILE_TYPE_RWSIG] = &print_help_rwsig,
 };
 
 static const char usage_default[] = "\n"
@@ -482,9 +528,11 @@
 	"  full firmware image (bios.bin)      same, or signed in-place\n"
 	"  raw linux kernel (vmlinuz)          kernel partition image\n"
 	"  kernel partition (/dev/sda2)        same, or signed in-place\n"
+	"  usbpd1 firmware image               same, or signed in-place\n"
+	"  RO+RW firmware image                same, or signed in-place\n"
 	"\n"
-	"For more information, use \"" MYNAME " help %s TYPE\",\n"
-	"where TYPE is one of:\n\n";
+	"For more information, use \"" MYNAME " help %s TYPE\", where\n"
+	"TYPE is one of:\n\n";
 static void print_help_default(int argc, char *argv[])
 {
 	enum futil_file_type type;
@@ -527,6 +575,10 @@
 	OPT_RW_SIZE,
 	OPT_RO_OFFSET,
 	OPT_RW_OFFSET,
+	OPT_PKEY_OFFSET,
+	OPT_SIG_OFFSET,
+	OPT_PRIKEY,
+	OPT_PUBKEY,
 	OPT_HELP,
 };
 
@@ -562,6 +614,11 @@
 	{"rw_size",      1, NULL, OPT_RW_SIZE},
 	{"ro_offset",    1, NULL, OPT_RO_OFFSET},
 	{"rw_offset",    1, NULL, OPT_RW_OFFSET},
+	{"pkey_offset",  1, NULL, OPT_PKEY_OFFSET},
+	{"sig_offset",   1, NULL, OPT_SIG_OFFSET},
+	{"prikey",       1, NULL, OPT_PRIKEY},
+	{"privkey",      1, NULL, OPT_PRIKEY},	/* alias */
+	{"pubkey",       1, NULL, OPT_PUBKEY},
 	{"help",         0, NULL, OPT_HELP},
 	{NULL,           0, NULL, 0},
 };
@@ -728,6 +785,14 @@
 			errorcnt += parse_number_opt(optarg, "rw_offset",
 						     &sign_option.rw_offset);
 			break;
+		case OPT_PKEY_OFFSET:
+			errorcnt += parse_number_opt(optarg, "pkey_offset",
+						     &sign_option.pkey_offset);
+			break;
+		case OPT_SIG_OFFSET:
+			errorcnt += parse_number_opt(optarg, "sig_offset",
+						     &sign_option.sig_offset);
+			break;
 		case OPT_PEM_SIGNPRIV:
 			sign_option.pem_signpriv = optarg;
 			break;
@@ -762,6 +827,19 @@
 				errorcnt++;
 			}
 			break;
+		case OPT_PRIKEY:
+			if (vb2_private_key_read(&sign_option.prikey,
+						 optarg)) {
+				fprintf(stderr, "Error reading %s\n", optarg);
+				errorcnt++;
+			}
+			break;
+		case OPT_PUBKEY:
+			if (vb2_packed_key_read(&sign_option.pkey, optarg)) {
+				fprintf(stderr, "Error reading %s\n", optarg);
+				errorcnt++;
+			}
+			break;
 		case OPT_HELP:
 			helpind = optind - 1;
 			break;
@@ -895,6 +973,9 @@
 		errorcnt += no_opt_if(sign_option.hash_alg == VB2_HASH_INVALID,
 				      "hash_alg");
 		break;
+	case FILE_TYPE_RWSIG:
+		errorcnt += no_opt_if(!sign_option.prikey, "prikey");
+		break;
 	default:
 		/* Anything else we don't care */
 		break;
diff --git a/futility/file_type.inc b/futility/file_type.inc
index 5433d03..fd12286 100644
--- a/futility/file_type.inc
+++ b/futility/file_type.inc
@@ -71,6 +71,10 @@
 	  NONE,
 	  NONE,
 	  NONE)
+FILE_TYPE(RWSIG,            "rwsig",         "RO+RW firmware image",
+	  NONE,
+	  NONE,
+	  S_(ft_sign_rwsig))
 /* Firmware for USB Type-C power adapters */
 FILE_TYPE(USBPD1,           "usbpd1",        "USB-PD charger image (v1.0)",
 	  R_(ft_recognize_usbpd1),
diff --git a/futility/file_type_rwsig.c b/futility/file_type_rwsig.c
new file mode 100644
index 0000000..6b757ff
--- /dev/null
+++ b/futility/file_type_rwsig.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/*
+ * Some instances of the Chrome OS embedded controller firmware can't do a
+ * normal software sync handshake at boot, but will verify their own RW images
+ * instead. This is typically done by putting a struct vb2_packed_key in the RO
+ * image and a corresponding struct vb2_signature in the RW image.
+ *
+ * This file provides the basic implementation for that approach.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "2sysincludes.h"
+#include "2common.h"
+#include "2rsa.h"
+#include "2sha.h"
+#include "file_type.h"
+#include "futility.h"
+#include "futility_options.h"
+#include "vb2_common.h"
+#include "host_common.h"
+#include "host_key2.h"
+#include "host_signature2.h"
+#include "util_misc.h"
+
+/*
+ * Reserved space for the public key and signature. This may not be enough for
+ * larger key sizes since the vb2 structs are more than just the raw bits.
+ */
+#define PUBKEY_RSVD_SIZE    2048
+#define SIGNATURE_RSVD_SIZE 1024
+
+/* True if start + size > max */
+static int bigger_than(uint32_t start, uint32_t size, uint32_t max)
+{
+	int r = start > max || size > max || start > max - size;
+	if (r)
+		Debug("%s: 0x%x + 0x%x > 0x%x\n", __func__, start, size, max);
+	return r;
+}
+
+/* True if one region overlaps the other */
+static int overlaps(uint32_t start_a, uint32_t size_a,
+		    uint32_t start_b, uint32_t size_b)
+{
+	if (start_a < start_b && start_a <= start_b - size_a)
+		return 0;
+	if (start_b < start_a && start_b <= start_a - size_b)
+		return 0;
+	Debug("%s: 0x%x + 0x%x overlaps 0x%x + 0x%x\n",
+	      __func__, start_a, size_a, start_b, size_b);
+	return 1;
+}
+
+/* Return 1 if okay, 0 if not */
+static int parse_size_opts(const uint8_t *buf, uint32_t len,
+			   uint32_t *rw_offset_ptr, uint32_t *rw_size_ptr,
+			   uint32_t *pkey_offset_ptr, uint32_t *sig_offset_ptr)
+{
+	uint32_t rw_offset, rw_size, pkey_offset, sig_offset;
+
+	/* Start with defaults */
+
+	/* The image has both RO and RW, evenly split, RO first. */
+	rw_size = rw_offset = len / 2;
+
+	/* The public key is up against the end of the RO half */
+	pkey_offset = rw_offset - PUBKEY_RSVD_SIZE;
+
+	/* The signature key is up against the end of the whole image */
+	sig_offset = len - SIGNATURE_RSVD_SIZE;
+
+	/* The RW image to be signed doesn't include the signature */
+	rw_size -= SIGNATURE_RSVD_SIZE;
+
+	/* FIXME: Override the defaults here by looking for an FMAP or similar
+	 * structure telling us where the parts are. */
+
+	/* We can override any of that with explicit args */
+	if (sign_option.rw_offset != 0xffffffff)
+		rw_offset = sign_option.rw_offset;
+	if (sign_option.rw_size != 0xffffffff)
+		rw_size = sign_option.rw_size;
+	if (sign_option.pkey_offset != 0xffffffff)
+		pkey_offset = sign_option.pkey_offset;
+	if (sign_option.sig_offset != 0xffffffff)
+		sig_offset = sign_option.sig_offset;
+
+	Debug("pkey_offset 0x%08x\n", pkey_offset);
+	Debug("rw_offset   0x%08x\n", rw_offset);
+	Debug("rw_size     0x%08x\n", rw_size);
+	Debug("sig_offset  0x%08x\n", sig_offset);
+
+	/* Now let's do some sanity checks. */
+	if (bigger_than(rw_offset, rw_size, len) ||
+	    overlaps(rw_offset, rw_size, pkey_offset, PUBKEY_RSVD_SIZE) ||
+	    overlaps(rw_offset, rw_size, sig_offset, SIGNATURE_RSVD_SIZE) ||
+	    overlaps(pkey_offset, PUBKEY_RSVD_SIZE,
+		     sig_offset, SIGNATURE_RSVD_SIZE)) {
+		printf("size/offset values are bogus\n");
+		return 0;
+	}
+
+	*rw_offset_ptr = rw_offset;
+	*rw_size_ptr = rw_size;
+	*pkey_offset_ptr = pkey_offset;
+	*sig_offset_ptr = sig_offset;
+
+	return 1;
+}
+
+int ft_sign_rwsig(const char *name, uint8_t *buf, uint32_t len, void *data)
+{
+	struct vb2_signature *sig = 0;
+	int retval = 1;
+	uint32_t rw_offset, rw_size;		/* what to sign */
+	uint32_t pkey_offset, sig_offset;	/* where to put blobs */
+	uint32_t r;
+
+	Debug("%s(): name %s\n", __func__, name);
+	Debug("%s(): len  0x%08x (%d)\n", __func__, len, len);
+
+	/* Figure out what to sign and where to put the blobs */
+	if (!parse_size_opts(buf, len,
+			     &rw_offset, &rw_size,
+			     &pkey_offset, &sig_offset))
+		goto done;
+
+	/* Sign the blob */
+	r = vb2_sign_data(&sig, buf + rw_offset, rw_size,
+			  sign_option.prikey, 0);
+	if (r) {
+		fprintf(stderr,
+			"Unable to sign data (error 0x%08x, if that helps)\n",
+			r);
+		goto done;
+	}
+
+	Debug("sig_offset   0x%08x\n", sig_offset);
+	Debug("sig_size     0x%08x\n", sig->c.total_size);
+
+	if (sig->c.total_size > SIGNATURE_RSVD_SIZE)
+		fprintf(stderr, "WARNING: The signature may be too large"
+			" (0x%08x > %08x)\n",
+			sig->c.total_size, SIGNATURE_RSVD_SIZE);
+
+	/* Update the signature */
+	memcpy(buf + sig_offset, sig, sig->c.total_size);
+
+	/* If weren't given a public key, we're done */
+	if (!sign_option.pkey) {
+		fprintf(stderr, "No public key given; not updating RO\n");
+		retval = 0;
+		goto done;
+	}
+
+	Debug("pkey_offset  0x%08x\n", pkey_offset);
+	Debug("pkey_size    0x%08x\n", sign_option.pkey->c.total_size);
+
+	if (sign_option.pkey->c.total_size > PUBKEY_RSVD_SIZE)
+		fprintf(stderr, "WARNING: The public key may be too large"
+			" (0x%08x > %08x)\n",
+			sign_option.pkey->c.total_size, PUBKEY_RSVD_SIZE);
+
+	/* Update the public key */
+	memcpy(buf + pkey_offset, sign_option.pkey,
+	       sign_option.pkey->c.total_size);
+
+	/* Finally */
+	retval = 0;
+done:
+	if (sign_option.prikey)
+		vb2_private_key_free(sign_option.prikey);
+	if (sign_option.pkey)
+		free(sign_option.pkey);
+
+	return retval;
+}
diff --git a/futility/file_type_usbpd1.c b/futility/file_type_usbpd1.c
index acf3de0..36cb5cf 100644
--- a/futility/file_type_usbpd1.c
+++ b/futility/file_type_usbpd1.c
@@ -12,7 +12,7 @@
  * and the image itself just looks like a bunch of random numbers.
  *
  * This file handles those images, but PLEASE don't use it as a template for
- * new devices.
+ * new devices. Look at file_type_rwsig.c instead.
  */
 
 #include <stdint.h>
@@ -259,7 +259,7 @@
 	VB2_SIG_RSA4096,
 	VB2_SIG_RSA8192,
 };
-enum vb2_hash_algorithm hashes[] = {
+static enum vb2_hash_algorithm hashes[] = {
 	VB2_HASH_SHA256,
 	VB2_HASH_SHA1,
 	VB2_HASH_SHA512,
diff --git a/futility/futility_options.h b/futility/futility_options.h
index c8d0a8a..e02ef2f 100644
--- a/futility/futility_options.h
+++ b/futility/futility_options.h
@@ -16,6 +16,9 @@
 #include "file_type.h"
 #include "2rsa.h"
 
+struct vb2_private_key;
+struct vb2_packed_key;
+
 struct show_option_s {
 	VbPublicKey *k;
 	uint8_t *fv;
@@ -58,6 +61,9 @@
 	enum vb2_hash_algorithm hash_alg;
 	uint32_t ro_size, rw_size;
 	uint32_t ro_offset, rw_offset;
+	uint32_t pkey_offset, sig_offset;
+	struct vb2_private_key *prikey;
+	struct vb2_packed_key *pkey;
 };
 extern struct sign_option_s sign_option;
 
diff --git a/tests/futility/test_file_types.c b/tests/futility/test_file_types.c
index 9f90a0f..6b28629 100644
--- a/tests/futility/test_file_types.c
+++ b/tests/futility/test_file_types.c
@@ -37,6 +37,7 @@
 	{FILE_TYPE_VB2_PRIVKEY,     "tests/futility/data/sample.vbprik2"},
 	{FILE_TYPE_PEM,             "tests/testkeys/key_rsa2048.pem"},
 	{FILE_TYPE_USBPD1,          "tests/futility/data/zinger_mp_image.bin"},
+	{FILE_TYPE_RWSIG,           },		/* need a test for this */
 };
 BUILD_ASSERT(ARRAY_SIZE(test_case) == NUM_FILE_TYPES);