| #!/bin/bash |
| |
| # Copyright (c) 2010 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. |
| |
| # Sign the final build image using the "official" keys. |
| # |
| # Prerequisite tools needed in the system path: |
| # |
| # gbb_utility (from src/platform/vboot_reference) |
| # vbutil_kernel (from src/platform/vboot_reference) |
| # cgpt (from src/platform/vboot_reference) |
| # dump_kernel_config (from src/platform/vboot_reference) |
| # verity (from src/platform/verity) |
| # load_kernel_test (from src/platform/vboot_reference) |
| # dumpe2fs |
| |
| # Load common constants and variables. |
| . "$(dirname "$0")/common.sh" |
| |
| # Print usage string |
| usage() { |
| cat <<EOF |
| 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) |
| 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 |
| |
| # Abort on errors. |
| set -e |
| |
| # Make sure the tools we need are available. |
| for prereqs in gbb_utility vbutil_kernel cgpt dump_kernel_config verity \ |
| load_kernel_test dumpe2fs; |
| do |
| type -P "${prereqs}" &>/dev/null || \ |
| { echo "${prereqs} tool not found."; exit 1; } |
| done |
| |
| TYPE=$1 |
| INPUT_IMAGE=$2 |
| KEY_DIR=$3 |
| OUTPUT_IMAGE=$4 |
| |
| # 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} |
| 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: |
| # |
| # dm="0 2097152 verity ROOT_DEV HASH_DEV 2097152 1 \ |
| # sha1 63b7ad16cb9db4b70b28593f825aa6b7825fdcf2" |
| # |
| |
| if [ -z "${dm_config}" ]; then |
| echo "WARNING: Couldn't grab dm_config. Aborting rootfs hash calculation" |
| exit 1 |
| fi |
| local rootfs_sectors=$(echo ${dm_config} | cut -f2 -d' ') |
| local root_dev=$(echo ${dm_config} | cut -f4 -d ' ') |
| local hash_dev=$(echo ${dm_config} | cut -f5 -d ' ') |
| local verity_depth=$(echo ${dm_config} | cut -f7 -d' ') |
| local verity_algorithm=$(echo ${dm_config} | cut -f8 -d' ') |
| |
| # Run the verity tool on the rootfs partition. |
| local table="vroot none ro,"$(sudo verity create \ |
| ${verity_depth} \ |
| ${verity_algorithm} \ |
| ${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}|")" |
| 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=$(sudo 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 ${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} \ |
| --keyblock ${keyblock} \ |
| --signprivate ${signprivate} \ |
| --oldblob ${temp_kimage} \ |
| --config ${temp_config} |
| |
| replace_image_partition ${image} 2 ${updated_kimage} |
| replace_image_partition ${image} 3 ${rootfs_image} |
| } |
| |
| # Extracts the firmware update binaries from the a firmware update |
| # shell ball (generated by src/platform/firmware/pack_firmware.sh) |
| # Args: INPUT_SCRIPT OUTPUT_DIR |
| get_firmwarebin_from_shellball() { |
| local input=$1 |
| local output_dir=$2 |
| if [ -s "${input}" ]; then |
| uudecode -o - ${input} | tar -C ${output_dir} -zxf - 2>/dev/null || \ |
| { echo "Extracting firmware autoupdate failed." && exit 1; } |
| else |
| return 1 |
| fi |
| } |
| |
| # Re-sign the firmware AU payload inside the image rootfs with a new keys. |
| # Args: IMAGE |
| resign_firmware_payload() { |
| local image=$1 |
| |
| # 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 can fail if the image has no |
| # firmware update. |
| get_firmwarebin_from_shellball \ |
| ${rootfs_dir}/usr/sbin/chromeos-firmwareupdate ${shellball_dir} || \ |
| { echo "Didn't find a firmware update. Not signing firmware." |
| return; } |
| echo "Found a valid firmware update shellball." |
| |
| temp_outfd=$(make_temp_file) |
| # Replace the root key in the GBB |
| # TODO(gauravsh): Remove when we lock down the R/O portion of firmware. |
| if [ -e "${KEY_DIR}/hwid" ]; then |
| # Only update the hwid if we see one in the key directory. |
| gbb_utility -s \ |
| --rootkey=${KEY_DIR}/root_key.vbpubk \ |
| --recoverykey=${KEY_DIR}/recovery_key.vbpubk \ |
| --hwid="$(cat ${KEY_DIR}/hwid)" \ |
| ${shellball_dir}/bios.bin ${temp_outfd} |
| else |
| gbb_utility -s \ |
| --rootkey=${KEY_DIR}/root_key.vbpubk \ |
| --recoverykey=${KEY_DIR}/recovery_key.vbpubk \ |
| ${shellball_dir}/bios.bin ${temp_outfd} |
| fi |
| # Resign the firmware with new keys |
| ${SCRIPT_DIR}/resign_firmwarefd.sh ${temp_outfd} ${shellball_dir}/bios.bin \ |
| ${KEY_DIR}/firmware_data_key.vbprivk \ |
| ${KEY_DIR}/firmware.keyblock \ |
| ${KEY_DIR}/kernel_subkey.vbpubk |
| |
| # 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 | |
| sed -e "s#\(.*\)\ \(.*bios.bin.*\)#${newfd_checksum}\ \2#" > ${temp_version} |
| sudo cp ${temp_version} ${shellball_dir}/VERSION |
| |
| # Re-generate firmware_update.tgz and copy over encoded archive in |
| # the original shell ball. |
| new_fwblob=$(make_temp_file) |
| tar zcf - -C ${shellball_dir} . | \ |
| uuencode firmware_package.tgz > ${new_fwblob} |
| new_shellball=$(make_temp_file) |
| cat ${rootfs_dir}/usr/sbin/chromeos-firmwareupdate | \ |
| sed -e '/^begin .*firmware_package/,/end/D' | \ |
| cat - ${new_fwblob} >${new_shellball} |
| sudo cp ${new_shellball} ${rootfs_dir}/usr/sbin/chromeos-firmwareupdate |
| # Force unmount of the image as it is needed later. |
| sudo umount -d ${rootfs_dir} |
| 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} \ |
| ${KEY_DIR}/kernel_data_key.vbprivk \ |
| ${KEY_DIR}/kernel.keyblock |
| echo "Signed SSD image output to ${OUTPUT_IMAGE}" |
| } |
| |
| # Generate the USB (recovery + install) image |
| sign_for_recovery() { |
| ${SCRIPT_DIR}/resign_image.sh ${INPUT_IMAGE} ${OUTPUT_IMAGE} \ |
| ${KEY_DIR}/recovery_kernel_data_key.vbprivk \ |
| ${KEY_DIR}/recovery_kernel.keyblock |
| |
| # Now generate the installer vblock with the SSD keys. |
| temp_kimage=$(make_temp_file) |
| temp_out_vb=$(make_temp_file) |
| extract_image_partition ${OUTPUT_IMAGE} 2 ${temp_kimage} |
| ${SCRIPT_DIR}/resign_kernel_partition.sh ${temp_kimage} ${temp_out_vb} \ |
| ${KEY_DIR}/kernel_data_key.vbprivk \ |
| ${KEY_DIR}/kernel.keyblock |
| |
| # Copy the installer vblock to the stateful partition. |
| local stateful_dir=$(make_temp_dir) |
| mount_image_partition ${OUTPUT_IMAGE} 1 ${stateful_dir} |
| sudo cp ${temp_out_vb} ${stateful_dir}/vmlinuz_hd.vblock |
| |
| echo "Signed recovery image output to ${OUTPUT_IMAGE}" |
| } |
| |
| # Generate the factory install image. |
| sign_for_factory_install() { |
| ${SCRIPT_DIR}/resign_image.sh ${INPUT_IMAGE} ${OUTPUT_IMAGE} \ |
| ${KEY_DIR}/recovery_kernel_data_key.vbprivk \ |
| ${KEY_DIR}/installer_kernel.keyblock |
| echo "Signed factory install image output to ${OUTPUT_IMAGE}" |
| } |
| |
| # 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 |
| resign_firmware_payload ${INPUT_IMAGE} |
| update_rootfs_hash ${INPUT_IMAGE} \ |
| ${KEY_DIR}/kernel.keyblock \ |
| ${KEY_DIR}/kernel_data_key.vbprivk |
| sign_for_ssd |
| elif [ "${TYPE}" == "recovery" ]; then |
| resign_firmware_payload ${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 |
| resign_firmware_payload ${INPUT_IMAGE} |
| update_rootfs_hash ${INPUT_IMAGE} \ |
| ${KEY_DIR}/installer_kernel.keyblock \ |
| ${KEY_DIR}/recovery_kernel_data_key.vbprivk |
| sign_for_factory_install |
| else |
| echo "Invalid type ${TYPE}" |
| exit 1 |
| fi |