Home | History | Annotate | Download | only in image_signing
      1 #!/bin/bash
      2 
      3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 # Sign the final build image using the "official" keys.
      8 #
      9 # Prerequisite tools needed in the system path:
     10 #
     11 #  gbb_utility (from src/platform/vboot_reference)
     12 #  vbutil_kernel (from src/platform/vboot_reference)
     13 #  cgpt (from src/platform/vboot_reference)
     14 #  dump_kernel_config (from src/platform/vboot_reference)
     15 #  verity (from src/platform/verity)
     16 #  load_kernel_test (from src/platform/vboot_reference)
     17 #  dumpe2fs
     18 #  sha1sum
     19 
     20 # Load common constants and variables.
     21 . "$(dirname "$0")/common.sh"
     22 
     23 # Print usage string
     24 usage() {
     25   cat <<EOF
     26 Usage: $PROG <type> input_image /path/to/keys/dir [output_image] [version_file]
     27 where <type> is one of:
     28              ssd  (sign an SSD image)
     29              recovery (sign a USB recovery image)
     30              factory (sign a factory install image)
     31              install (old alias to "factory")
     32              update_payload (sign a delta update hash)
     33              firmware (sign a firmware image)
     34              usb  (sign an image to boot directly from USB)
     35              verify (verify an image including rootfs hashes)
     36 
     37 output_image: File name of the signed output image
     38 version_file: File name of where to read the kernel and firmware versions.
     39 
     40 If you are signing an image, you must specify an [output_image] and
     41 optionally, a [version_file].
     42 
     43 EOF
     44   if [[ $# -gt 0 ]]; then
     45     error "$*"
     46     exit 1
     47   fi
     48   exit 0
     49 }
     50 
     51 # Verify we have as many arguments as we expect, else show usage & quit.
     52 # Usage:
     53 #  check_argc <number args> <exact number>
     54 #  check_argc <number args> <lower bound> <upper bound>
     55 check_argc() {
     56   case $# in
     57   2)
     58     if [[ $1 -ne $2 ]]; then
     59       usage "command takes exactly $2 args"
     60     fi
     61     ;;
     62   3)
     63     if [[ $1 -lt $2 || $1 -gt $3 ]]; then
     64       usage "command takes $2 to $3 args"
     65     fi
     66     ;;
     67   *)
     68     die "check_argc: incorrect number of arguments"
     69   esac
     70 }
     71 
     72 # Abort on errors.
     73 set -e
     74 
     75 # Add to the path since some tools reside here and may not be in the non-root
     76 # system path.
     77 PATH=$PATH:/usr/sbin:/sbin
     78 
     79 # Make sure the tools we need are available.
     80 for prereqs in gbb_utility vbutil_kernel cgpt dump_kernel_config verity \
     81   load_kernel_test dumpe2fs sha1sum e2fsck; do
     82   type -P "${prereqs}" &>/dev/null || \
     83     { echo "${prereqs} tool not found."; exit 1; }
     84 done
     85 
     86 TYPE=$1
     87 INPUT_IMAGE=$2
     88 KEY_DIR=$3
     89 OUTPUT_IMAGE=$4
     90 VERSION_FILE=$5
     91 
     92 FIRMWARE_VERSION=1
     93 KERNEL_VERSION=1
     94 
     95 # Get current rootfs hash and kernel command line
     96 # ARGS: IMAGE KERNELPART
     97 grab_kernel_config() {
     98   local image=$1
     99   local kernelpart=$2  # Kernel partition number to grab.
    100   # Grab the existing kernel partition and get the kernel config.
    101   temp_kimage=$(make_temp_file)
    102   extract_image_partition ${image} ${kernelpart} ${temp_kimage}
    103   dump_kernel_config ${temp_kimage}
    104 }
    105 
    106 # TODO(gauravsh): These are duplicated from chromeos-setimage. We need
    107 # to move all signing and rootfs code to one single place where it can be
    108 # reused. crosbug.com/19543
    109 
    110 # get_verity_arg <commandline> <key> -> <value>
    111 get_verity_arg() {
    112   echo "$1" | sed -n "s/.*\b$2=\([^ \"]*\).*/\1/p"
    113 }
    114 
    115 is_old_verity_argv() {
    116   local depth=$(echo "$1" | cut -f7 -d' ')
    117   if [ "$depth" = "0" ]; then
    118     return 0
    119   fi
    120   return 1
    121 }
    122 
    123 # Get the dmparams parameters from a kernel config.
    124 get_dmparams_from_config() {
    125   local kernel_config=$1
    126   echo ${kernel_config} | sed -nre 's/.*dm="([^"]*)".*/\1/p'
    127 }
    128 # Get the verity root digest hash from a kernel config command line.
    129 get_hash_from_config() {
    130   local kernel_config=$1
    131   local dm_config=$(get_dmparams_from_config "${kernel_config}")
    132   local vroot_dev=$(get_dm_slave "${dm_config}" vroot)
    133   if is_old_verity_argv "${vroot_dev}"; then
    134     echo ${vroot_dev} | cut -f9 -d ' '
    135   else
    136     echo $(get_verity_arg "${vroot_dev}" root_hexdigest)
    137   fi
    138 }
    139 
    140 # Get the slave device and its args
    141 # get_dm_ags $dm_config [vboot|vroot]
    142 # Assumes we have only one slave device per device
    143 get_dm_slave() {
    144   local dm=$1
    145   local device=$2
    146   echo $(echo "${dm}" | sed -nre "s/.*${device}[^,]*,([^,]*).*/\1/p")
    147 }
    148 
    149 # Set the slave device and its args for a device
    150 # get_dm_ags $dm_config [vboot|vroot] args
    151 # Assumes we have only one slave device per device
    152 set_dm_slave() {
    153   local dm=$1
    154   local device=$2
    155   local slave=$3
    156   echo $(echo "${dm}" |
    157     sed -nre "s#(.*${device}[^,]*,)([^,]*)(.*)#\1${slave}\3#p")
    158 }
    159 
    160 CALCULATED_KERNEL_CONFIG=
    161 CALCULATED_DM_ARGS=
    162 # Calculate rootfs hash of an image
    163 # Args: ROOTFS_IMAGE KERNEL_CONFIG HASH_IMAGE
    164 #
    165 # rootfs calculation parameters are grabbed from KERNEL_CONFIG
    166 #
    167 # Updated dm-verity arguments (to be replaced in kernel config command line)
    168 # with the new hash is stored in $CALCULATED_DM_ARGS and the new hash image is
    169 # written to the file HASH_IMAGE.
    170 calculate_rootfs_hash() {
    171   local rootfs_image=$1
    172   local kernel_config=$2
    173   local hash_image=$3
    174   local dm_config=$(get_dmparams_from_config "${kernel_config}")
    175 
    176   if [ -z "${dm_config}" ]; then
    177     echo "WARNING: Couldn't grab dm_config. Aborting rootfs hash calculation."
    178     return 1
    179   fi
    180   local vroot_dev=$(get_dm_slave "${dm_config}" vroot)
    181 
    182   local rootfs_sectors
    183   local verity_depth
    184   local verity_algorithm
    185   local root_dev
    186   local hash_dev
    187   local verity_bin="verity"
    188   if is_old_verity_argv "${vroot_dev}"; then
    189     # dm="0 2097152 verity ROOT_DEV HASH_DEV 2097152 1 \
    190     # sha1 63b7ad16cb9db4b70b28593f825aa6b7825fdcf2"
    191     rootfs_sectors=$(echo ${vroot_dev} | cut -f2 -d' ')
    192     verity_depth=$(echo ${vroot_dev} | cut -f7 -d' ')
    193     verity_algorithm=$(echo ${vroot_dev} | cut -f8 -d' ')
    194     root_dev=$(echo ${vroot_dev} | cut -f4 -d ' ')
    195     hash_dev=$(echo ${vroot_dev} | cut -f5 -d ' ')
    196     # Hack around the fact that the signer needs to use the old version of
    197     # verity to generate legacy verity kernel parameters. If we find it,
    198     # we use it.
    199     type -P "verity-old" &>/dev/null && verity_bin="verity-old"
    200   else
    201     # Key-value parameters.
    202     rootfs_sectors=$(get_verity_arg "${vroot_dev}" hashstart)
    203     verity_depth=0
    204     verity_algorithm=$(get_verity_arg "${vroot_dev}" alg)
    205     root_dev=$(get_verity_arg "${vroot_dev}" payload)
    206     hash_dev=$(get_verity_arg "${vroot_dev}" hashtree)
    207     salt=$(get_verity_arg "${vroot_dev}" salt)
    208   fi
    209 
    210   local salt_arg
    211   if [ -n "$salt" ]; then
    212     salt_arg="salt=$salt"
    213   fi
    214 
    215   # Run the verity tool on the rootfs partition.
    216   local slave=$(sudo ${verity_bin} mode=create \
    217     alg=${verity_algorithm} \
    218     payload="${rootfs_image}" \
    219     payload_blocks=$((rootfs_sectors / 8)) \
    220     hashtree="${hash_image}" ${salt_arg})
    221   # Reconstruct new kernel config command line and replace placeholders.
    222   slave="$(echo "${slave}" |
    223     sed -s "s|ROOT_DEV|${root_dev}|g;s|HASH_DEV|${hash_dev}|")"
    224   CALCULATED_DM_ARGS="$(set_dm_slave "${dm_config}" vroot "${slave}")"
    225   CALCULATED_KERNEL_CONFIG="$(echo "${kernel_config}" |
    226     sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${CALCULATED_DM_ARGS}\3#g")"
    227 }
    228 
    229 # Re-calculate rootfs hash, update rootfs and kernel command line(s).
    230 # Args: IMAGE DM_PARTNO KERN_A_KEYBLOCK KERN_A_PRIVKEY KERN_B_KEYBLOCK \
    231 #       KERN_B_PRIVKEY
    232 #
    233 # The rootfs is hashed by tool 'verity', and the hash data is stored after the
    234 # rootfs. A hash of those hash data (also known as final verity hash) may be
    235 # contained in kernel 2 or kernel 4 command line.
    236 #
    237 # This function reads dm-verity configuration from DM_PARTNO, rebuilds rootfs
    238 # hash, and then resigns kernel A & B by their keyblock and private key files.
    239 update_rootfs_hash() {
    240   local image=$1  # Input image.
    241   local dm_partno="$2"  # Partition number of kernel that contains verity args.
    242   local kern_a_keyblock="$3"  # Keyblock file for kernel A.
    243   local kern_a_privkey="$4"  # Private key file for kernel A.
    244   local kern_b_keyblock="$5"  # Keyblock file for kernel B.
    245   local kern_b_privkey="$6"  # Private key file for kernel A.
    246 
    247   # Note even though there are two kernels, there is one place (after rootfs)
    248   # for hash data, so we must assume both kernel use same hash algorithm (i.e.,
    249   # DM config).
    250   echo "Updating rootfs hash and updating config for Kernel partitions"
    251 
    252   # If we can't find dm parameters in the kernel config, bail out now.
    253   local kernel_config=$(grab_kernel_config "${image}" "${dm_partno}")
    254   local dm_config=$(get_dmparams_from_config "${kernel_config}")
    255   if [ -z "${dm_config}" ]; then
    256     echo "ERROR: Couldn't grab dm_config from kernel partition ${dm_partno}"
    257     echo " (config: ${kernel_config})"
    258     return 1
    259   fi
    260 
    261   # check and clear need_to_resign tag
    262   local rootfs_dir=$(make_temp_dir)
    263   mount_image_partition_ro "${image}" 3 "${rootfs_dir}"
    264   if has_needs_to_be_resigned_tag "${rootfs_dir}"; then
    265     # remount as RW
    266     sudo umount "${rootfs_dir}"
    267     mount_image_partition "${image}" 3 "${rootfs_dir}"
    268     sudo rm -f "${rootfs_dir}/${TAG_NEEDS_TO_BE_SIGNED}"
    269   fi
    270   sudo umount "${rootfs_dir}"
    271 
    272   local rootfs_image=$(make_temp_file)
    273   extract_image_partition ${image} 3 ${rootfs_image}
    274   local hash_image=$(make_temp_file)
    275 
    276   # Disable rw mount support prior to hashing.
    277   disable_rw_mount "${rootfs_image}"
    278 
    279   if ! calculate_rootfs_hash "${rootfs_image}"  "${kernel_config}" \
    280     "${hash_image}"; then
    281     echo "calculate_rootfs_hash failed!"
    282     echo "Aborting rootfs hash update!"
    283     return 1
    284   fi
    285 
    286   local rootfs_blocks=$(sudo dumpe2fs "${rootfs_image}" 2> /dev/null |
    287     grep "Block count" |
    288     tr -d ' ' |
    289     cut -f2 -d:)
    290   local rootfs_sectors=$((rootfs_blocks * 8))
    291 
    292   # Overwrite the appended hashes in the rootfs
    293   dd if=${hash_image} of=${rootfs_image} bs=512 \
    294     seek=${rootfs_sectors} conv=notrunc 2>/dev/null
    295   replace_image_partition ${image} 3 ${rootfs_image}
    296 
    297   # Update kernel command lines
    298   local dm_args="${CALCULATED_DM_ARGS}"
    299   local temp_config=$(make_temp_file)
    300   local temp_kimage=$(make_temp_file)
    301   local updated_kimage=$(make_temp_file)
    302   local kernelpart=
    303   local keyblock=
    304   local priv_key=
    305   local new_kernel_config=
    306 
    307   for kernelpart in 2 4; do
    308     if ! new_kernel_config="$(
    309          grab_kernel_config "${image}" "${kernelpart}" 2>/dev/null)" &&
    310        [[ "${kernelpart}" == 4 ]]; then
    311       # Legacy images don't have partition 4.
    312       echo "Skipping empty kernel partition 4 (legacy images)."
    313       continue
    314     fi
    315     new_kernel_config="$(echo "${new_kernel_config}" |
    316       sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${dm_args}\3#g")"
    317     echo "New config for kernel partition ${kernelpart} is:"
    318     echo "${new_kernel_config}" | tee "${temp_config}"
    319     extract_image_partition "${image}" "${kernelpart}" "${temp_kimage}"
    320     # Re-calculate kernel partition signature and command line.
    321     if [[ "$kernelpart" == 2 ]]; then
    322       keyblock="${kern_a_keyblock}"
    323       priv_key="${kern_a_privkey}"
    324     else
    325       keyblock="${kern_b_keyblock}"
    326       priv_key="${kern_b_privkey}"
    327     fi
    328     vbutil_kernel --repack ${updated_kimage} \
    329       --keyblock ${keyblock} \
    330       --signprivate ${priv_key} \
    331       --version "${KERNEL_VERSION}" \
    332       --oldblob ${temp_kimage} \
    333       --config ${temp_config}
    334     replace_image_partition ${image} ${kernelpart} ${updated_kimage}
    335   done
    336 }
    337 
    338 # Update the SSD install-able vblock file on stateful partition.
    339 # ARGS: Image
    340 # This is deprecated because all new images should have a SSD boot-able kernel
    341 # in partition 4. However, the signer needs to be able to sign new & old images
    342 # (crbug.com/449450#c13) so we will probably never remove this.
    343 update_stateful_partition_vblock() {
    344   local image="$1"
    345   local kernb_image="$(make_temp_file)"
    346   local temp_out_vb="$(make_temp_file)"
    347 
    348   extract_image_partition "${image}" 4 "${kernb_image}"
    349   if [[ "$(dump_kernel_config "${kernb_image}" 2>/dev/null)" == "" ]]; then
    350     echo "Building vmlinuz_hd.vblock from legacy image partition 2."
    351     extract_image_partition "${image}" 2 "${kernb_image}"
    352   fi
    353 
    354   # vblock should always use kernel keyblock.
    355   vbutil_kernel --repack "${temp_out_vb}" \
    356     --keyblock "${KEY_DIR}/kernel.keyblock" \
    357     --signprivate "${KEY_DIR}/kernel_data_key.vbprivk" \
    358     --oldblob "${kernb_image}" \
    359     --vblockonly
    360 
    361   # Copy the installer vblock to the stateful partition.
    362   local stateful_dir=$(make_temp_dir)
    363   mount_image_partition "${image}" 1 "${stateful_dir}"
    364   sudo cp ${temp_out_vb} ${stateful_dir}/vmlinuz_hd.vblock
    365   sudo umount "${stateful_dir}"
    366 }
    367 
    368 # Do a sanity check on the image's rootfs
    369 # ARGS: Image
    370 verify_image_rootfs() {
    371   local image=$1
    372   local rootfs_image=$(make_temp_file)
    373   extract_image_partition ${image} 3 ${rootfs_image}
    374   # This flips the read-only compatibility flag, so that e2fsck does not
    375   # complain about unknown file system capabilities.
    376   enable_rw_mount ${rootfs_image}
    377   echo "Running e2fsck to check root file system for errors"
    378   sudo e2fsck -fn "${rootfs_image}" ||
    379     { echo "Root file system has errors!" && exit 1;}
    380 }
    381 
    382 # Extracts a firmware updater bundle (for firmware image binaries) file
    383 # (generated by src/platform/firmware/pack_firmware.sh).
    384 # Args: INPUT_FILE OUTPUT_DIR
    385 extract_firmware_bundle() {
    386   local input="$(readlink -f "$1")"
    387   local output_dir="$2"
    388   if [ ! -s "${input}" ]; then
    389     return 1
    390   elif grep -q '^##CUTHERE##' "${input}"; then
    391     # Bundle supports self-extraction.
    392     "$input" --sb_extract "${output_dir}" ||
    393       die "Extracting firmware autoupdate (--sb_extract) failed."
    394   else
    395     # Legacy bundle - try uudecode.
    396     uudecode -o - ${input} | tar -C ${output_dir} -zxf - 2>/dev/null ||
    397       die "Extracting firmware autoupdate failed."
    398   fi
    399 }
    400 
    401 # Repacks firmware updater bundle content from given folder.
    402 # Args: INPUT_DIR TARGET_SCRIPT
    403 repack_firmware_bundle() {
    404   local input_dir="$1"
    405   local target="$(readlink -f "$2")"
    406 
    407   if [ ! -s "${target}" ]; then
    408     return 1
    409   elif grep -q '^##CUTHERE##' "${target}"; then
    410     # Bundle supports repacking.
    411     # Workaround issue crosbug.com/p/33719
    412     sed -i \
    413       's/shar -Q -q -x -m -w/shar -Q -q -x -m --no-character-count/' \
    414       "${target}"
    415     "$target" --sb_repack "${input_dir}" ||
    416       die "Updating firmware autoupdate (--sb_repack) failed."
    417   else
    418     # Legacy bundle using uuencode + tar.gz.
    419     # Replace MD5 checksum in the firmware update payload.
    420     local newfd_checksum="$(md5sum ${input_dir}/bios.bin | cut -f 1 -d ' ')"
    421     local temp_version="$(make_temp_file)"
    422     cat ${input_dir}/VERSION |
    423     sed -e "s#\(.*\)\ \(.*bios.bin.*\)#${newfd_checksum}\ \2#" > ${temp_version}
    424     mv ${temp_version} ${input_dir}/VERSION
    425 
    426     # Re-generate firmware_update.tgz and copy over encoded archive in
    427     # the original shell ball.
    428     sed -ine '/^begin .*firmware_package/,/end/D' "$target"
    429     tar zcf - -C "${input_dir}" . |
    430       uuencode firmware_package.tgz >>"${target}"
    431   fi
    432 }
    433 
    434 # Sign a firmware in-place with the given keys.
    435 # Args: FIRMWARE_IMAGE KEY_DIR FIRMWARE_VERSION [LOEM_OUTPUT_DIR]
    436 sign_firmware() {
    437   local image=$1
    438   local key_dir=$2
    439   local firmware_version=$3
    440   local loem_output_dir=${4:-}
    441 
    442   local temp_firmware=$(make_temp_file)
    443   # Resign the firmware with new keys, also replacing the root and recovery
    444   # public keys in the GBB.
    445   "${SCRIPT_DIR}/sign_firmware.sh" "${image}" "${key_dir}" "${temp_firmware}" \
    446     "${firmware_version}" "${loem_output_dir}"
    447   # Note: Although sign_firmware.sh may correctly handle specifying the same
    448   # output file as the input file, we do not want to rely on it correctly
    449   # handing that. Hence, the use of a temporary file.
    450   mv ${temp_firmware} ${image}
    451   echo "Signed firmware image output to ${image}"
    452 }
    453 
    454 # Sign a delta update payload (usually created by paygen).
    455 # Args: INPUT_IMAGE KEY_DIR OUTPUT_IMAGE
    456 sign_update_payload() {
    457   local image=$1
    458   local key_dir=$2
    459   local output=$3
    460   local key_size key_file="${key_dir}/update_key.pem"
    461   # Maps key size to verified boot's algorithm id (for pad_digest_utility).
    462   # Hashing algorithm is always SHA-256.
    463   local algo algos=(
    464     [1024]=1
    465     [2048]=4
    466     [4096]=7
    467     [8192]=10
    468   )
    469 
    470   key_size=$(openssl rsa -text -noout -in "${key_file}" | \
    471     sed -n -r '1{s/Private-Key: \(([0-9]*) bit\)/\1/p}')
    472   algo=${algos[${key_size}]}
    473   if [[ -z ${algo} ]]; then
    474     die "Unknown algorithm specified by key_size=${key_size}"
    475   fi
    476 
    477   pad_digest_utility ${algo} "${image}" | \
    478     openssl rsautl -sign -pkcs -inkey "${key_file}" -out "${output}"
    479 }
    480 
    481 # Re-sign the firmware AU payload inside the image rootfs with a new keys.
    482 # Args: IMAGE
    483 resign_firmware_payload() {
    484   local image=$1
    485 
    486   if [ -n "${NO_FWUPDATE}" ]; then
    487     echo "Skipping firmware update."
    488     return
    489   fi
    490 
    491   # Grab firmware image from the autoupdate bundle (shellball).
    492   local rootfs_dir=$(make_temp_dir)
    493   mount_image_partition ${image} 3 ${rootfs_dir}
    494   local firmware_bundle="${rootfs_dir}/usr/sbin/chromeos-firmwareupdate"
    495   local shellball_dir=$(make_temp_dir)
    496 
    497   # extract_firmware_bundle can fail if the image has no firmware update.
    498   if ! extract_firmware_bundle "${firmware_bundle}" "${shellball_dir}"; then
    499     # Unmount now to prevent changes.
    500     sudo umount "${rootfs_dir}"
    501     echo "Didn't find a firmware update. Not signing firmware."
    502     return
    503   fi
    504   echo "Found a valid firmware update shellball."
    505 
    506   local image_file sign_args=() loem_sfx loem_output_dir
    507   for image_file in "${shellball_dir}"/bios*.bin; do
    508     if [[ -e "${KEY_DIR}/loem.ini" ]]; then
    509       # Extract the extended details from "bios.bin" and use that in the
    510       # subdir for the keyset.
    511       loem_sfx=$(sed -r 's:.*/bios([^/]*)[.]bin$:\1:' <<<"${image_file}")
    512       loem_output_dir="${shellball_dir}/keyset${loem_sfx}"
    513       sign_args=( "${loem_output_dir}" )
    514       mkdir -p "${loem_output_dir}"
    515     fi
    516     sign_firmware "${image_file}" "${KEY_DIR}" "${FIRMWARE_VERSION}" \
    517       "${sign_args[@]}"
    518   done
    519 
    520   local signer_notes="${shellball_dir}/VERSION.signer"
    521   echo "" >"$signer_notes"
    522   echo "Signed with keyset in $(readlink -f "${KEY_DIR}") ." >>"$signer_notes"
    523 
    524   new_shellball=$(make_temp_file)
    525   cp -f "${firmware_bundle}" "${new_shellball}"
    526   chmod a+rx "${new_shellball}"
    527   repack_firmware_bundle "${shellball_dir}" "${new_shellball}"
    528   sudo cp -f "${new_shellball}" "${firmware_bundle}"
    529   sudo chmod a+rx "${firmware_bundle}"
    530   # Unmount now to flush changes.
    531   sudo umount "${rootfs_dir}"
    532   echo "Re-signed firmware AU payload in $image"
    533 }
    534 
    535 # Verify an image including rootfs hash using the specified keys.
    536 verify_image() {
    537   local rootfs_image=$(make_temp_file)
    538   extract_image_partition ${INPUT_IMAGE} 3 ${rootfs_image}
    539 
    540   echo "Verifying RootFS hash..."
    541   # What we get from image.
    542   local kernel_config
    543   # What we calculate from the rootfs.
    544   local new_kernel_config
    545   # Depending on the type of image, the verity parameters may
    546   # exist in either kernel partition 2 or kernel partition 4
    547   local partnum
    548   for partnum in 2 4; do
    549     echo "Considering Kernel partition $partnum"
    550     kernel_config=$(grab_kernel_config ${INPUT_IMAGE} $partnum)
    551     local hash_image=$(make_temp_file)
    552     if ! calculate_rootfs_hash "${rootfs_image}" "${kernel_config}" \
    553       "${hash_image}"; then
    554       echo "Trying next kernel partition."
    555       continue
    556     fi
    557     new_kernel_config="$CALCULATED_KERNEL_CONFIG"
    558     break
    559   done
    560 
    561   # Note: If calculate_rootfs_hash succeeded above, these should
    562   # be non-empty.
    563   expected_hash=$(get_hash_from_config "${new_kernel_config}")
    564   got_hash=$(get_hash_from_config "${kernel_config}")
    565 
    566   if [ -z "${expected_hash}" ] || [ -z "${got_hash}" ]; then
    567     echo "FAILURE: Couldn't verify RootFS hash on the image."
    568     exit 1
    569   fi
    570 
    571   if [ ! "${got_hash}" = "${expected_hash}" ]; then
    572     cat <<EOF
    573 FAILED: RootFS hash is incorrect.
    574 Expected: ${expected_hash}
    575 Got: ${got_hash}
    576 EOF
    577     exit 1
    578   else
    579     echo "PASS: RootFS hash is correct (${expected_hash})"
    580   fi
    581 
    582   # Now try and verify kernel partition signature.
    583   set +e
    584   local try_key=${KEY_DIR}/recovery_key.vbpubk
    585   echo "Testing key verification..."
    586   # The recovery key is only used in the recovery mode.
    587   echo -n "With Recovery Key (Recovery Mode ON, Dev Mode OFF): " && \
    588   { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 2 >/dev/null 2>&1 && \
    589     echo "YES"; } || echo "NO"
    590   echo -n "With Recovery Key (Recovery Mode ON, Dev Mode ON): " && \
    591   { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 3 >/dev/null 2>&1 && \
    592     echo "YES"; } || echo "NO"
    593 
    594   try_key=${KEY_DIR}/kernel_subkey.vbpubk
    595   # The SSD key is only used in non-recovery mode.
    596   echo -n "With SSD Key (Recovery Mode OFF, Dev Mode OFF): " && \
    597   { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 0 >/dev/null 2>&1  && \
    598     echo "YES"; } || echo "NO"
    599   echo -n "With SSD Key (Recovery Mode OFF, Dev Mode ON): " && \
    600   { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 1 >/dev/null 2>&1 && \
    601     echo "YES"; } || echo "NO"
    602   set -e
    603 
    604   verify_image_rootfs "${INPUT_IMAGE}"
    605 
    606   # TODO(gauravsh): Check embedded firmware AU signatures.
    607 }
    608 
    609 # Re-calculate recovery kernel hash.
    610 # Args: IMAGE_BIN
    611 update_recovery_kernel_hash() {
    612   image_bin=$1
    613 
    614   # Update the Kernel B hash in Kernel A command line
    615   local old_kerna_config=$(grab_kernel_config "${image_bin}" 2)
    616   local new_kernb=$(make_temp_file)
    617   extract_image_partition ${image_bin} 4 ${new_kernb}
    618   local new_kernb_hash=$(sha1sum ${new_kernb} | cut -f1 -d' ')
    619 
    620   new_kerna_config=$(make_temp_file)
    621   echo "$old_kerna_config" |
    622     sed -e "s#\(kern_b_hash=\)[a-z0-9]*#\1${new_kernb_hash}#" \
    623       > ${new_kerna_config}
    624   echo "New config for kernel partition 2 is"
    625   cat ${new_kerna_config}
    626 
    627   local temp_kimagea=$(make_temp_file)
    628   extract_image_partition ${image_bin} 2 ${temp_kimagea}
    629 
    630   # Re-calculate kernel partition signature and command line.
    631   local updated_kimagea=$(make_temp_file)
    632   vbutil_kernel --repack ${updated_kimagea} \
    633     --keyblock ${KEY_DIR}/recovery_kernel.keyblock \
    634     --signprivate ${KEY_DIR}/recovery_kernel_data_key.vbprivk \
    635     --version "${KERNEL_VERSION}" \
    636     --oldblob ${temp_kimagea} \
    637     --config ${new_kerna_config}
    638 
    639   replace_image_partition ${image_bin} 2 ${updated_kimagea}
    640 }
    641 
    642 # Sign an image file with proper keys.
    643 # Args: IMAGE_TYPE INPUT OUTPUT DM_PARTNO KERN_A_KEYBLOCK KERN_A_PRIVKEY \
    644 #       KERN_B_KEYBLOCK KERN_B_PRIVKEY
    645 #
    646 # A ChromiumOS image file (INPUT) always contains 2 partitions (kernel A & B).
    647 # This function will rebuild hash data by DM_PARTNO, resign kernel partitions by
    648 # their KEYBLOCK and PRIVKEY files, and then write to OUTPUT file. Note some
    649 # special images (specified by IMAGE_TYPE, like 'recovery' or 'factory_install')
    650 # may have additional steps (ex, tweaking verity hash or not stripping files)
    651 # when generating output file.
    652 sign_image_file() {
    653   local image_type="$1"
    654   local input="$2"
    655   local output="$3"
    656   local dm_partno="$4"
    657   local kernA_keyblock="$5"
    658   local kernA_privkey="$6"
    659   local kernB_keyblock="$7"
    660   local kernB_privkey="$8"
    661   echo "Preparing ${image_type} image..."
    662   cp "${input}" "${output}"
    663   resign_firmware_payload "${output}"
    664   # We do NOT strip /boot for factory installer, since some devices need it to
    665   # boot EFI. crbug.com/260512 would obsolete this requirement.
    666   if [[ "${image_type}" != "factory_install" ]]; then
    667     "${SCRIPT_DIR}/strip_boot_from_image.sh" --image "${output}"
    668   fi
    669   update_rootfs_hash "${output}" "${dm_partno}" \
    670     "${kernA_keyblock}" "${kernA_privkey}" \
    671     "${kernB_keyblock}" "${kernB_privkey}"
    672   update_stateful_partition_vblock "${output}"
    673   if [[ "${image_type}" == "recovery" ]]; then
    674     update_recovery_kernel_hash "${output}"
    675   fi
    676   echo "Signed ${image_type} image output to ${output}"
    677 }
    678 
    679 # Verification
    680 case ${TYPE} in
    681 dump_config)
    682   check_argc $# 2
    683   for partnum in 2 4; do
    684     echo "kernel config in partition number ${partnum}:"
    685     grab_kernel_config "${INPUT_IMAGE}" ${partnum}
    686     echo
    687   done
    688   exit 0
    689   ;;
    690 verify)
    691   check_argc $# 2
    692   verify_image
    693   exit 0
    694   ;;
    695 *)
    696   # All other signing commands take 4 to 5 args.
    697   if [ -z "${OUTPUT_IMAGE}" ]; then
    698     # Friendlier message.
    699     usage "Missing output image name"
    700   fi
    701   check_argc $# 4 5
    702   ;;
    703 esac
    704 
    705 # If a version file was specified, read the firmware and kernel
    706 # versions from there.
    707 if [ -n "${VERSION_FILE}" ]; then
    708   FIRMWARE_VERSION=$(sed -n 's#^firmware_version=\(.*\)#\1#pg' ${VERSION_FILE})
    709   KERNEL_VERSION=$(sed -n 's#^kernel_version=\(.*\)#\1#pg' ${VERSION_FILE})
    710 fi
    711 echo "Using firmware version: ${FIRMWARE_VERSION}"
    712 echo "Using kernel version: ${KERNEL_VERSION}"
    713 
    714 # Make all modifications on output copy.
    715 if [[ "${TYPE}" == "ssd" ]]; then
    716   sign_image_file "SSD" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 2 \
    717     "${KEY_DIR}/kernel.keyblock" "${KEY_DIR}/kernel_data_key.vbprivk" \
    718     "${KEY_DIR}/kernel.keyblock" "${KEY_DIR}/kernel_data_key.vbprivk"
    719 elif [[ "${TYPE}" == "usb" ]]; then
    720   sign_image_file "USB" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 2 \
    721     "${KEY_DIR}/recovery_kernel.keyblock" \
    722     "${KEY_DIR}/recovery_kernel_data_key.vbprivk" \
    723     "${KEY_DIR}/kernel.keyblock" \
    724     "${KEY_DIR}/kernel_data_key.vbprivk"
    725 elif [[ "${TYPE}" == "recovery" ]]; then
    726   sign_image_file "recovery" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 4 \
    727     "${KEY_DIR}/recovery_kernel.keyblock" \
    728     "${KEY_DIR}/recovery_kernel_data_key.vbprivk" \
    729     "${KEY_DIR}/kernel.keyblock" \
    730     "${KEY_DIR}/kernel_data_key.vbprivk"
    731 elif [[ "${TYPE}" == "factory" ]] || [[ "${TYPE}" == "install" ]]; then
    732   sign_image_file "factory_install" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 2 \
    733     "${KEY_DIR}/installer_kernel.keyblock" \
    734     "${KEY_DIR}/installer_kernel_data_key.vbprivk" \
    735     "${KEY_DIR}/kernel.keyblock" \
    736     "${KEY_DIR}/kernel_data_key.vbprivk"
    737 elif [[ "${TYPE}" == "firmware" ]]; then
    738   if [[ -e "${KEY_DIR}/loem.ini" ]]; then
    739     echo "LOEM signing not implemented yet for firmware images"
    740     exit 1
    741   fi
    742   cp ${INPUT_IMAGE} ${OUTPUT_IMAGE}
    743   sign_firmware ${OUTPUT_IMAGE} ${KEY_DIR} ${FIRMWARE_VERSION}
    744 elif [[ "${TYPE}" == "update_payload" ]]; then
    745   sign_update_payload ${INPUT_IMAGE} ${KEY_DIR} ${OUTPUT_IMAGE}
    746 else
    747   echo "Invalid type ${TYPE}"
    748   exit 1
    749 fi
    750