futility: Allow signing raw firmware blob and keyblocks

BUG=none
BRANCH=ToT
TEST=make runtests

Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Change-Id: Ib1cf55301fd4c54e3280ef01b7d67a780e7e56fe
Reviewed-on: https://chromium-review.googlesource.com/219731
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/futility/cmd_sign.c b/futility/cmd_sign.c
index f4c682e..c28aee9 100644
--- a/futility/cmd_sign.c
+++ b/futility/cmd_sign.c
@@ -51,11 +51,16 @@
 	uint8_t *config_data;
 	uint64_t config_size;
 	enum arch_t arch;
+	int fv_specified;
 	uint32_t kloadaddr;
 	uint32_t padding;
 	int vblockonly;
 	char *outfile;
 	int create_new_outfile;
+	char *pem_signpriv;
+	int pem_algo_specified;
+	uint32_t pem_algo;
+	char *pem_external;
 } option = {
 	.version = 1,
 	.arch = ARCH_UNSPECIFIED,
@@ -77,8 +82,39 @@
 /* This wraps/signs a public key, producing a keyblock. */
 int futil_cb_sign_pubkey(struct futil_traverse_state_s *state)
 {
-	fprintf(stderr, "Don't know how to sign %s yet\n", state->name);
-	return 1;
+	VbPublicKey *data_key = (VbPublicKey *)state->my_area->buf;
+	VbKeyBlockHeader *vblock;
+
+	if (option.pem_signpriv) {
+		if (option.pem_external) {
+			/* External signing uses the PEM file directly. */
+			vblock = KeyBlockCreate_external(
+				data_key,
+				option.pem_signpriv,
+				option.pem_algo, option.flags,
+				option.pem_external);
+		} else {
+			option.signprivate = PrivateKeyReadPem(
+				option.pem_signpriv, option.pem_algo);
+			if (!option.signprivate) {
+				fprintf(stderr,
+					"Unable to read PEM signing key: %s\n",
+					strerror(errno));
+				return 1;
+			}
+			vblock = KeyBlockCreate(data_key, option.signprivate,
+						option.flags);
+		}
+	} else {
+		/* Not PEM. Should already have a signing key. */
+		vblock = KeyBlockCreate(data_key, option.signprivate,
+					option.flags);
+	}
+
+	/* Write it out */
+	return WriteSomeParts(option.outfile,
+			      vblock, vblock->key_block_size,
+			      NULL, 0);
 }
 
 /*
@@ -287,8 +323,36 @@
 
 int futil_cb_sign_raw_firmware(struct futil_traverse_state_s *state)
 {
-	fprintf(stderr, "Don't know how to sign %s yet\n", state->name);
-	return 1;
+	VbSignature *body_sig;
+	VbFirmwarePreambleHeader *preamble;
+	int rv;
+
+	body_sig = CalculateSignature(state->my_area->buf, state->my_area->len,
+				      option.signprivate);
+	if (!body_sig) {
+		fprintf(stderr, "Error calculating body signature\n");
+		return 1;
+	}
+
+	preamble = CreateFirmwarePreamble(option.version,
+					  option.kernel_subkey,
+					  body_sig,
+					  option.signprivate,
+					  option.flags);
+	if (!preamble) {
+		fprintf(stderr, "Error creating firmware preamble.\n");
+		free(body_sig);
+		return 1;
+	}
+
+	rv = WriteSomeParts(option.outfile,
+			    option.keyblock, option.keyblock->key_block_size,
+			    preamble, preamble->preamble_size);
+
+	free(preamble);
+	free(body_sig);
+
+	return rv;
 }
 
 
@@ -446,10 +510,53 @@
 	"\n"
 	"Where INFILE is a\n"
 	"\n"
+	"  public key (.vbpubk); OUTFILE is a keyblock\n"
+	"  raw firmware blob (FW_MAIN_A/B); OUTFILE is a VBLOCK_A/B\n"
 	"  complete firmware image (bios.bin)\n"
 	"  raw linux kernel; OUTFILE is a kernel partition image\n"
 	"  kernel partition image (/dev/sda2, /dev/mmcblk0p2)\n";
 
+static const char usage_pubkey[] = "\n"
+	"-----------------------------------------------------------------\n"
+	"To sign a public key / create a new keyblock:\n"
+	"\n"
+	"Required PARAMS:\n"
+	"  [--datapubkey]   INFILE          The public key to wrap\n"
+	"  [--outfile]      OUTFILE         The resulting keyblock\n"
+	"\n"
+	"Optional PARAMS:\n"
+	"  A private signing key, specified as either\n"
+	"    -s|--signprivate FILE.vbprivk  Signing key in .vbprivk format\n"
+	"  Or\n"
+	"    --pem_signpriv   FILE.pem      Signing key in PEM format...\n"
+	"    --pem_algo       NUM           AND the algorithm to use (0 - %d)\n"
+	"\n"
+	"  If a signing key is not given, the keyblock will not be signed (duh)."
+	"\n\n"
+	"And these, too:\n\n"
+	"  -f|--flags       NUM             Flags specifying use conditions\n"
+	"  --pem_external   PROGRAM"
+	"         External program to compute the signature\n"
+	"                                     (requires a PEM signing key)\n";
+
+static const char usage_fw_main[] = "\n"
+	"-----------------------------------------------------------------\n"
+	"To sign a raw firmware blob (FW_MAIN_A/B):\n"
+	"\n"
+	"Required PARAMS:\n"
+	"  -s|--signprivate FILE.vbprivk    The private firmware data key\n"
+	"  -b|--keyblock    FILE.keyblock   The keyblock containing the\n"
+	"                                     public firmware data key\n"
+	"  -k|--kernelkey   FILE.vbpubk     The public kernel subkey\n"
+	"  -v|--version     NUM             The firmware version number\n"
+	"  [--fv]           INFILE"
+	"          The raw firmware blob (FW_MAIN_A/B)\n"
+	"  [--outfile]      OUTFILE         Output VBLOCK_A/B\n"
+	"\n"
+	"Optional PARAMS:\n"
+	"  -f|--flags       NUM             The preamble flags value"
+	" (default is 0)\n";
+
 static const char usage_bios[] = "\n"
 	"-----------------------------------------------------------------\n"
 	"To sign a complete firmware image (bios.bin):\n"
@@ -528,6 +635,8 @@
 static void print_help(const char *prog)
 {
 	printf(usage, prog);
+	printf(usage_pubkey, kNumAlgorithms - 1);
+	puts(usage_fw_main);
 	printf(usage_bios, option.version);
 	printf(usage_new_kpart, option.kloadaddr, option.padding);
 	printf(usage_old_kpart, option.padding);
@@ -542,6 +651,9 @@
 	OPT_ARCH,
 	OPT_KLOADADDR,
 	OPT_PADDING,
+	OPT_PEM_SIGNPRIV,
+	OPT_PEM_ALGO,
+	OPT_PEM_EXTERNAL,
 };
 
 static const struct option long_opts[] = {
@@ -565,6 +677,9 @@
 	{"arch",         1, NULL, OPT_ARCH},
 	{"kloadaddr",    1, NULL, OPT_KLOADADDR},
 	{"pad",          1, NULL, OPT_PADDING},
+	{"pem_signpriv", 1, NULL, OPT_PEM_SIGNPRIV},
+	{"pem_algo",     1, NULL, OPT_PEM_ALGO},
+	{"pem_external", 1, NULL, OPT_PEM_EXTERNAL},
 	{"vblockonly",   0, &option.vblockonly, 1},
 	{"debug",        0, &debugging_enabled, 1},
 	{NULL,           0, NULL, 0},
@@ -648,6 +763,9 @@
 		case 'l':
 			option.loemid = optarg;
 			break;
+		case OPT_FV:
+			option.fv_specified = 1;
+			/* fallthrough */
 		case OPT_INFILE:		/* aka "--vmlinuz" */
 			inout_file_count++;
 			infile = optarg;
@@ -711,6 +829,23 @@
 				errorcnt++;
 			}
 			break;
+		case OPT_PEM_SIGNPRIV:
+			option.pem_signpriv = optarg;
+			break;
+		case OPT_PEM_ALGO:
+			option.pem_algo_specified = 1;
+			option.pem_algo = strtoul(optarg, &e, 0);
+			if (!*optarg || (e && *e) ||
+			    (option.pem_algo >= kNumAlgorithms)) {
+				fprintf(stderr,
+					"Invalid --pem_algo \"%s\"\n", optarg);
+				errorcnt++;
+			}
+			break;
+		case OPT_PEM_EXTERNAL:
+			option.pem_external = optarg;
+			break;
+
 		case '?':
 			if (optopt)
 				fprintf(stderr, "Unrecognized option: -%c\n",
@@ -752,6 +887,8 @@
 		if (option.bootloader_data || option.config_data
 		    || option.arch != ARCH_UNSPECIFIED)
 			type = FILE_TYPE_RAW_KERNEL;
+		else if (option.kernel_subkey || option.fv_specified)
+			type = FILE_TYPE_RAW_FIRMWARE;
 	}
 
 	/* Check the arguments for the type of thing we want to sign */
@@ -761,6 +898,28 @@
 			"Unable to determine the type of the input file\n");
 		errorcnt++;
 		goto done;
+	case FILE_TYPE_PUBKEY:
+		option.create_new_outfile = 1;
+		if (option.signprivate && option.pem_signpriv) {
+			fprintf(stderr,
+				"Only one of --signprivate and --pem_signpriv"
+				" can be specified\n");
+			errorcnt++;
+		}
+		if ((option.signprivate && option.pem_algo_specified) ||
+		    (option.pem_signpriv && !option.pem_algo_specified)) {
+			fprintf(stderr, "--pem_algo must be used with"
+				" --pem_signpriv\n");
+			errorcnt++;
+		}
+		if (option.pem_external && !option.pem_signpriv) {
+			fprintf(stderr, "--pem_external must be used with"
+				" --pem_signpriv\n");
+			errorcnt++;
+		}
+		/* We'll wait to read the PEM file, since the external signer
+		 * may want to read it instead. */
+		break;
 	case FILE_TYPE_KEYBLOCK:
 		fprintf(stderr, "Resigning a keyblock is kind of pointless.\n");
 		fprintf(stderr, "Just create a new one.\n");
@@ -786,6 +945,13 @@
 		if (option.vblockonly)
 			option.create_new_outfile = 1;
 		break;
+	case FILE_TYPE_RAW_FIRMWARE:
+		option.create_new_outfile = 1;
+		errorcnt += no_opt_if(!option.signprivate, "signprivate");
+		errorcnt += no_opt_if(!option.keyblock, "keyblock");
+		errorcnt += no_opt_if(!option.kernel_subkey, "kernelkey");
+		errorcnt += no_opt_if(!option.version_specified, "version");
+		break;
 	case FILE_TYPE_RAW_KERNEL:
 		option.create_new_outfile = 1;
 		errorcnt += no_opt_if(!option.signprivate, "signprivate");
diff --git a/tests/futility/run_test_scripts.sh b/tests/futility/run_test_scripts.sh
index bb3a600..f450846 100755
--- a/tests/futility/run_test_scripts.sh
+++ b/tests/futility/run_test_scripts.sh
@@ -45,6 +45,8 @@
 ${SCRIPTDIR}/test_load_fmap.sh
 ${SCRIPTDIR}/test_gbb_utility.sh
 ${SCRIPTDIR}/test_show_kernel.sh
+${SCRIPTDIR}/test_sign_keyblocks.sh
+${SCRIPTDIR}/test_sign_fw_main.sh
 ${SCRIPTDIR}/test_sign_firmware.sh
 ${SCRIPTDIR}/test_sign_kernel.sh
 "
diff --git a/tests/futility/test_sign_fw_main.sh b/tests/futility/test_sign_fw_main.sh
new file mode 100755
index 0000000..eec68a6
--- /dev/null
+++ b/tests/futility/test_sign_fw_main.sh
@@ -0,0 +1,46 @@
+#!/bin/bash -eux
+# Copyright 2014 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"
+
+KEYDIR=${SRCDIR}/tests/devkeys
+
+# create a firmware blob
+dd bs=1024 count=16 if=/dev/urandom of=${TMP}.fw_main
+
+# try the old way
+${FUTILITY} vbutil_firmware --vblock ${TMP}.vblock.old \
+  --keyblock ${KEYDIR}/firmware.keyblock \
+  --signprivate ${KEYDIR}/firmware_data_key.vbprivk \
+  --version 12 \
+  --fv ${TMP}.fw_main \
+  --kernelkey ${KEYDIR}/kernel_subkey.vbpubk \
+  --flags 42
+
+# verify
+${FUTILITY} vbutil_firmware --verify ${TMP}.vblock.old \
+  --signpubkey ${KEYDIR}/root_key.vbpubk \
+  --fv ${TMP}.fw_main
+
+# and the new way
+${FUTILITY} sign --debug \
+  --signprivate ${KEYDIR}/firmware_data_key.vbprivk \
+  --keyblock ${KEYDIR}/firmware.keyblock \
+  --kernelkey ${KEYDIR}/kernel_subkey.vbpubk \
+  --version 12 \
+  --fv ${TMP}.fw_main \
+  --flags 42 \
+  ${TMP}.vblock.new
+
+# They should match
+cmp ${TMP}.vblock.old ${TMP}.vblock.new
+
+# cleanup
+rm -rf ${TMP}*
+exit 0
diff --git a/tests/futility/test_sign_keyblocks.sh b/tests/futility/test_sign_keyblocks.sh
new file mode 100755
index 0000000..1cccd34
--- /dev/null
+++ b/tests/futility/test_sign_keyblocks.sh
@@ -0,0 +1,110 @@
+#!/bin/bash -eux
+# Copyright 2014 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"
+
+# some stuff we'll need
+DEVKEYS=${SRCDIR}/tests/devkeys
+TESTKEYS=${SRCDIR}/tests/testkeys
+SIGNER=${SRCDIR}/tests/external_rsa_signer.sh
+
+
+# Create a copy of an existing keyblock, using the old way
+${FUTILITY} vbutil_keyblock --pack ${TMP}.keyblock0 \
+  --datapubkey ${DEVKEYS}/firmware_data_key.vbpubk \
+  --flags 7 \
+  --signprivate ${DEVKEYS}/root_key.vbprivk
+
+# Check it.
+${FUTILITY} vbutil_keyblock --unpack ${TMP}.keyblock0 \
+  --signpubkey ${DEVKEYS}/root_key.vbpubk
+
+# It should be the same as the dev-key firmware keyblock
+cmp ${DEVKEYS}/firmware.keyblock ${TMP}.keyblock0
+
+
+# Now create it the new way
+${FUTILITY} sign --debug \
+  --datapubkey ${DEVKEYS}/firmware_data_key.vbpubk \
+  --flags 7 \
+  --signprivate ${DEVKEYS}/root_key.vbprivk \
+  --outfile ${TMP}.keyblock1
+
+# It should be the same too.
+cmp ${DEVKEYS}/firmware.keyblock ${TMP}.keyblock1
+
+
+# Create a keyblock without signing it.
+
+# old way
+${FUTILITY} vbutil_keyblock --pack ${TMP}.keyblock0 \
+  --datapubkey ${DEVKEYS}/firmware_data_key.vbpubk \
+  --flags 14
+
+# new way
+${FUTILITY} sign --debug \
+  --flags 14 \
+  ${DEVKEYS}/firmware_data_key.vbpubk \
+  ${TMP}.keyblock1
+
+cmp ${TMP}.keyblock0 ${TMP}.keyblock1
+
+
+# Create one using PEM args
+
+# old way
+${FUTILITY} vbutil_keyblock --pack ${TMP}.keyblock2 \
+  --datapubkey ${DEVKEYS}/firmware_data_key.vbpubk \
+  --signprivate_pem ${TESTKEYS}/key_rsa4096.pem \
+  --pem_algorithm 8 \
+  --flags 9
+
+# verify it
+${FUTILITY} vbutil_keyblock --unpack ${TMP}.keyblock2 \
+  --signpubkey ${TESTKEYS}/key_rsa4096.sha512.vbpubk
+
+# new way
+${FUTILITY} sign --debug \
+  --pem_signpriv ${TESTKEYS}/key_rsa4096.pem \
+  --pem_algo 8 \
+  --flags 9 \
+  ${DEVKEYS}/firmware_data_key.vbpubk \
+  ${TMP}.keyblock3
+
+cmp ${TMP}.keyblock2 ${TMP}.keyblock3
+
+# Try it with an external signer
+
+# old way
+${FUTILITY} vbutil_keyblock --pack ${TMP}.keyblock4 \
+  --datapubkey ${DEVKEYS}/firmware_data_key.vbpubk \
+  --signprivate_pem ${TESTKEYS}/key_rsa4096.pem \
+  --pem_algorithm 8 \
+  --flags 19 \
+  --externalsigner ${SIGNER}
+
+# verify it
+${FUTILITY} vbutil_keyblock --unpack ${TMP}.keyblock4 \
+  --signpubkey ${TESTKEYS}/key_rsa4096.sha512.vbpubk
+
+# new way
+${FUTILITY} sign --debug \
+  --pem_signpriv ${TESTKEYS}/key_rsa4096.pem \
+  --pem_algo 8 \
+  --pem_external ${SIGNER} \
+  --flags 19 \
+  ${DEVKEYS}/firmware_data_key.vbpubk \
+  ${TMP}.keyblock5
+
+cmp ${TMP}.keyblock4 ${TMP}.keyblock5
+
+
+# cleanup
+rm -rf ${TMP}*
+exit 0