#!/bin/bash -eux # Copyright 2014 The ChromiumOS Authors # 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" DATADIR="${SCRIPT_DIR}/futility/data" # The input BIOS images are all signed with MP keys. We resign them with dev # keys, which means we can precalculate the expected results. Note that the # script does not change the root or recovery keys in the GBB. INFILES=" ${DATADIR}/bios_link_mp.bin ${DATADIR}/bios_peppy_mp.bin " # BIOS image containing CBFS RW/A and RW/B, and signed with developer keys. GOOD_CBFS="${DATADIR}/bios_voxel_dev.bin" # BIOS image containing CBFS RW/A and RW/B, and signed with developer keys. INFILES="${INFILES} ${GOOD_CBFS} " # We also want to test that we can sign an image without any valid firmware # preambles. That one won't be able to tell how much of the FW_MAIN region is # the valid firmware, so it'll have to sign the entire region. GOOD_VBLOCKS="${DATADIR}/bios_peppy_mp.bin" ONEMORE=bios_peppy_mp_no_vblock.bin CLEAN_B=bios_peppy_mp_clean_b_slot.bin cp "${GOOD_VBLOCKS}" "${ONEMORE}" cp "${GOOD_VBLOCKS}" "${CLEAN_B}" GOOD_DEV="${DATADIR}/bios_peppy_dev.bin" NO_B_SLOT_PATCH="${DATADIR}/bios_voxel_dev.no_b_slot.xxd.patch" BAD_KEYBLOCK_PATCHES=( "${DATADIR}/bios_peppy_dev.bad_keyblock_data_key_offset_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_data_key_size_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_hash_data_size_too_small.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_hash_invalid_contents.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_hash_offset_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_hash_size_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_invalid_magic.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_invalid_major_version.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_size_not_fully_signed.xxd.patch" ) BAD_PREAMBLE_PATCHES=( "${DATADIR}/bios_peppy_dev.bad_preamble_body_signature_offset_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_body_signature_size_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_header_version_major.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_header_version_minor.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_kernel_subkey_offset_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_kernel_subkey_size_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_signature_data_size_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_signature_data_size_too_small.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_signature_invalid_contents.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_signature_offset_too_big.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_signature_size_too_big.xxd.patch" ) BAD_FMAP_KEYBLOCK_PATCHES=( "${DATADIR}/bios_peppy_dev.bad_keyblock_fmap_too_small_for_whole.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_keyblock_fmap_too_small.xxd.patch" ) BAD_FMAP_PREAMBLE_PATCHES=( "${DATADIR}/bios_peppy_dev.bad_preamble_fmap_too_small_for_whole.xxd.patch" "${DATADIR}/bios_peppy_dev.bad_preamble_fmap_too_small.xxd.patch" ) "${FUTILITY}" load_fmap "${ONEMORE}" VBLOCK_A:/dev/urandom VBLOCK_B:/dev/zero INFILES="${INFILES} ${ONEMORE}" # args: xxd_patch_file input_file function apply_xxd_patch { xxd -r "${1}" "${2}" } # args: file1 file2 function cmp_first_line { cmp <(head -n1 "${1}") <(head -n1 "${2}") } function cmp_last_line { cmp <(tail -n1 "${1}") <(tail -n1 "${2}") } set -o pipefail count=0 for infile in $INFILES; do base=${infile##*/} : $(( count++ )) echo -n "${count} " 1>&3 outfile="${TMP}.${base}.new" loemid="loem" loemdir="${TMP}.${base}_dir" mkdir -p "${loemdir}" "${FUTILITY}" sign \ -K "${KEYDIR}" \ -v 14 \ -f 8 \ -d "${loemdir}" \ -l "${loemid}" \ "${infile}" "${outfile}" # check the firmware version and preamble flags m=$("${FUTILITY}" verify --publickey "${KEYDIR}/root_key.vbpubk" \ "${outfile}" | grep -c -E 'Firmware version: +14$|Preamble flags: +8$') [ "${m}" = "4" ] # check the sha1sums "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" "${outfile}" \ | grep ::sha1_sum:: | sort | cut -d: -f5- > "${TMP}.${base}.sha.new" cmp "${SCRIPT_DIR}/futility/data_${base}_expect.txt" "${TMP}.${base}.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" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ --fv "${loemdir}/fw_main_A" "${loemdir}/vblock_A.${loemid}" \ | grep ::sha1_sum:: | sort | cut -d: -f3- > "${loemdir}/loem.sha.new" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ --fv "${loemdir}/fw_main_B" "${loemdir}/vblock_B.${loemid}" \ | grep ::sha1_sum:: | sort | cut -d: -f3- >> "${loemdir}/loem.sha.new" # the vblocks don't have root or recovery keys cmp <(grep -Ev "recovery_key|root_key" "${SCRIPT_DIR}/futility/data_${base}_expect.txt") \ "${loemdir}/loem.sha.new" done # Make sure that the BIOS with the good vblocks signed the right size. GOOD_OUT="${TMP}.${GOOD_VBLOCKS##*/}.new" MORE_OUT="${TMP}.${ONEMORE##*/}.new" GOOD_CBFS_OUT="${TMP}.${GOOD_CBFS##*/}.new" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" "${GOOD_OUT}" \ | grep "::body::size::" | cut -d: -f9 > "${TMP}.good.body" "${FUTILITY}" dump_fmap -p "${GOOD_OUT}" \ | awk '/FW_MAIN_/ {print $3}' > "${TMP}.good.fw_main" # This should fail because they're different 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 "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" "${MORE_OUT}" \ | grep "::body::size::" | cut -d: -f9 > "${TMP}.onemore.body" "${FUTILITY}" dump_fmap -p "${MORE_OUT}" \ | awk '/FW_MAIN_/ {print $3}' > "${TMP}.onemore.fw_main" # These should match cmp "${TMP}.onemore.body" "${TMP}.onemore.fw_main" cmp "${TMP}.onemore.body" "${TMP}.good.fw_main" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${GOOD_CBFS_OUT}" \ | grep "::body::size::" | cut -d: -f9 > "${TMP}.good_cbfs.body" "${FUTILITY}" dump_fmap -p "${GOOD_CBFS_OUT}" \ | awk '/FW_MAIN_/ {print $3}' > "${TMP}.good_cbfs.fw_main" if cmp "${TMP}.good_cbfs.body" "${TMP}.good_cbfs.fw_main"; then false; fi # Sign CBFS image after adding new files. Size should increase but still be # smaller than FlashMap size. : $(( count++ )) echo -n "${count} " 1>&3 cp "${GOOD_CBFS_OUT}" "${GOOD_CBFS_OUT}.1" truncate -s 512 "${TMP}.zero_512" cbfstool "${GOOD_CBFS_OUT}.1" expand -r FW_MAIN_A,FW_MAIN_B cbfstool "${GOOD_CBFS_OUT}.1" add \ -r FW_MAIN_A,FW_MAIN_B -f "${TMP}.zero_512" -n new-data-file -t raw "${FUTILITY}" sign \ -s "${KEYDIR}/firmware_data_key.vbprivk" \ -K "${KEYDIR}" \ "${GOOD_CBFS_OUT}.1" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${GOOD_CBFS_OUT}.1" \ | grep "::body::size::" | cut -d: -f9 > "${TMP}.good_cbfs.1.body" "${FUTILITY}" dump_fmap -p "${GOOD_CBFS_OUT}" \ | awk '/FW_MAIN_/ {print $3}' > "${TMP}.good_cbfs.1.fw_main" # Check if size increased, but also if it was correctly truncated, # so it does not span over whole FlashMap area. [[ $(head -n1 "${TMP}.good_cbfs.body") \ < $(head -n1 "${TMP}.good_cbfs.1.body") ]] [[ $(tail -n1 "${TMP}.good_cbfs.body") \ < $(tail -n1 "${TMP}.good_cbfs.1.body") ]] [[ $(head -n1 "${TMP}.good_cbfs.1.body") \ < $(head -n1 "${TMP}.good_cbfs.1.fw_main") ]] [[ $(tail -n1 "${TMP}.good_cbfs.1.body") \ < $(tail -n1 "${TMP}.good_cbfs.1.fw_main") ]] # Sign image again but don't specify the version or the preamble flags. # The firmware version and preamble flags should be preserved. # NOTICE: Version preservation behavior changed from defaulting to 1. : $(( count++ )) echo -n "${count} " 1>&3 "${FUTILITY}" sign \ -b "${KEYDIR}/firmware.keyblock" \ -K "${KEYDIR}" \ "${MORE_OUT}" "${MORE_OUT}.2" m=$("${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${MORE_OUT}.2" | grep -c -E 'firmware_version::14$|preamble::flags::8$') [ "${m}" = "4" ] # If the original preamble is not present, the preamble flags should be zero. : $(( count++ )) echo -n "${count} " 1>&3 "${FUTILITY}" load_fmap "${MORE_OUT}" VBLOCK_A:/dev/urandom VBLOCK_B:/dev/zero "${FUTILITY}" sign \ -k "${KEYDIR}/kernel_subkey.vbpubk" \ -K "${KEYDIR}" \ "${MORE_OUT}" "${MORE_OUT}.3" m=$("${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${MORE_OUT}.3" | grep -c -E 'firmware_version::1$|preamble::flags::0$') [ "${m}" = "4" ] # Check signing when B slot is empty : $(( count++ )) echo -n "${count} " 1>&3 "${FUTILITY}" load_fmap "${CLEAN_B}" VBLOCK_B:/dev/zero FW_MAIN_B:/dev/zero "${FUTILITY}" sign \ -s "${KEYDIR}/firmware_data_key.vbprivk" \ -b "${KEYDIR}/firmware.keyblock" \ -K "${KEYDIR}" \ "${CLEAN_B}" "${CLEAN_B}.1" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" "${CLEAN_B}.1" \ | grep "::body::size::" | cut -d: -f9 > "${TMP}.clean_b.body" "${FUTILITY}" dump_fmap -p "${CLEAN_B}.1" \ | awk '/FW_MAIN_/ {print $3}' > "${TMP}.clean_b.fw_main" # These should not be equal, as FW_MAIN_A size should be kept intact, when size # of FW_MAIN_B should be taken from FlashMap. if cmp "${TMP}.clean_b.body" "${TMP}.clean_b.fw_main" ; then false; fi if cmp "${TMP}.clean_b.body" "${TMP}.good.body" ; then false; fi cmp_first_line "${TMP}.clean_b.body" "${TMP}.good.body" cmp_last_line "${TMP}.clean_b.body" "${TMP}.clean_b.fw_main" # Version for slot A should be kept intact, while for B slot it should default # to 1. All flags should be zero. m=$("${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${CLEAN_B}.1" \ | grep -c -E 'firmware_version::1$|preamble::flags::0$|firmware_version::2$') [ "${m}" = "4" ] # Check signing when there is no B slot : $(( count++ )) echo -n "${count} " 1>&3 NO_B_SLOT="${TMP}.${GOOD_CBFS##*/}.no_b_slot" NO_B_SLOT_SIGNED_IMG="${NO_B_SLOT}.signed" cp "${GOOD_CBFS}" "${NO_B_SLOT}" apply_xxd_patch "${NO_B_SLOT_PATCH}" "${NO_B_SLOT}" "${FUTILITY}" sign \ -s "${KEYDIR}/firmware_data_key.vbprivk" \ -k "${KEYDIR}/kernel_subkey.vbpubk" \ -K "${KEYDIR}" \ -v 1 \ "${NO_B_SLOT}" "${NO_B_SLOT_SIGNED_IMG}" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${NO_B_SLOT_SIGNED_IMG}" \ | grep "::body::size::" | cut -d: -f9 > "${TMP}.no_b_slot.body" "${FUTILITY}" dump_fmap -p "${NO_B_SLOT_SIGNED_IMG}" \ | awk '/FW_MAIN_/ {print $3}' > "${TMP}.no_b_slot.fw_main" if cmp "${TMP}.no_b_slot.body" "${TMP}.no_b_slot.fw_main" ; then false; fi cmp "${TMP}.no_b_slot.body" <(tail -n1 "${TMP}.good_cbfs.body") m=$("${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" \ "${NO_B_SLOT_SIGNED_IMG}" \ | grep -c -E 'firmware_version::1$|preamble::flags::0$') [ "${m}" = "2" ] # Check signing when cbfstool reports incorrect size # Signing should fail, as it should not be possible for CBFS contents to be # bigger than FlashMap size of the area : $(( count++ )) echo -n "${count} " 1>&3 CBFSTOOL_STUB="$(realpath "${TMP}.cbfs_stub.sh")" echo -en 'echo "0xFFEEDD0"; exit 0;' > "${CBFSTOOL_STUB}" chmod +x "${CBFSTOOL_STUB}" if CBFSTOOL="${CBFSTOOL_STUB}" "${FUTILITY}" sign \ -b "${KEYDIR}/firmware.keyblock" \ -k "${KEYDIR}/kernel_subkey.vbpubk" \ -K "${KEYDIR}" \ -v 1 \ "${GOOD_CBFS}" "${TMP}.1.${GOOD_CBFS##*/}" then false fi # Redefine cbfstool stub to return valid value for FW_MAIN_A and invalid for # FW_MAIN_B size. With this behavior futility should fail to sign this image, # as cbfstool should never return incorrect size (larger than area). cp "${GOOD_CBFS}" "${TMP}.good_cbfs.bin" FW_MAIN_A_SIZE="$(printf '0x%x' \ "$(cbfstool "${TMP}.good_cbfs.bin" truncate -r FW_MAIN_A)")" MARK_FILE="$(realpath "${TMP}.mark1")" rm -f "${MARK_FILE}" cat << EOF > "${CBFSTOOL_STUB}" #!/usr/bin/env bash if ! [ -f "${MARK_FILE}" ]; then echo "${FW_MAIN_A_SIZE}"; echo 1 > "${MARK_FILE}"; else echo 0xFFFFAA0; fi exit 0; EOF if CBFSTOOL="${CBFSTOOL_STUB}" "${FUTILITY}" sign \ -K "${KEYDIR}" \ -v 1 \ "${GOOD_CBFS}" "${TMP}.2.${GOOD_CBFS##*/}" then false fi # Check various incorrect values in VBLOCK (keyblock and preamble) : $(( count++ )) echo -n "${count} " 1>&3 bad_counter=1 for keyblock_patch in "${BAD_KEYBLOCK_PATCHES[@]}"; do echo -n "${count}.${bad_counter} " 1>&3 BAD_IN="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.in.bin" BAD_OUT="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.out.bin" cp "${GOOD_DEV}" "${BAD_IN}" apply_xxd_patch "${keyblock_patch}" "${BAD_IN}" FUTIL_OUTPUT="$(if "${FUTILITY}" verify -P \ --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_IN}"; \ then false; fi)" grep -q 'bios::VBLOCK_A::keyblock::invalid' <<< "${FUTIL_OUTPUT}" FUTIL_OUTPUT="$("${FUTILITY}" sign \ -K "${KEYDIR}" \ "${BAD_IN}" "${BAD_OUT}" 2>&1)" grep -q 'VBLOCK_A keyblock is invalid' <<< "${FUTIL_OUTPUT}" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_OUT}" \ | grep "::body::size::" | cut -d: -f9 > "${BAD_OUT}.body" "${FUTILITY}" dump_fmap -p "${BAD_OUT}" \ | awk '/FW_MAIN_/ {print $3}' > "${BAD_OUT}.fw_main" cmp "${BAD_OUT}.fw_main" "${TMP}.good.fw_main" cmp_first_line "${BAD_OUT}.body" "${TMP}.good.fw_main" cmp_last_line "${BAD_OUT}.body" "${TMP}.good.body" : $(( bad_counter++ )) done for vblock_patch in "${BAD_PREAMBLE_PATCHES[@]}"; do echo -n "${count}.${bad_counter} " 1>&3 BAD_IN="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.in.bin" BAD_OUT="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.out.bin" cp "${GOOD_DEV}" "${BAD_IN}" apply_xxd_patch "${vblock_patch}" "${BAD_IN}" FUTIL_OUTPUT="$(if "${FUTILITY}" verify -P \ --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_IN}"; \ then false; fi)" grep -q 'bios::VBLOCK_A::preamble::invalid' <<< "${FUTIL_OUTPUT}" FUTIL_OUTPUT="$("${FUTILITY}" sign \ -K "${KEYDIR}" \ "${BAD_IN}" "${BAD_OUT}" 2>&1)" grep -q 'VBLOCK_A preamble is invalid' <<< "${FUTIL_OUTPUT}" "${FUTILITY}" verify -P --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_OUT}" \ | grep "::body::size::" | cut -d: -f9 > "${BAD_OUT}.body" "${FUTILITY}" dump_fmap -p "${BAD_OUT}" \ | awk '/FW_MAIN_/ {print $3}' > "${BAD_OUT}.fw_main" cmp "${BAD_OUT}.fw_main" "${TMP}.good.fw_main" cmp_first_line "${BAD_OUT}.body" "${TMP}.good.fw_main" cmp_last_line "${BAD_OUT}.body" "${TMP}.good.body" : $(( bad_counter++ )) done for vblock_patch in "${BAD_FMAP_KEYBLOCK_PATCHES[@]}"; do echo -n "${count}.${bad_counter} " 1>&3 BAD_IN="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.in.bin" BAD_OUT="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.out.bin" cp "${GOOD_DEV}" "${BAD_IN}" apply_xxd_patch "${vblock_patch}" "${BAD_IN}" FUTIL_OUTPUT="$(if "${FUTILITY}" verify -P\ --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_IN}"; \ then false; fi)" grep -q 'bios::VBLOCK_A::keyblock::invalid' <<< "${FUTIL_OUTPUT}" FUTIL_OUTPUT="$(if "${FUTILITY}" sign \ -K "${KEYDIR}" \ "${BAD_IN}" "${BAD_OUT}" 2>&1; \ then false; fi)" m="$(grep -c -E \ 'VBLOCK_A keyblock is invalid|Keyblock and preamble do not fit in VBLOCK' \ <<< "${FUTIL_OUTPUT}")" [ "${m}" = "2" ] : $(( bad_counter++ )) done echo -n "${count}.${bad_counter} " 1>&3 BAD_IN="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.in.bin" BAD_OUT="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.out.bin" cp "${GOOD_DEV}" "${BAD_IN}" apply_xxd_patch "${BAD_FMAP_PREAMBLE_PATCHES[0]}" "${BAD_IN}" FUTIL_OUTPUT="$(if "${FUTILITY}" verify -P \ --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_IN}"; \ then false; fi)" grep -q 'bios::VBLOCK_A::preamble::invalid' <<< "${FUTIL_OUTPUT}" FUTIL_OUTPUT="$(if "${FUTILITY}" sign \ -K "${KEYDIR}" \ "${BAD_IN}" "${BAD_OUT}" 2>&1; \ then false; fi)" m="$(grep -c -E \ 'VBLOCK_A preamble is invalid|Keyblock and preamble do not fit in VBLOCK' \ <<< "${FUTIL_OUTPUT}")" [ "${m}" = "2" ] : $(( bad_counter++ )) echo -n "${count}.${bad_counter} " 1>&3 BAD_IN="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.in.bin" BAD_OUT="${TMP}.${GOOD_DEV##*/}.bad.${bad_counter}.out.bin" cp "${GOOD_DEV}" "${BAD_IN}" apply_xxd_patch "${BAD_FMAP_PREAMBLE_PATCHES[1]}" "${BAD_IN}" FUTIL_OUTPUT="$(if "${FUTILITY}" verify -P \ --publickey "${KEYDIR}/root_key.vbpubk" "${BAD_IN}"; \ then false; fi)" grep -q 'bios::VBLOCK_A::preamble::invalid' <<< "${FUTIL_OUTPUT}" FUTIL_OUTPUT="$(if "${FUTILITY}" sign \ -K "${KEYDIR}" \ "${BAD_IN}" "${BAD_OUT}" 2>&1; \ then false; fi)" m="$(grep -c -E \ -e 'VBLOCK_A is invalid\. Keyblock and preamble do not fit' \ -e 'Keyblock and preamble do not fit in VBLOCK' \ <<< "${FUTIL_OUTPUT}")" [ "${m}" = "2" ] : $(( bad_counter++ )) # cleanup rm -rf "${TMP}"* "${ONEMORE}" exit 0