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
24usage() {
25  cat <<EOF
26Usage: $PROG <type> input_image /path/to/keys/dir [output_image] [version_file]
27where <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
37output_image: File name of the signed output image
38version_file: File name of where to read the kernel and firmware versions.
39
40If you are signing an image, you must specify an [output_image] and
41optionally, a [version_file].
42
43EOF
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>
55check_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.
73set -e
74
75# Add to the path since some tools reside here and may not be in the non-root
76# system path.
77PATH=$PATH:/usr/sbin:/sbin
78
79# Make sure the tools we need are available.
80for 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; }
84done
85
86TYPE=$1
87INPUT_IMAGE=$2
88KEY_DIR=$3
89OUTPUT_IMAGE=$4
90VERSION_FILE=$5
91
92FIRMWARE_VERSION=1
93KERNEL_VERSION=1
94
95# Get current rootfs hash and kernel command line
96# ARGS: IMAGE KERNELPART
97grab_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>
111get_verity_arg() {
112  echo "$1" | sed -n "s/.*\b$2=\([^ \"]*\).*/\1/p"
113}
114
115is_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.
124get_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.
129get_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
143get_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
152set_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
160CALCULATED_KERNEL_CONFIG=
161CALCULATED_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.
170calculate_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.
239update_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.
343update_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
370verify_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
385extract_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
403repack_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]
436sign_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
456sign_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
483resign_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.
536verify_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
573FAILED: RootFS hash is incorrect.
574Expected: ${expected_hash}
575Got: ${got_hash}
576EOF
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
611update_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.
652sign_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
680case ${TYPE} in
681dump_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  ;;
690verify)
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  ;;
703esac
704
705# If a version file was specified, read the firmware and kernel
706# versions from there.
707if [ -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})
710fi
711echo "Using firmware version: ${FIRMWARE_VERSION}"
712echo "Using kernel version: ${KERNEL_VERSION}"
713
714# Make all modifications on output copy.
715if [[ "${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"
719elif [[ "${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"
725elif [[ "${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"
731elif [[ "${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"
737elif [[ "${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}
744elif [[ "${TYPE}" == "update_payload" ]]; then
745  sign_update_payload ${INPUT_IMAGE} ${KEY_DIR} ${OUTPUT_IMAGE}
746else
747  echo "Invalid type ${TYPE}"
748  exit 1
749fi
750