futility: show vs verify

This adds a --strict mode to the show command, which requires
that all signatures be valid in order to exit cleanly. It also
creates a "verify" command, which is really just an alias for
"show --strict".

BUG=none
BRANCH=ToT
TEST=make runtests

Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Change-Id: I1fed7db7fe7128191bcab0c615706ef4fe2709f5
Reviewed-on: https://chromium-review.googlesource.com/219732
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/futility/cmd_show.c b/futility/cmd_show.c
index b8f0601..2873f60 100644
--- a/futility/cmd_show.c
+++ b/futility/cmd_show.c
@@ -175,6 +175,8 @@
 	bmp = (BmpBlockHeader *)(buf + gbb->bmpfv_offset);
 	if (0 != memcmp(bmp, BMPBLOCK_SIGNATURE, BMPBLOCK_SIGNATURE_SIZE)) {
 		printf("  BmpBlock:              <invalid>\n");
+		/* We don't support older BmpBlock formats, so we can't
+		 * be strict about this. */
 	} else {
 		printf("  BmpBlock:\n");
 		printf("    Version:             %d.%d\n",
@@ -198,6 +200,7 @@
 	VbKeyBlockHeader *block = (VbKeyBlockHeader *)state->my_area->buf;
 	VbPublicKey *sign_key = option.k;
 	int good_sig = 0;
+	int retval = 0;
 
 	/* Check the hash only first */
 	if (0 != KeyBlockVerify(block, state->my_area->len, NULL, 1)) {
@@ -210,11 +213,14 @@
 	    KeyBlockVerify(block, state->my_area->len, sign_key, 0))
 		good_sig = 1;
 
+	if (option.strict && (!sign_key || !good_sig))
+		retval = 1;
+
 	show_keyblock(block, state->in_filename, !!sign_key, good_sig);
 
 	state->my_area->_flags |= AREA_IS_VALID;
 
-	return 0;
+	return retval;
 }
 
 /*
@@ -244,6 +250,7 @@
 	uint64_t fv_size = option.fv_size;
 	struct cb_area_s *fw_body_area = 0;
 	int good_sig = 0;
+	int retval = 0;
 
 	/* Check the hash... */
 	if (VBOOT_SUCCESS != KeyBlockVerify(key_block, len, NULL, 1)) {
@@ -283,6 +290,9 @@
 		      ? state->in_filename : state->name,
 		      !!sign_key, good_sig);
 
+	if (option.strict && (!sign_key || !good_sig))
+		retval = 1;
+
 	RSAPublicKey *rsa = PublicKeyToRSA(&key_block->data_key);
 	if (!rsa) {
 		fprintf(stderr, "Error parsing data key in %s\n", state->name);
@@ -311,6 +321,8 @@
 	       kernel_subkey->algorithm,
 	       (kernel_subkey->algorithm < kNumAlgorithms ?
 		algo_strings[kernel_subkey->algorithm] : "(invalid)"));
+	if (kernel_subkey->algorithm >= kNumAlgorithms)
+		retval = 1;
 	printf("  Kernel key version:    %" PRIu64 "\n",
 	       kernel_subkey->key_version);
 	printf("  Kernel key sha1sum:    ");
@@ -335,6 +347,8 @@
 
 	if (!fv_data) {
 		printf("No firmware body available to verify.\n");
+		if (option.strict)
+			return 1;
 		return 0;
 	}
 
@@ -354,9 +368,11 @@
 		state->my_area->_flags |= AREA_IS_VALID;
 	} else {
 		printf("Seems legit, but the signature is unverified.\n");
+		if (option.strict)
+			retval = 1;
 	}
 
-	return 0;
+	return retval;
 }
 
 int futil_cb_show_kernel_preamble(struct futil_traverse_state_s *state)
@@ -368,6 +384,7 @@
 	uint8_t *kernel_blob = 0;
 	uint64_t kernel_size;
 	int good_sig = 0;
+	int retval = 0;
 
 	/* Check the hash... */
 	if (VBOOT_SUCCESS != KeyBlockVerify(key_block, len, NULL, 1)) {
@@ -383,6 +400,9 @@
 	printf("Kernel partition:        %s\n", state->in_filename);
 	show_keyblock(key_block, NULL, !!sign_key, good_sig);
 
+	if (option.strict && (!sign_key || !good_sig))
+		retval = 1;
+
 	RSAPublicKey *rsa = PublicKeyToRSA(&key_block->data_key);
 	if (!rsa) {
 		fprintf(stderr, "Error parsing data key in %s\n", state->name);
@@ -443,7 +463,7 @@
 
 	printf("Config:\n%s\n", kernel_blob + KernelCmdLineOffset(preamble));
 
-	return 0;
+	return retval;
 }
 
 int futil_cb_show_begin(struct futil_traverse_state_s *state)
@@ -474,6 +494,7 @@
 	"\n"
 	"Where FILE could be a\n"
 	"\n"
+	"%s"
 	"  keyblock (.keyblock)\n"
 	"  firmware preamble signature (VBLOCK_A/B)\n"
 	"  firmware image (bios.bin)\n"
@@ -484,11 +505,19 @@
 	"            Use this public key for validation\n"
 	"  -f|--fv          FILE            Verify this payload (FW_MAIN_A/B)\n"
 	"  --pad            NUM             Kernel vblock padding size\n"
+	"%s"
 	"\n";
 
 static void print_help(const char *prog)
 {
-	printf(usage, prog);
+	if (strcmp(prog, "verify"))
+		printf(usage, prog,
+		       "  public key (.vbpubk)\n",
+		       "  --strict                         "
+		       "Fail unless all signatures are valid\n");
+	else
+		printf(usage, prog, "",
+		       "\nIt will fail unless all signatures are valid\n");
 }
 
 static const struct option long_opts[] = {
@@ -496,6 +525,7 @@
 	{"publickey",   1, 0, 'k'},
 	{"fv",          1, 0, 'f'},
 	{"pad",         1, NULL, OPT_PADDING},
+	{"verify",      0, &option.strict, 1},
 	{"debug",       0, &debugging_enabled, 1},
 	{NULL, 0, NULL, 0},
 };
@@ -612,3 +642,13 @@
 DECLARE_FUTIL_COMMAND(show, do_show,
 		      "Display the content of various binary components",
 		      print_help);
+
+static int do_verify(int argc, char *argv[])
+{
+	option.strict = 1;
+	return do_show(argc, argv);
+}
+
+DECLARE_FUTIL_COMMAND(verify, do_verify,
+		      "Verify the signatures of various binary components",
+		      print_help);
diff --git a/tests/futility/data/rec_kernel_part.bin b/tests/futility/data/rec_kernel_part.bin
new file mode 100644
index 0000000..54694bb
--- /dev/null
+++ b/tests/futility/data/rec_kernel_part.bin
Binary files differ
diff --git a/tests/futility/run_test_scripts.sh b/tests/futility/run_test_scripts.sh
index f450846..f6bbe88 100755
--- a/tests/futility/run_test_scripts.sh
+++ b/tests/futility/run_test_scripts.sh
@@ -45,6 +45,7 @@
 ${SCRIPTDIR}/test_load_fmap.sh
 ${SCRIPTDIR}/test_gbb_utility.sh
 ${SCRIPTDIR}/test_show_kernel.sh
+${SCRIPTDIR}/test_show_vs_verify.sh
 ${SCRIPTDIR}/test_sign_keyblocks.sh
 ${SCRIPTDIR}/test_sign_fw_main.sh
 ${SCRIPTDIR}/test_sign_firmware.sh
diff --git a/tests/futility/test_show_vs_verify.sh b/tests/futility/test_show_vs_verify.sh
new file mode 100755
index 0000000..6cccd0b
--- /dev/null
+++ b/tests/futility/test_show_vs_verify.sh
@@ -0,0 +1,75 @@
+#!/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
+
+# The show command exits with 0 if the data is consistent.
+# The verify command exits with 0 only if all the data is verified.
+
+####  keyblock
+
+${FUTILITY} show ${DEVKEYS}/firmware.keyblock
+
+if ${FUTILITY} verify ${DEVKEYS}/firmware.keyblock ; then false; fi
+
+${FUTILITY} verify ${DEVKEYS}/firmware.keyblock \
+  --publickey ${DEVKEYS}/root_key.vbpubk
+
+
+#### firmware vblock
+
+# Get some bits to look at
+${FUTILITY} dump_fmap -x ${SCRIPTDIR}/data/bios_peppy_mp.bin \
+  GBB:${TMP}.gbb VBLOCK_A:${TMP}.vblock_a FW_MAIN_A:${TMP}.fw_main_a
+${FUTILITY} gbb_utility -g -k ${TMP}.rootkey ${TMP}.gbb
+
+
+${FUTILITY} show ${TMP}.vblock_a
+
+${FUTILITY} show ${TMP}.vblock_a --publickey ${TMP}.rootkey
+
+${FUTILITY} show ${TMP}.vblock_a \
+  --publickey ${TMP}.rootkey \
+  --fv ${TMP}.fw_main_a
+
+if ${FUTILITY} verify ${TMP}.vblock_a ; then false ; fi
+
+if ${FUTILITY} verify ${TMP}.vblock_a \
+  --publickey ${TMP}.rootkey ; then false ; fi
+
+${FUTILITY} verify ${TMP}.vblock_a \
+  --publickey ${TMP}.rootkey \
+  --fv ${TMP}.fw_main_a
+
+
+#### kernel partition
+
+${FUTILITY} show ${SCRIPTDIR}/data/rec_kernel_part.bin
+
+${FUTILITY} show ${SCRIPTDIR}/data/rec_kernel_part.bin \
+  --publickey ${DEVKEYS}/kernel_subkey.vbpubk
+
+${FUTILITY} show ${SCRIPTDIR}/data/rec_kernel_part.bin \
+  --publickey ${DEVKEYS}/recovery_key.vbpubk
+
+if ${FUTILITY} verify ${SCRIPTDIR}/data/rec_kernel_part.bin ; then false ; fi
+
+if ${FUTILITY} verify ${SCRIPTDIR}/data/rec_kernel_part.bin \
+  --publickey ${DEVKEYS}/kernel_subkey.vbpubk ; then false ; fi
+
+${FUTILITY} verify ${SCRIPTDIR}/data/rec_kernel_part.bin \
+  --publickey ${DEVKEYS}/recovery_key.vbpubk
+
+
+# cleanup
+rm -rf ${TMP}*
+exit 0
diff --git a/tests/futility/test_sign_firmware.sh b/tests/futility/test_sign_firmware.sh
index 649382d..7ebedcc 100755
--- a/tests/futility/test_sign_firmware.sh
+++ b/tests/futility/test_sign_firmware.sh
@@ -30,6 +30,8 @@
 ${FUTILITY} load_fmap ${ONEMORE} VBLOCK_A:/dev/urandom VBLOCK_B:/dev/zero
 INFILES="${INFILES} ${ONEMORE}"
 
+set -o pipefail
+
 count=0
 for infile in $INFILES; do
 
@@ -84,21 +86,32 @@
     ${infile} ${outfile}
 
   # check the firmware version and preamble flags
-  # TODO: verify
-  m=$(${FUTILITY} show ${outfile} | \
-    egrep 'Firmware version: +14$|Preamble flags: +8$' | wc -l)
+  m=$(${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk ${outfile} \
+    | egrep 'Firmware version: +14$|Preamble flags: +8$' | wc -l)
   [ "$m" = "4" ]
 
   # check the sha1sums
-  # TODO: verify
-  ${FUTILITY} show ${outfile} | grep sha1sum \
+  ${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk ${outfile} \
+    | grep sha1sum \
     | sed -e 's/.*: \+//' > ${TMP}.${base}.sha.new
   cmp ${SCRIPTDIR}/data_${base}_expect.txt ${TMP}.${base}.sha.new
 
-  # and the LOEM stuff
-  # TODO: verify
-  ${FUTILITY} show ${loemdir}/*.${loemid} | grep sha1sum \
-    | sed -e 's/.*: \+//' > ${loemdir}/loem.sha.new
+   # and the LOEM stuff
+   ${FUTILITY} dump_fmap -x ${outfile} \
+     FW_MAIN_A:${loemdir}/fw_main_A FW_MAIN_B:${loemdir}/fw_main_B \
+     "Firmware A Data":${loemdir}/fw_main_A \
+     "Firmware B Data":${loemdir}/fw_main_B
+
+
+   ${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk \
+     --fv ${loemdir}/fw_main_A \
+     ${loemdir}/vblock_A.${loemid} | grep sha1sum \
+     | sed -e 's/.*: \+//' > ${loemdir}/loem.sha.new
+   ${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk \
+     --fv ${loemdir}/fw_main_B \
+     ${loemdir}/vblock_B.${loemid} | grep sha1sum \
+     | sed -e 's/.*: \+//' >> ${loemdir}/loem.sha.new
+
   # the vblocks don't have root or recovery keys
   tail -4 ${SCRIPTDIR}/data_${base}_expect.txt > ${loemdir}/sha.expect
   cmp ${loemdir}/sha.expect ${loemdir}/loem.sha.new
@@ -109,8 +122,7 @@
 GOOD_OUT=${TMP}.${GOOD_VBLOCKS##*/}.new
 MORE_OUT=${TMP}.${ONEMORE##*/}.new
 
-# TODO: verify
-${FUTILITY} show ${GOOD_OUT} \
+${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk ${GOOD_OUT} \
   | awk '/Firmware body size:/ {print $4}' > ${TMP}.good.body
 ${FUTILITY} dump_fmap -p ${GOOD_OUT} \
   | awk '/FW_MAIN_/ {print $3}' > ${TMP}.good.fw_main
@@ -118,8 +130,7 @@
 if cmp ${TMP}.good.body ${TMP}.good.fw_main; then false; fi
 
 # Make sure that the BIOS with the bad vblocks signed the whole fw body
-# TODO: verify
-${FUTILITY} show ${MORE_OUT} \
+${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk ${MORE_OUT} \
   | awk '/Firmware body size:/ {print $4}' > ${TMP}.onemore.body
 ${FUTILITY} dump_fmap -p ${MORE_OUT} \
   | awk '/FW_MAIN_/ {print $3}' > ${TMP}.onemore.fw_main
@@ -141,9 +152,8 @@
   -k ${KEYDIR}/kernel_subkey.vbpubk \
   ${MORE_OUT} ${MORE_OUT}.2
 
-# TODO: verify
-m=$(${FUTILITY} show ${MORE_OUT}.2 | \
-  egrep 'Firmware version: +1$|Preamble flags: +8$' | wc -l)
+m=$(${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk ${MORE_OUT}.2 \
+  | egrep 'Firmware version: +1$|Preamble flags: +8$' | wc -l)
 [ "$m" = "4" ]
 
 
@@ -160,9 +170,8 @@
   -k ${KEYDIR}/kernel_subkey.vbpubk \
   ${MORE_OUT} ${MORE_OUT}.3
 
-# TODO: verify
-m=$(${FUTILITY} show ${MORE_OUT}.3 | \
-  egrep 'Firmware version: +1$|Preamble flags: +0$' | wc -l)
+m=$(${FUTILITY} verify --publickey ${KEYDIR}/root_key.vbpubk ${MORE_OUT}.3 \
+  | egrep 'Firmware version: +1$|Preamble flags: +0$' | wc -l)
 [ "$m" = "4" ]
 
 
diff --git a/tests/futility/test_sign_kernel.sh b/tests/futility/test_sign_kernel.sh
index 0fdb625..2a8e8c1 100755
--- a/tests/futility/test_sign_kernel.sh
+++ b/tests/futility/test_sign_kernel.sh
@@ -147,6 +147,14 @@
 
   cmp ${TMP}.blob2.${arch}.vb0 ${TMP}.blob2.${arch}.vb1
 
+  # and verify it the new way
+  dd bs=${padding} skip=1 if=${TMP}.blob2.${arch} of=${TMP}.blob2.${arch}.kb1
+  ${FUTILITY} verify --debug \
+    --pad ${padding} \
+    --publickey ${DEVKEYS}/recovery_key.vbpubk \
+    --fv ${TMP}.blob2.${arch}.kb1 \
+    ${TMP}.blob2.${arch}.vb1
+
   echo -n "5 " 1>&3
 
   dd bs=${padding} count=1 if=${TMP}.blob3.${arch} of=${TMP}.blob3.${arch}.vb0
@@ -176,6 +184,13 @@
 
   cmp ${TMP}.blob4.${arch}.vb0 ${TMP}.blob4.${arch}.vb1
 
+  dd bs=${padding} skip=1 if=${TMP}.blob4.${arch} of=${TMP}.blob4.${arch}.kb1
+  ${FUTILITY} verify --debug \
+    --pad ${padding} \
+    --publickey ${DEVKEYS}/kernel_subkey.vbpubk \
+    --fv ${TMP}.blob4.${arch}.kb1 \
+    ${TMP}.blob4.${arch}.vb1
+
   # Note: We specifically do not test repacking with a different --kloadaddr,
   # because the old way has a bug and does not update params->cmd_line_ptr to
   # point at the new on-disk location. Apparently (and not surprisingly), no