diff --git a/scripts/image_signing/common.sh b/scripts/image_signing/common.sh index ffc344cb22..37834dc541 100755 --- a/scripts/image_signing/common.sh +++ b/scripts/image_signing/common.sh @@ -45,9 +45,9 @@ extract_image_partition() { 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 cdc1837af0..d7140acc64 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 input_image /path/to/keys/dir output_image -# -# where 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 < input_image /path/to/keys/dir output_image" +Usage: $PROG input_image /path/to/keys/dir [output_image] where 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 @@ fi 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 @@ INPUT_IMAGE=$2 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 @@ recalculate_rootfs_hash() { 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 @@ recalculate_rootfs_hash() { 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 @@ recalculate_rootfs_hash() { --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 @@ recalculate_rootfs_hash() { # 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 @@ resign_firmware_payload() { # 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 @@ resign_firmware_payload() { # 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 @@ resign_firmware_payload() { 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 </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_ssd() { 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 @@ if [ "${FW_UPDATE}" == "1" ]; then 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