futility: Add show capability for usbpd1 images

The firmware for the USB Type-C power adapters uses raw binary
blobs for the public keys and signatures instead of
readily-identifiable structs. We've been able to sign these
firmware images for some time, but verifying the result generally
required testing them on hardware.

This CL adds some futilty support for recognizing and verifying
those images too. It just tries various sig and hash algorithms,
until it finds a combination for which the image is
self-consistent (where the pubkey blob verifies the signature
blob).

BUG=none
BRANCH=none
TEST=make runtests

This change also adds additional tests for usbpd1 images. We
ensure that we correctly recognize and verify an MP-signed
firmware, plus test signing and verifying usbpd1 images using
multiple signature and hash algorithms.

Change-Id: I4fbe8b37a694992f635d5469ae1c2449b1610dfd
Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/302415
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/futility/file_type.inc b/futility/file_type.inc
index 4014c0b..5433d03 100644
--- a/futility/file_type.inc
+++ b/futility/file_type.inc
@@ -71,9 +71,8 @@
 	  NONE,
 	  NONE,
 	  NONE)
-
-/* Firmware for Samus' USB Type-C power adapters */
+/* Firmware for USB Type-C power adapters */
 FILE_TYPE(USBPD1,           "usbpd1",        "USB-PD charger image (v1.0)",
-	  NONE,
-	  NONE,
+	  R_(ft_recognize_usbpd1),
+	  S_(ft_show_usbpd1),
 	  S_(ft_sign_usbpd1))
diff --git a/futility/file_type_usbpd1.c b/futility/file_type_usbpd1.c
index 7230b0c..acf3de0 100644
--- a/futility/file_type_usbpd1.c
+++ b/futility/file_type_usbpd1.c
@@ -31,6 +31,55 @@
 #include "host_signature2.h"
 #include "util_misc.h"
 
+/* Return 1 if okay, 0 if not */
+static int parse_size_opts(uint32_t len,
+			   uint32_t *ro_size_ptr, uint32_t *rw_size_ptr,
+			   uint32_t *ro_offset_ptr, uint32_t * rw_offset_ptr)
+{
+	uint32_t ro_size, rw_size, ro_offset, rw_offset;
+
+	/* Assume the image has both RO and RW, evenly split. */
+	ro_offset = 0;
+	ro_size = rw_size = rw_offset = len / 2;
+
+	/* Unless told otherwise... */
+	if (sign_option.ro_size != 0xffffffff)
+		ro_size = sign_option.ro_size;
+	if (sign_option.ro_offset != 0xffffffff)
+		ro_offset = sign_option.ro_offset;
+
+	/* If RO is missing, the whole thing must be RW */
+	if (!ro_size) {
+		rw_size = len;
+		rw_offset = 0;
+	}
+
+	/* Unless that's overridden too */
+	if (sign_option.rw_size != 0xffffffff)
+		rw_size = sign_option.rw_size;
+	if (sign_option.rw_offset != 0xffffffff)
+		rw_offset = sign_option.rw_offset;
+
+	Debug("ro_size     0x%08x\n", ro_size);
+	Debug("ro_offset   0x%08x\n", ro_offset);
+	Debug("rw_size     0x%08x\n", rw_size);
+	Debug("rw_offset   0x%08x\n", rw_offset);
+
+	/* Now let's do some sanity checks. */
+	if (ro_size > len || ro_offset > len - ro_size ||
+	    rw_size > len || rw_offset > len - rw_size) {
+		printf("size/offset values are bogus\n");
+		return 0;
+	}
+
+	*ro_size_ptr = ro_size;
+	*rw_size_ptr = rw_size;
+	*ro_offset_ptr = ro_offset;
+	*rw_offset_ptr = rw_offset;
+
+	return 1;
+}
+
 int ft_sign_usbpd1(const char *name, uint8_t *buf, uint32_t len, void *data)
 {
 	struct vb2_private_key *key_ptr = 0;
@@ -51,36 +100,9 @@
 	Debug("%s(): name %s\n", __func__, name);
 	Debug("%s(): len  0x%08x (%d)\n", __func__, len, len);
 
-	/*
-	 * Check for size args. Note that we're NOT worrying about rollover,
-	 * overlapping regions, out of bounds, etc.
-	 */
-	ro_offset = 0;
-	ro_size = rw_size = rw_offset = len / 2;
-
-	/* Override some stuff? */
-	if (sign_option.ro_size != 0xffffffff)
-		ro_size = sign_option.ro_size;
-	if (sign_option.rw_size != 0xffffffff)
-		rw_size = sign_option.rw_size;
-
-	Debug("ro_size     0x%08x\n", ro_size);
-	Debug("ro_offset   0x%08x\n", ro_offset);
-
-	/* If RO is missing, the whole thing must be RW */
-	if (!ro_size) {
-		rw_size = len;
-		rw_offset = 0;
-	}
-
-	/* Unless that's overridden too */
-	if (sign_option.ro_offset != 0xffffffff)
-		ro_offset = sign_option.ro_offset;
-	if (sign_option.rw_offset != 0xffffffff)
-		rw_offset = sign_option.rw_offset;
-
-	Debug("rw_size     0x%08x\n", rw_size);
-	Debug("rw_offset   0x%08x\n", rw_offset);
+	/* Get image locations */
+	if (!parse_size_opts(len, &ro_size, &rw_size, &ro_offset, &rw_offset))
+		goto done;
 
 	/* Read the signing keypair file */
 	if (vb2_private_key_read_pem(&key_ptr, sign_option.pem_signpriv)) {
@@ -225,3 +247,243 @@
 
 	return retval;
 }
+
+
+/*
+ * Algorithms that we want to try, in order. We've only ever shipped with
+ * RSA2048 / SHA256, but the others should work in tests.
+ */
+static enum vb2_signature_algorithm sigs[] = {
+	VB2_SIG_RSA2048,
+	VB2_SIG_RSA1024,
+	VB2_SIG_RSA4096,
+	VB2_SIG_RSA8192,
+};
+enum vb2_hash_algorithm hashes[] = {
+	VB2_HASH_SHA256,
+	VB2_HASH_SHA1,
+	VB2_HASH_SHA512,
+};
+
+/*
+ * The size of the public key structure used by usbpd1 is
+ * 2 x RSANUMBYTES for n and rr fields
+ * plus 4 for n0inv, aligned on a multiple of 16
+ */
+static uint32_t usbpd1_packed_key_size(enum vb2_signature_algorithm sig_alg)
+{
+	switch (sig_alg) {
+	case VB2_SIG_RSA1024:
+		return 272;
+	case VB2_SIG_RSA2048:
+		return 528;
+	case VB2_SIG_RSA4096:
+		return 1040;
+	case VB2_SIG_RSA8192:
+		return 2064;
+	default:
+		return 0;
+	}
+}
+static void vb2_pubkey_from_usbpd1(struct vb2_public_key *key,
+				   enum vb2_signature_algorithm sig_alg,
+				   enum vb2_hash_algorithm hash_alg,
+				   const uint8_t *o_pubkey,
+				   uint32_t o_pubkey_size)
+{
+	key->arrsize = vb2_rsa_sig_size(sig_alg) / sizeof(uint32_t);
+	key->n0inv = *((uint32_t *)o_pubkey + 2 * key->arrsize);
+	key->n = (uint32_t *)o_pubkey;
+	key->rr = (uint32_t *)o_pubkey + key->arrsize;
+	key->sig_alg = sig_alg;
+	key->hash_alg = hash_alg;
+	key->desc = 0;
+	key->version = 0;
+	key->id = vb2_hash_id(hash_alg);
+}
+
+static int vb2_sig_from_usbpd1(struct vb2_signature **sig,
+			       enum vb2_signature_algorithm sig_alg,
+			       enum vb2_hash_algorithm hash_alg,
+			       const uint8_t *o_sig,
+			       uint32_t o_sig_size,
+			       uint32_t data_size)
+{
+	struct vb2_signature s = {
+		.c.magic = VB2_MAGIC_SIGNATURE,
+		.c.struct_version_major = VB2_SIGNATURE_VERSION_MAJOR,
+		.c.struct_version_minor = VB2_SIGNATURE_VERSION_MINOR,
+		.c.fixed_size = sizeof(s),
+		.sig_alg = sig_alg,
+		.hash_alg = hash_alg,
+		.data_size = data_size,
+		.sig_size = vb2_rsa_sig_size(sig_alg),
+		.sig_offset = sizeof(s),
+	};
+	uint32_t total_size = sizeof(s) + o_sig_size;
+	uint8_t *buf = calloc(1, total_size);
+	if (!buf)
+		return VB2_ERROR_UNKNOWN;
+
+	memcpy(buf, &s, sizeof(s));
+	memcpy(buf + sizeof(s), o_sig, o_sig_size);
+
+	*sig = (struct vb2_signature *)buf;
+	return VB2_SUCCESS;
+}
+
+static void show_usbpd1_stuff(const char *name,
+			      enum vb2_signature_algorithm sig_alg,
+			      enum vb2_hash_algorithm hash_alg,
+			      const uint8_t *o_pubkey, uint32_t o_pubkey_size)
+{
+	struct vb2_public_key key;
+	struct vb2_packed_key *pkey;
+	uint8_t *sha1sum;
+	int i;
+
+	vb2_pubkey_from_usbpd1(&key, sig_alg, hash_alg,
+			       o_pubkey, o_pubkey_size);
+
+	if (vb2_public_key_pack(&pkey, &key))
+		return;
+
+	sha1sum = DigestBuf((uint8_t *)pkey + pkey->key_offset,
+			pkey->key_size, SHA1_DIGEST_ALGORITHM);
+
+	printf("USB-PD v1 image:       %s\n", name);
+	printf("  Algorithm:           %s %s\n",
+	       vb2_lookup_by_num(vb2_text_vs_sig, sig_alg)->name,
+	       vb2_lookup_by_num(vb2_text_vs_hash, hash_alg)->name);
+	printf("  Key sha1sum:         ");
+	for (i = 0; i < SHA1_DIGEST_SIZE; i++)
+		printf("%02x", sha1sum[i]);
+	printf("\n");
+
+	free(sha1sum);
+	free(pkey);
+}
+
+
+/* Returns VB2_SUCCESS or random error code */
+static int try_our_own(enum vb2_signature_algorithm sig_alg,
+		       enum vb2_hash_algorithm hash_alg,
+		       const uint8_t *o_pubkey, uint32_t o_pubkey_size,
+		       const uint8_t *o_sig, uint32_t o_sig_size,
+		       const uint8_t *data, uint32_t data_size)
+{
+	struct vb2_public_key pubkey;
+	struct vb2_signature *sig;
+	uint8_t buf[VB2_WORKBUF_RECOMMENDED_SIZE]
+		__attribute__ ((aligned (VB2_WORKBUF_ALIGN)));
+	struct vb2_workbuf wb = {
+		.buf = buf,
+		.size = sizeof(buf),
+	};
+	int rv = VB2_ERROR_UNKNOWN;
+
+	vb2_pubkey_from_usbpd1(&pubkey, sig_alg, hash_alg,
+			       o_pubkey, o_pubkey_size);
+
+	if ((rv = vb2_sig_from_usbpd1(&sig, sig_alg, hash_alg,
+				      o_sig, o_sig_size, data_size)))
+	    return rv;
+
+	rv = vb2_verify_data(data, data_size, sig, &pubkey, &wb);
+
+	free(sig);
+
+	return rv;
+}
+
+/* Returns VB2_SUCCESS if the image validates itself */
+static int check_self_consistency(const uint8_t *buf,
+				  const char *name,
+				  uint32_t ro_size, uint32_t rw_size,
+				  uint32_t ro_offset, uint32_t rw_offset,
+				  enum vb2_signature_algorithm sig_alg,
+				  enum vb2_hash_algorithm hash_alg)
+{
+	/* Where are the important bits? */
+	uint32_t sig_size = vb2_rsa_sig_size(sig_alg);
+	uint32_t sig_offset = rw_offset + rw_size - sig_size;
+	uint32_t pubkey_size = usbpd1_packed_key_size(sig_alg);
+	uint32_t pubkey_offset = ro_offset + ro_size - pubkey_size;
+	int rv;
+
+	/* Skip stuff that obviously doesn't work */
+	if (sig_size > rw_size || pubkey_size > ro_size)
+		return VB2_ERROR_UNKNOWN;
+
+	rv = try_our_own(sig_alg, hash_alg,		   /* algs */
+			 buf + pubkey_offset, pubkey_size, /* pubkey blob */
+			 buf + sig_offset, sig_size,	   /* sig blob */
+			 buf + rw_offset, rw_size - sig_size); /* RW image */
+
+	if (rv == VB2_SUCCESS && name)
+		show_usbpd1_stuff(name, sig_alg, hash_alg,
+				  buf + pubkey_offset, pubkey_size);
+
+	return rv;
+}
+
+
+int ft_show_usbpd1(const char *name, uint8_t *buf, uint32_t len, void *data)
+{
+	uint32_t ro_size, rw_size, ro_offset, rw_offset;
+	int s, h;
+
+	Debug("%s(): name %s\n", __func__, name);
+	Debug("%s(): len  0x%08x (%d)\n", __func__, len, len);
+
+	/* Get image locations */
+	if (!parse_size_opts(len, &ro_size, &rw_size, &ro_offset, &rw_offset))
+		return 1;
+
+	/* TODO: If we don't have a RO image, ask for a public key
+	 * TODO: If we're given an external public key, use it (and its alg) */
+	if (!ro_size) {
+		printf("Can't find the public key\n");
+		return 1;
+	}
+
+	/* TODO: Only loop through the numbers we haven't been given */
+	for (s = 0; s < ARRAY_SIZE(sigs); s++)
+		for (h = 0; h < ARRAY_SIZE(hashes); h++)
+			if (!check_self_consistency(buf, name,
+						    ro_size, rw_size,
+						    ro_offset, rw_offset,
+						    sigs[s], hashes[h]))
+				return 0;
+
+	printf("This doesn't appear to be a complete usbpd1 image\n");
+	return 1;
+}
+
+enum futil_file_type ft_recognize_usbpd1(uint8_t *buf, uint32_t len)
+{
+	uint32_t ro_size, rw_size, ro_offset, rw_offset;
+	int s, h;
+
+	Debug("%s(): len  0x%08x (%d)\n", __func__, len, len);
+
+	/*
+	 * Since we don't use any headers to identify or locate the pubkey and
+	 * signature, in order to identify blob as the right type we have to
+	 * just assume that the RO & RW are 1) both present, and 2) evenly
+	 * split. Then we just try to use what we think might be the pubkey to
+	 * validate what we think might be the signature.
+	 */
+	ro_offset = 0;
+	ro_size = rw_size = rw_offset = len / 2;
+
+	for (s = 0; s < ARRAY_SIZE(sigs); s++)
+		for (h = 0; h < ARRAY_SIZE(hashes); h++)
+			if (!check_self_consistency(buf, 0,
+						    ro_size, rw_size,
+						    ro_offset, rw_offset,
+						    sigs[s], hashes[h]))
+				return FILE_TYPE_USBPD1;
+
+	return FILE_TYPE_UNKNOWN;
+}
diff --git a/tests/futility/data/zinger_mp_image.bin b/tests/futility/data/zinger_mp_image.bin
new file mode 100644
index 0000000..68152c0
--- /dev/null
+++ b/tests/futility/data/zinger_mp_image.bin
Binary files differ
diff --git a/tests/futility/run_test_scripts.sh b/tests/futility/run_test_scripts.sh
index 8e6281a..a0d9e47 100755
--- a/tests/futility/run_test_scripts.sh
+++ b/tests/futility/run_test_scripts.sh
@@ -47,6 +47,7 @@
 ${SCRIPTDIR}/test_main.sh
 ${SCRIPTDIR}/test_show_kernel.sh
 ${SCRIPTDIR}/test_show_vs_verify.sh
+${SCRIPTDIR}/test_show_usbpd1.sh
 ${SCRIPTDIR}/test_sign_firmware.sh
 ${SCRIPTDIR}/test_sign_fw_main.sh
 ${SCRIPTDIR}/test_sign_kernel.sh
diff --git a/tests/futility/test_file_types.c b/tests/futility/test_file_types.c
index 3fb21cc..9f90a0f 100644
--- a/tests/futility/test_file_types.c
+++ b/tests/futility/test_file_types.c
@@ -29,15 +29,14 @@
 	{FILE_TYPE_BIOS_IMAGE,      "tests/futility/data/bios_zgb_mp.bin"},
 	{FILE_TYPE_OLD_BIOS_IMAGE,  "tests/futility/data/bios_mario_mp.bin"},
 	{FILE_TYPE_KERN_PREAMBLE,   "tests/futility/data/kern_preamble.bin"},
-	/* We don't have a way to identify these (yet?) */
-	{FILE_TYPE_RAW_FIRMWARE,    },
-	{FILE_TYPE_RAW_KERNEL,	    },
-	{FILE_TYPE_CHROMIUMOS_DISK, },
+	{FILE_TYPE_RAW_FIRMWARE,    },		/* need a test for this */
+	{FILE_TYPE_RAW_KERNEL,	    },		/* need a test for this */
+	{FILE_TYPE_CHROMIUMOS_DISK, },		/* need a test for this */
 	{FILE_TYPE_PRIVKEY,	    "tests/devkeys/root_key.vbprivk"},
 	{FILE_TYPE_VB2_PUBKEY,      "tests/futility/data/sample.vbpubk2"},
 	{FILE_TYPE_VB2_PRIVKEY,     "tests/futility/data/sample.vbprik2"},
 	{FILE_TYPE_PEM,             "tests/testkeys/key_rsa2048.pem"},
-	{FILE_TYPE_USBPD1,          },
+	{FILE_TYPE_USBPD1,          "tests/futility/data/zinger_mp_image.bin"},
 };
 BUILD_ASSERT(ARRAY_SIZE(test_case) == NUM_FILE_TYPES);
 
diff --git a/tests/futility/test_show_usbpd1.sh b/tests/futility/test_show_usbpd1.sh
new file mode 100755
index 0000000..5fa5b93
--- /dev/null
+++ b/tests/futility/test_show_usbpd1.sh
@@ -0,0 +1,46 @@
+#!/bin/bash -eux
+# 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.
+
+me=${0##*/}
+TMP="$me.tmp"
+
+# Work in scratch directory
+cd "$OUTDIR"
+
+DATADIR="${SCRIPTDIR}/data"
+TESTS="dingdong hoho minimuffin zinger"
+TESTKEYS=${SRCDIR}/tests/testkeys
+
+SIGS="1024 2048 4096 8192"
+HASHES="SHA1 SHA256 SHA512"
+
+set -o pipefail
+
+for s in $SIGS; do
+
+    echo -n "$s " 1>&3
+
+    for test in $TESTS; do
+
+        infile=${DATADIR}/${test}.unsigned
+
+        for h in $HASHES; do
+
+            pemfile=${TESTKEYS}/key_rsa${s}.pem
+            outfile=${TMP}.${test}_${s}_${h}.new
+
+            # sign it
+            ${FUTILITY} sign --type usbpd1 --pem ${pemfile} ${infile} ${outfile}
+
+            # make sure it identifies correctly
+            ${FUTILITY} verify ${outfile}
+
+        done
+    done
+done
+
+# cleanup
+rm -rf ${TMP}*
+exit 0