Add a "verify" option to sign_official_build.sh.

This option will perform verification operations on an image.
1) Check if the RootFS hash is correct.
2) Check if the image will verify using recovery keys (in recovery mode)
3) Check if the image will verify using SSD keys (in non-recovery mode)

2) and 3) are both tested with and without dev mode.

Also re-factor existing code for rootfs calculation and update.

BUG=5830,3496
TEST=manual

Example usage and output follows:

# Verifying an image meant for factory install.
sudo ./sign_official_build.sh verify factory_install_image.sh ../../tests/devkeys/
Verifying RootFS hash...
PASS: RootFS hash is correct
Testing key verification...
With Recovery Key (Recovery Mode ON, Dev Mode OFF): NO
With Recovery Key (Recovery Mode ON, Dev Mode ON): YES
With SSD Key (Recovery Mode OFF, Dev Mode OFF): NO
With SSD Key (Recovery Mode OFF, Dev Mode ON): YES

# Verifying an image meant for recovery mode.
sudo ./sign_official_build.sh verify recovery_image.bin ../../tests/devkeys/
Verifying RootFS hash...
PASS: RootFS hash is correct
Testing key verification...
With Recovery Key (Recovery Mode ON, Dev Mode OFF): YES
With Recovery Key (Recovery Mode ON, Dev Mode ON): YES
With SSD Key (Recovery Mode OFF, Dev Mode OFF): NO
With SSD Key (Recovery Mode OFF, Dev Mode ON): YES

# Verifying an image meant for the SSD drive.
sudo ./sign_official_build.sh verify ssd_image.bin ../../tests/devkeys/
Verifying RootFS hash...
PASS: RootFS hash is correct
Testing key verification...
With Recovery Key (Recovery Mode ON, Dev Mode OFF): NO
With Recovery Key (Recovery Mode ON, Dev Mode ON): NO
With SSD Key (Recovery Mode OFF, Dev Mode OFF): YES
With SSD Key (Recovery Mode OFF, Dev Mode ON): YES

# Image with an incorrect rootfs hash but otherwise validly signed
sudo ./sign_official_build.sh verify ssd_image.bin ../../tests/devkeys/
Verifying RootFS hash...
FAILED: RootFS hash is incorrect.
Expected: ebce345727ca05ea9368d3b8d5ce1c81471d7d3b
Got: 9b092985996bb2422b11487a66929a1a004df4fc
Testing key verification...
With Recovery Key (Recovery Mode ON, Dev Mode OFF): NO
With Recovery Key (Recovery Mode ON, Dev Mode ON): NO
With SSD Key (Recovery Mode OFF, Dev Mode OFF): YES
With SSD Key (Recovery Mode OFF, Dev Mode ON): YES

# Image signed using a different set of keys (but validly signed).
sudo ./sign_official_build.sh verify invalid_image.bin ../../tests/devkeys/
Verifying RootFS hash...
PASS: RootFS hash is correct (70e6f2de0220991fd503a6fcc7edac131b4a48ca)
Testing key verification...
With Recovery Key (Recovery Mode ON, Dev Mode OFF): NO
With Recovery Key (Recovery Mode ON, Dev Mode ON): NO
With SSD Key (Recovery Mode OFF, Dev Mode OFF): NO
With SSD Key (Recovery Mode OFF, Dev Mode ON): YES

Change-Id: I4960cdbbbe93e685346417b882739f9cfd5f6b75

Review URL: http://codereview.chromium.org/3327005
diff --git a/scripts/image_signing/common.sh b/scripts/image_signing/common.sh
index ffc344c..37834dc 100755
--- a/scripts/image_signing/common.sh
+++ b/scripts/image_signing/common.sh
@@ -45,9 +45,9 @@
   local output_file=$3
   local offset=$(partoffset "$image" "$partnum")
   local size=$(partsize "$image" "$partnum")
-  dd if=$image of=$output_file bs=512 skip=$offset count=$size conv=notrunc
+  dd if=$image of=$output_file bs=512 skip=$offset count=$size conv=notrunc >/dev/null 2>&1
 }
-  
+
 # Replace a partition in an image from file
 # Args: IMAGE PARTNUM INPUTFILE
 replace_image_partition() {
diff --git a/scripts/image_signing/sign_official_build.sh b/scripts/image_signing/sign_official_build.sh
index cdc1837..d7140ac 100755
--- a/scripts/image_signing/sign_official_build.sh
+++ b/scripts/image_signing/sign_official_build.sh
@@ -13,25 +13,27 @@
 #  cgpt (from src/platform/vboot_reference)
 #  dump_kernel_config (from src/platform/vboot_reference)
 #  verity (from src/platform/verity)
-#
-# Usage: sign_for_ssd.sh <type> input_image /path/to/keys/dir output_image
-# 
-# where <type> is one of:
-#               ssd  (sign an SSD image)
-#               recovery (sign a USB recovery image)               
-#               install (sign a factory install image) 
+#  load_kernel_test (from src/platform/vboot_reference)
 
 # Load common constants and variables.
 . "$(dirname "$0")/common.sh"
 
-if [ $# -ne 4 ]; then
+# Print usage string
+usage() {
   cat <<EOF
-Usage: $0 <type> input_image /path/to/keys/dir output_image"
+Usage: $PROG <type> input_image /path/to/keys/dir [output_image]
 where <type> is one of:
              ssd  (sign an SSD image)
-             recovery (sign a USB recovery image)               
-             install (sign a factory install image) 
+             recovery (sign a USB recovery image)
+             install (sign a factory install image)
+             verify (verify an image including rootfs hashes)
+
+If you are signing an image, you must specify an [output_image].
 EOF
+}
+
+if [ $# -ne 3 ] && [ $# -ne 4 ]; then
+  usage
   exit 1
 fi
 
@@ -39,7 +41,9 @@
 set -e
 
 # Make sure the tools we need are available.
-for prereqs in gbb_utility vbutil_kernel cgpt dump_kernel_config verity; do
+for prereqs in gbb_utility vbutil_kernel cgpt dump_kernel_config verity \
+  load_kernel_test;
+do
   type -P "${prereqs}" &>/dev/null || \
     { echo "${prereqs} tool not found."; exit 1; }
 done
@@ -49,19 +53,35 @@
 KEY_DIR=$3
 OUTPUT_IMAGE=$4
 
-# Re-calculate rootfs hash, update rootfs and kernel command line.
-# Args: IMAGE KEYBLOCK PRIVATEKEY
-recalculate_rootfs_hash() {
-  echo "Recalculating rootfs"
-  local image=$1  # Input image.
-  local keyblock=$2  # Keyblock for re-generating signed kernel partition
-  local signprivate=$3  # Private key to use for signing.
-
-  # First, grab the existing kernel partition and get the kernel config.
+# Get current rootfs hash and kernel command line
+# ARGS: IMAGE
+grab_kernel_config() {
+  local image=$1
+  # Grab the existing kernel partition and get the kernel config.
   temp_kimage=$(make_temp_file)
   extract_image_partition ${image} 2 ${temp_kimage}
-  local kernel_config=$(sudo dump_kernel_config ${temp_kimage})
-  local dm_config=$(echo $kernel_config |
+  dump_kernel_config ${temp_kimage}
+}
+
+# Get the hash from a kernel config command line
+get_hash_from_config() {
+  local kernel_config=$1
+  echo ${kernel_config} | sed -e 's/.*dm="\([^"]*\)".*/\1/g' | \
+    cut -f2- -d, | cut -f9 -d ' '
+}
+
+# Calculate rootfs hash of an image
+# Args: ROOTFS_IMAGE KERNEL_CONFIG HASH_IMAGE
+#
+# rootfs calculation parameters are grabbed from KERNEL_CONFIG
+#
+# Returns an updated kernel config command line with the new hash.
+# and writes the new hash image to the file HASH_IMAGE
+calculate_rootfs_hash() {
+  local rootfs_image=$1
+  local kernel_config=$2
+  local hash_image=$3
+  local dm_config=$(echo ${kernel_config} |
     sed -e 's/.*dm="\([^"]*\)".*/\1/g' |
     cut -f2- -d,)
   # We extract dm=... portion of the config command line. Here's an example:
@@ -72,7 +92,7 @@
 
   if [ -z "${dm_config}" ]; then
     echo "WARNING: Couldn't grab dm_config. Aborting rootfs hash calculation"
-    return
+    exit 1
   fi
   local rootfs_sectors=$(echo ${dm_config} | cut -f2 -d' ')
   local root_dev=$(echo ${dm_config} | cut -f4 -d ' ')
@@ -80,28 +100,49 @@
   local verity_depth=$(echo ${dm_config} | cut -f7 -d' ')
   local verity_algorithm=$(echo ${dm_config} | cut -f8 -d' ')
 
-  # Mount the rootfs and run the verity tool on it.
-  local hash_image=$(make_temp_file)
-  local rootfs_img=$(make_temp_file)
-  extract_image_partition ${image} 3 ${rootfs_img}
+  # Run the verity tool on the rootfs partition.
   local table="vroot none ro,"$(sudo verity create \
     ${verity_depth} \
     ${verity_algorithm} \
-    ${rootfs_img} \
+    ${rootfs_image} \
     $((rootfs_sectors / 8)) \
     ${hash_image})
   # Reconstruct new kernel config command line and replace placeholders.
   table="$(echo "$table" |
     sed -s "s|ROOT_DEV|${root_dev}|g;s|HASH_DEV|${hash_dev}|")"
-  kernel_config=$(echo ${kernel_config} |
-    sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${table}\3#g")
+  echo ${kernel_config} | sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${table}\3#g"
+}
+
+# Re-calculate rootfs hash, update rootfs and kernel command line.
+# Args: IMAGE KEYBLOCK PRIVATEKEY
+update_rootfs_hash() {
+  echo "Recalculating rootfs"
+  local image=$1  # Input image.
+  local keyblock=$2  # Keyblock for re-generating signed kernel partition
+  local signprivate=$3  # Private key to use for signing.
+
+  local rootfs_image=$(make_temp_file)
+  extract_image_partition ${image} 3 ${rootfs_image}
+  local kernel_config=$(grab_kernel_config "${image}")
+  local hash_image=$(make_temp_file)
+
+  local new_kernel_config=$(calculate_rootfs_hash "${rootfs_image}" \
+    "${kernel_config}" "${hash_image}")
+
+  local rootfs_blocks=$(dumpe2fs "${rootfs_image}" 2> /dev/null |
+    grep "Block count" |
+    tr -d ' ' |
+    cut -f2 -d:)
+  local rootfs_sectors=$((rootfs_blocks * 8))
 
   # Overwrite the appended hashes in the rootfs
   local temp_config=$(make_temp_file)
-  echo ${kernel_config} >${temp_config}
-  dd if=${hash_image} of=${rootfs_img} bs=512 \
+  echo ${new_kernel_config} >${temp_config}
+  dd if=${hash_image} of=${rootfs_image} bs=512 \
     seek=${rootfs_sectors} conv=notrunc
 
+  local temp_kimage=$(make_temp_file)
+  extract_image_partition ${image} 2 ${temp_kimage}
   # Re-calculate kernel partition signature and command line.
   local updated_kimage=$(make_temp_file)
   vbutil_kernel --repack ${updated_kimage} \
@@ -111,7 +152,7 @@
     --config ${temp_config}
 
   replace_image_partition ${image} 2 ${updated_kimage}
-  replace_image_partition ${image} 3 ${rootfs_img}
+  replace_image_partition ${image} 3 ${rootfs_image}
 }
 
 # Extracts the firmware update binaries from the a firmware update
@@ -119,7 +160,7 @@
 # Args: INPUT_SCRIPT OUTPUT_DIR
 get_firmwarebin_from_shellball() {
   local input=$1
-  local output_dir=$2  
+  local output_dir=$2
   uudecode -o - ${input} | tar -C ${output_dir} -zxf - 2>/dev/null || \
     echo "Extracting firmware autoupdate failed." && exit 1
 }
@@ -132,7 +173,7 @@
   # Grab firmware image from the autoupdate shellball.
   local rootfs_dir=$(make_temp_dir)
   mount_image_partition ${image} 3 ${rootfs_dir}
-  
+
   local shellball_dir=$(make_temp_dir)
   get_firmwarebin_from_shellball \
     ${rootfs_dir}/usr/sbin/chromeos-firmwareupdate ${shellball_dir}
@@ -154,7 +195,7 @@
   # Replace MD5 checksum in the firmware update payload
   newfd_checksum=$(md5sum ${shellball_dir}/bios.bin | cut -f 1 -d ' ')
   temp_version=$(make_temp_file)
-  cat ${shellball_dir}/VERSION | 
+  cat ${shellball_dir}/VERSION |
   sed -e "s#\(.*\)\ \(.*bios.bin.*\)#${newfd_checksum}\ \2#" > ${temp_version}
   sudo cp ${temp_version} ${shellball_dir}/VERSION
 
@@ -173,6 +214,57 @@
   echo "Re-signed firmware AU payload in $image"
 }
 
+# Verify an image including rootfs hash using the specified keys.
+verify_image() {
+  local kernel_config=$(grab_kernel_config ${INPUT_IMAGE})
+  local rootfs_image=$(make_temp_file)
+  extract_image_partition ${INPUT_IMAGE} 3 ${rootfs_image}
+  local hash_image=$(make_temp_file)
+  local type=""
+
+
+  # First, perform RootFS verification
+  echo "Verifying RootFS hash..."
+  local new_kernel_config=$(calculate_rootfs_hash "${rootfs_image}" \
+    "${kernel_config}" "${hash_image}")
+  local expected_hash=$(get_hash_from_config "${new_kernel_config}")
+  local got_hash=$(get_hash_from_config "${kernel_config}")
+
+  if [ ! "${got_hash}" = "${expected_hash}" ]; then
+    cat <<EOF
+FAILED: RootFS hash is incorrect.
+Expected: ${expected_hash}
+Got: ${got_hash}
+EOF
+  else
+    echo "PASS: RootFS hash is correct (${expected_hash})"
+  fi
+
+  # Now try and verify kernel partition signature.
+  set +e
+  local try_key=${KEY_DIR}/recovery_key.vbpubk
+  echo "Testing key verification..."
+  # The recovery key is only used in the recovery mode.
+  echo -n "With Recovery Key (Recovery Mode ON, Dev Mode OFF): " && \
+  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 2 >/dev/null 2>&1 && \
+    echo "YES"; } || echo "NO"
+  echo -n "With Recovery Key (Recovery Mode ON, Dev Mode ON): " && \
+  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 3 >/dev/null 2>&1 && \
+    echo "YES"; } || echo "NO"
+
+  try_key=${KEY_DIR}/kernel_subkey.vbpubk
+  # The SSD key is only used in non-recovery mode.
+  echo -n "With SSD Key (Recovery Mode OFF, Dev Mode OFF): " && \
+  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 0 >/dev/null 2>&1  && \
+    echo "YES"; } || echo "NO"
+  echo -n "With SSD Key (Recovery Mode OFF, Dev Mode ON): " && \
+  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 1 >/dev/null 2>&1 && \
+    echo "YES"; } || echo "NO"
+  set -e
+
+  # TODO(gauravsh): Check embedded firmware AU signatures.
+}
+
 # Generate the SSD image
 sign_for_ssd() {
   ${SCRIPT_DIR}/resign_image.sh ${INPUT_IMAGE} ${OUTPUT_IMAGE} \
@@ -185,7 +277,7 @@
 sign_for_recovery() {
   ${SCRIPT_DIR}/resign_image.sh ${INPUT_IMAGE} ${OUTPUT_IMAGE} \
     ${KEY_DIR}/recovery_kernel_data_key.vbprivk \
-    ${KEY_DIR}/recovery_kernel.keyblock 
+    ${KEY_DIR}/recovery_kernel.keyblock
 
   # Now generate the installer vblock with the SSD keys.
   temp_kimage=$(make_temp_file)
@@ -217,18 +309,31 @@
   resign_firmware_payload ${INPUT_IMAGE}
 fi
 
+# Verification
+if [ "${TYPE}" == "verify" ]; then
+  verify_image
+  exit 1
+fi
+
+
+# Signing requires an output image name
+if [ -z "${OUTPUT_IMAGE}" ]; then
+  usage
+  exit 1
+fi
+
 if [ "${TYPE}" == "ssd" ]; then
-  recalculate_rootfs_hash ${INPUT_IMAGE} \
+  update_rootfs_hash ${INPUT_IMAGE} \
     ${KEY_DIR}/kernel.keyblock \
     ${KEY_DIR}/kernel_data_key.vbprivk
   sign_for_ssd
 elif [ "${TYPE}" == "recovery" ]; then
-  recalculate_rootfs_hash ${INPUT_IMAGE} \
+  update_rootfs_hash ${INPUT_IMAGE} \
     ${KEY_DIR}/recovery_kernel.keyblock \
     ${KEY_DIR}/recovery_kernel_data_key.vbprivk
   sign_for_recovery
 elif [ "${TYPE}" == "install" ]; then
-  recalculate_rootfs_hash ${INPUT_IMAGE} \
+  update_rootfs_hash ${INPUT_IMAGE} \
     ${KEY_DIR}/installer_kernel.keyblock \
     ${KEY_DIR}/recovery_kernel_data_key.vbprivk
   sign_for_factory_install