xref: /aosp_15_r20/external/vboot_reference/scripts/image_signing/sign_official_build.sh (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
1#!/bin/bash
2
3# Copyright 2011 The ChromiumOS Authors
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#  futility (from src/platform/vboot_reference)
12#  verity (from src/platform2/verity)
13#  load_kernel_test (from src/platform/vboot_reference)
14#  dumpe2fs
15#  e2fsck
16#  sha1sum
17
18# Load common constants and variables.
19. "$(dirname "$0")/common.sh"
20. "$(dirname "$0")/lib/keycfg.sh"
21
22# Abort on errors.
23set -e
24
25# Our random local constants.
26MINIOS_KERNEL_GUID="09845860-705F-4BB5-B16C-8A8A099CAF52"
27FIRMWARE_VERSION=1
28KERNEL_VERSION=1
29V1_SUFFIX=".v1"
30
31# Print usage string
32usage() {
33  cat <<EOF
34Usage: ${PROG} <type> input_image /path/to/keys/dir [output_image] \
35[version_file] [--cloud-signing]
36where <type> is one of:
37             base (sign a base image)
38             recovery (sign a USB recovery image)
39             factory (sign a factory install image)
40             update_payload (sign a delta update hash)
41             firmware (sign a firmware image)
42             verify (verify an image including rootfs hashes)
43             accessory_usbpd (sign USB-PD accessory firmware)
44             accessory_rwsig (sign accessory RW firmware)
45             gsc_firmware (sign a GSC firmware image)
46             uefi_kernel (sign a UEFI kernel image)
47
48output_image: File name of the signed output image
49version_file: File name of where to read the kernel and firmware versions.
50--cloud-signing: Instead of relying on a local key directory, retrieve keys
51  from Cloud KMS.
52--debug: Show more information for debugging purpose.
53
54If you are signing an image, you must specify an [output_image] and
55optionally, a [version_file].
56
57EOF
58  if [[ $# -gt 0 ]]; then
59    error "$*"
60    exit 1
61  fi
62  exit 0
63}
64
65# Verify we have as many arguments as we expect, else show usage & quit.
66# Usage:
67#  check_argc <number args> <exact number>
68#  check_argc <number args> <lower bound> <upper bound>
69check_argc() {
70  case $# in
71  2)
72    if [[ $1 -ne $2 ]]; then
73      usage "command takes exactly $2 args"
74    fi
75    ;;
76  3)
77    if [[ $1 -lt $2 || $1 -gt $3 ]]; then
78      usage "command takes $2 to $3 args"
79    fi
80    ;;
81  *)
82    die "check_argc: incorrect number of arguments"
83  esac
84}
85
86# Run futility as root with some preserved environment variables.
87sudo_futility() {
88  sudo "KMS_PKCS11_CONFIG=${KMS_PKCS11_CONFIG}" "${FUTILITY}" ${FUTILITY_EXTRA_FLAGS} "$@"
89}
90
91do_futility() {
92  "${FUTILITY}" ${FUTILITY_EXTRA_FLAGS} "$@"
93}
94
95# TODO(gauravsh): These are duplicated from chromeos-setimage. We need
96# to move all signing and rootfs code to one single place where it can be
97# reused. crosbug.com/19543
98
99# get_verity_arg <commandline> <key> -> <value>
100get_verity_arg() {
101  echo "$1" | sed -n "s/.*\b$2=\([^ \"]*\).*/\1/p"
102}
103
104# Get the dmparams parameters from a kernel config.
105get_dmparams_from_config() {
106  local kernel_config=$1
107  echo "${kernel_config}" | sed -nre 's/.*dm="([^"]*)".*/\1/p'
108}
109# Get the verity root digest hash from a kernel config command line.
110get_hash_from_config() {
111  local kernel_config=$1
112  local dm_config
113  dm_config=$(get_dmparams_from_config "${kernel_config}")
114  local vroot_dev
115  vroot_dev=$(get_dm_device "${dm_config}" vroot)
116  get_verity_arg "${vroot_dev}" root_hexdigest
117}
118
119# Get the mapped device and its args.
120# Usage:
121#   get_dm_device $dm_config [vboot|vroot]
122# Assumes we have only one mapped device per device.
123get_dm_device() {
124  local dm=$1
125  local device=$2
126  echo "${dm}" | sed -nre "s/.*${device}[^,]*,([^,]*).*/\1/p"
127}
128
129# Set the mapped device and its args for a device.
130# Usage:
131#   set_dm_device $dm_config [vboot|vroot] args
132# Assumes we have only one mapped device per device.
133set_dm_device() {
134  local dm=$1
135  local device=$2
136  local args=$3
137  echo "${dm}" | sed -nre "s#(.*${device}[^,]*,)([^,]*)(.*)#\1${args}\3#p"
138}
139
140CALCULATED_KERNEL_CONFIG=
141CALCULATED_DM_ARGS=
142# Calculate rootfs hash of an image
143# Args: ROOTFS_IMAGE KERNEL_CONFIG HASH_IMAGE
144#
145# rootfs calculation parameters are grabbed from KERNEL_CONFIG
146#
147# Updated dm-verity arguments (to be replaced in kernel config command line)
148# with the new hash is stored in $CALCULATED_DM_ARGS and the new hash image is
149# written to the file HASH_IMAGE.
150calculate_rootfs_hash() {
151  local rootfs_image=$1
152  local kernel_config=$2
153  local hash_image=$3
154  local dm_config
155  dm_config=$(get_dmparams_from_config "${kernel_config}")
156
157  if [ -z "${dm_config}" ]; then
158    warn "Couldn't grab dm_config. Aborting rootfs hash calculation."
159    return 1
160  fi
161  local vroot_dev
162  vroot_dev=$(get_dm_device "${dm_config}" vroot)
163
164  # Extract the key-value parameters from the kernel command line.
165  local rootfs_sectors
166  rootfs_sectors=$(get_verity_arg "${vroot_dev}" hashstart)
167  local verity_algorithm
168  verity_algorithm=$(get_verity_arg "${vroot_dev}" alg)
169  local root_dev
170  root_dev=$(get_verity_arg "${vroot_dev}" payload)
171  local hash_dev
172  hash_dev=$(get_verity_arg "${vroot_dev}" hashtree)
173  local salt
174  salt=$(get_verity_arg "${vroot_dev}" salt)
175
176  local salt_arg
177  if [ -n "${salt}" ]; then
178    salt_arg="salt=${salt}"
179  fi
180
181  # Run the verity tool on the rootfs partition.
182  local table
183  table=$(sudo verity mode=create \
184    alg="${verity_algorithm}" \
185    payload="${rootfs_image}" \
186    payload_blocks=$((rootfs_sectors / 8)) \
187    hashtree="${hash_image}" "${salt_arg}")
188  # Reconstruct new kernel config command line and replace placeholders.
189  table="$(echo "${table}" |
190    sed -s "s|ROOT_DEV|${root_dev}|g;s|HASH_DEV|${hash_dev}|")"
191  CALCULATED_DM_ARGS="$(set_dm_device "${dm_config}" vroot "${table}")"
192  # shellcheck disable=SC2001
193  CALCULATED_KERNEL_CONFIG="$(echo "${kernel_config}" |
194    sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${CALCULATED_DM_ARGS}\3#g")"
195}
196
197# Re-calculate rootfs hash, update rootfs and kernel command line(s).
198# Args: LOOPDEV KERNEL \
199#       KERN_A_KEYBLOCK KERN_A_PRIVKEY \
200#       KERN_B_KEYBLOCK KERN_B_PRIVKEY SHOULD_SIGN_KERN_B \
201#       KERN_C_KEYBLOCK KERN_C_PRIVKEY SHOULD_SIGN_KERN_C
202#
203# The rootfs is hashed by tool 'verity', and the hash data is stored after the
204# rootfs. A hash of those hash data (also known as final verity hash) may be
205# contained in kernel 2 or kernel 4 command line.
206#
207# This function reads dm-verity configuration from KERNEL, rebuilds the rootfs
208# hash, and then resigns kernel A & B (& C if needed) by their keyblock and
209# private key files.
210update_rootfs_hash() {
211  local loopdev="$1"  # Input image.
212  local loop_kern="$2"  # Kernel that contains verity args.
213  local kern_a_keyblock="$3"  # Keyblock file for kernel A.
214  local kern_a_privkey="$4"  # Private key file for kernel A.
215  local kern_b_keyblock="$5"  # Keyblock file for kernel B.
216  local kern_b_privkey="$6"  # Private key file for kernel B.
217  local should_sign_kern_b="$7"
218  local kern_c_keyblock="$8"  # Keyblock file for kernel C.
219  local kern_c_privkey="$9"  # Private key file for kernel C.
220  local should_sign_kern_c="${10}"
221  local loop_rootfs="${loopdev}p3"
222
223  # Note even though there are two kernels, there is one place (after rootfs)
224  # for hash data, so we must assume both kernel use same hash algorithm (i.e.,
225  # DM config).
226  info "Updating rootfs hash and updating config for Kernel partitions"
227
228  # If we can't find dm parameters in the kernel config, bail out now.
229  local kernel_config
230  kernel_config=$(sudo_futility dump_kernel_config "${loop_kern}")
231  local dm_config
232  dm_config=$(get_dmparams_from_config "${kernel_config}")
233  if [ -z "${dm_config}" ]; then
234    error "Couldn't grab dm_config from kernel ${loop_kern}"
235    error " (config: ${kernel_config})"
236    return 1
237  fi
238
239  # check and clear need_to_resign tag
240  local rootfs_dir
241  rootfs_dir=$(make_temp_dir)
242  sudo mount -o ro "${loop_rootfs}" "${rootfs_dir}"
243  if has_needs_to_be_resigned_tag "${rootfs_dir}"; then
244    # remount as RW
245    sudo mount -o remount,rw "${rootfs_dir}"
246    sudo rm -f "${rootfs_dir}/${TAG_NEEDS_TO_BE_SIGNED}"
247  fi
248  sudo umount "${rootfs_dir}"
249
250  local hash_image
251  hash_image=$(make_temp_file)
252
253  # Disable rw mount support prior to hashing.
254  disable_rw_mount "${loop_rootfs}"
255
256  if ! calculate_rootfs_hash "${loop_rootfs}"  "${kernel_config}" \
257    "${hash_image}"; then
258    error "calculate_rootfs_hash failed!"
259    error "Aborting rootfs hash update!"
260    return 1
261  fi
262
263  local rootfs_blocks
264  rootfs_blocks=$(sudo dumpe2fs "${loop_rootfs}" 2> /dev/null |
265    grep "Block count" |
266    tr -d ' ' |
267    cut -f2 -d:)
268  local rootfs_sectors=$((rootfs_blocks * 8))
269
270  # Overwrite the appended hashes in the rootfs
271  sudo dd if="${hash_image}" of="${loop_rootfs}" bs=512 \
272    seek="${rootfs_sectors}" conv=notrunc 2>/dev/null
273
274  # Update kernel command lines
275  local dm_args="${CALCULATED_DM_ARGS}"
276  local temp_config
277  temp_config=$(make_temp_file)
278  local kernelpart=
279  local keyblock=
280  local priv_key=
281  local new_kernel_config=
282
283  for kernelpart in 2 4 6; do
284    loop_kern="${loopdev}p${kernelpart}"
285    if ! new_kernel_config="$(
286         sudo_futility dump_kernel_config "${loop_kern}" 2>/dev/null)" &&
287       [[ "${kernelpart}" == 4 ]]; then
288      # Legacy images don't have partition 4.
289      info "Skipping empty kernel partition 4 (legacy images)."
290      continue
291    fi
292    if [[ "${should_sign_kern_b}" == "false" && "${kernelpart}" == 4 ]]; then
293      info "Skip signing kernel B."
294      continue
295    fi
296    if [[ "${should_sign_kern_c}" == "false" && "${kernelpart}" == 6 ]]; then
297      info "Skip signing kernel C."
298      continue
299    fi
300    # shellcheck disable=SC2001
301    new_kernel_config="$(echo "${new_kernel_config}" |
302      sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${dm_args}\3#g")"
303    info "New config for kernel partition ${kernelpart} is:"
304    echo "${new_kernel_config}" | tee "${temp_config}"
305    # Re-calculate kernel partition signature and command line.
306    if [[ "${kernelpart}" == 2 ]]; then
307      keyblock="${kern_a_keyblock}"
308      priv_key="${kern_a_privkey}"
309    elif [[ "${kernelpart}" == 4 ]]; then
310      keyblock="${kern_b_keyblock}"
311      priv_key="${kern_b_privkey}"
312    else
313      keyblock="${kern_c_keyblock}"
314      priv_key="${kern_c_privkey}"
315    fi
316    sudo_futility vbutil_kernel --repack "${loop_kern}" \
317      --keyblock "${keyblock}" \
318      --signprivate "${priv_key}" \
319      --version "${KERNEL_VERSION}" \
320      --oldblob "${loop_kern}" \
321      --config "${temp_config}"
322  done
323}
324
325# Update the SSD install-able vblock file on stateful partition.
326# ARGS: Loopdev
327# This is deprecated because all new images should have a SSD boot-able kernel
328# in partition 4. However, the signer needs to be able to sign new & old images
329# (crbug.com/449450#c13) so we will probably never remove this.
330update_stateful_partition_vblock() {
331  local loopdev="$1"
332  local temp_out_vb
333  temp_out_vb="$(make_temp_file)"
334
335  local loop_kern="${loopdev}p4"
336  if [[ -z "$(sudo_futility dump_kernel_config "${loop_kern}" \
337        2>/dev/null)" ]]; then
338    info "Building vmlinuz_hd.vblock from legacy image partition 2."
339    loop_kern="${loopdev}p2"
340  fi
341
342  # vblock should always use kernel keyblock.
343  sudo_futility vbutil_kernel --repack "${temp_out_vb}" \
344    --keyblock "${KEYCFG_KERNEL_KEYBLOCK}" \
345    --signprivate "${KEYCFG_KERNEL_VBPRIVK}" \
346    --oldblob "${loop_kern}" \
347    --vblockonly
348
349  # Copy the installer vblock to the stateful partition.
350  local stateful_dir
351  stateful_dir=$(make_temp_dir)
352  sudo mount "${loopdev}p1" "${stateful_dir}"
353  sudo cp "${temp_out_vb}" "${stateful_dir}"/vmlinuz_hd.vblock
354  sudo umount "${stateful_dir}"
355}
356
357# Do a validity check on the image's rootfs
358# ARGS: Image
359verify_image_rootfs() {
360  local rootfs=$1
361  # This flips the read-only compatibility flag, so that e2fsck does not
362  # complain about unknown file system capabilities.
363  enable_rw_mount "${rootfs}"
364  info "Running e2fsck to check root file system for errors"
365  sudo e2fsck -fn "${rootfs}" ||
366    die "Root file system has errors!"
367  # Flip the bit back so we don't break hashes.
368  disable_rw_mount "${rootfs}"
369}
370
371# Repacks firmware updater bundle content from given folder.
372# Args: INPUT_DIR TARGET_SCRIPT
373repack_firmware_bundle() {
374  local input_dir="$1"
375  local target
376  target="$(readlink -f "$2")"
377
378  if [ ! -s "${target}" ]; then
379    return 1
380  elif grep -q '^##CUTHERE##' "${target}"; then
381    # Bundle supports repacking (--repack, --sb_repack)
382    # Workaround issue crosbug.com/p/33719
383    sed -i \
384      's/shar -Q -q -x -m -w/shar -Q -q -x -m --no-character-count/' \
385      "${target}"
386    "${target}" --repack "${input_dir}" ||
387      "${target}" --sb_repack "${input_dir}" ||
388        die "Updating firmware autoupdate (--repack) failed."
389  else
390    # Legacy bundle using uuencode + tar.gz.
391    # Replace MD5 checksum in the firmware update payload.
392    local newfd_checksum
393    newfd_checksum="$(md5sum "${input_dir}"/bios.bin | cut -f 1 -d ' ')"
394    local temp_version
395    temp_version="$(make_temp_file)"
396    cat "${input_dir}"/VERSION |
397    sed -e "s#\(.*\)\ \(.*bios.bin.*\)#${newfd_checksum}\ \2#" > \
398      "${temp_version}"
399    mv "${temp_version}" "${input_dir}"/VERSION
400
401    # Re-generate firmware_update.tgz and copy over encoded archive in
402    # the original shell ball.
403    sed -ine '/^begin .*firmware_package/,/end/D' "${target}"
404    tar zcf - -C "${input_dir}" . |
405      uuencode firmware_package.tgz >>"${target}"
406  fi
407}
408
409# Sign a firmware in-place with the given keys.
410# Args: FIRMWARE_IMAGE KEY_DIR FIRMWARE_VERSION [LOEM_OUTPUT_DIR]
411sign_firmware() {
412  local image=$1
413  local key_dir=$2
414  local firmware_version=$3
415  local loem_output_dir=${4:-}
416
417  # Resign the firmware with new keys, also replacing the root and recovery
418  # public keys in the GBB.
419  "${SCRIPT_DIR}/sign_firmware.sh" "${image}" "${key_dir}" "${image}" \
420    "${firmware_version}" "${loem_output_dir}"
421  info "Signed firmware image output to ${image}"
422}
423
424# Sign a delta update payload (usually created by paygen).
425# Args: INPUT_IMAGE KEY_DIR OUTPUT_IMAGE
426sign_update_payload() {
427  local image=$1
428  local key_info=$2
429  local output=$3
430  local key_output key_size
431
432  if [[ "${key_info}" == "remote:"* ]]; then
433    # get label from key_info with format "remote:<libkmsp11.so>:<slot>:<label>"
434    IFS=":" read -r -a parsed_info <<< "${key_info}"
435    if [[ "${#parsed_info[@]}" -ne 4 ]]; then
436      die "Failed to parse key info '${key_info}'"
437    fi
438    local p11_module="${parsed_info[1]}"
439    # We omit slot because the pkeyutl command will look in every keyring in the
440    # config file for the named key.
441    local key_label="${parsed_info[3]}"
442    # Hashing algorithm is always SHA-256.
443    PKCS11_MODULE_PATH="${p11_module}" openssl pkeyutl -pkeyopt \
444      rsa_padding_mode:pkcs1 -pkeyopt digest:sha256 -engine pkcs11 \
445      -keyform engine -sign --inkey "pkcs11:object=${key_label}" \
446      -in "${image}" -out "${output}"
447    return
448  fi
449  # Strip the prefix "local:"
450  key_info="${key_info#local:}"
451  # Maps key size to verified boot's algorithm id (for pad_digest_utility).
452  # Hashing algorithm is always SHA-256.
453  local algo algos=(
454    [1024]=1
455    [2048]=4
456    [4096]=7
457    [8192]=10
458  )
459
460  key_output=$(do_futility show "${key_info}")
461  key_size=$(echo "${key_output}" | sed -n '/Key length/s/[^0-9]*//p')
462  algo=${algos[${key_size}]}
463  if [[ -z ${algo} ]]; then
464    die "Unknown algorithm: futility output=${key_output}"
465  fi
466
467  pad_digest_utility "${algo}" "${image}" | \
468    openssl rsautl -sign -pkcs -inkey "${key_info}" -out "${output}"
469}
470
471# Re-sign the firmware AU payload inside the image rootfs with a new keys.
472# Args: LOOPDEV
473resign_firmware_payload() {
474  local loopdev="$1"
475
476  if [ -n "${NO_FWUPDATE}" ]; then
477    info "Skipping firmware update."
478    return
479  fi
480
481  # Grab firmware image from the autoupdate bundle (shellball).
482  local rootfs_dir
483  rootfs_dir=$(make_temp_dir)
484  mount_loop_image_partition "${loopdev}" 3 "${rootfs_dir}"
485
486  local ret=0
487  resign_firmware_shellball "${rootfs_dir}/usr/sbin/chromeos-firmwareupdate" || ret=$?
488  sudo umount "${rootfs_dir}"
489  if [[ "${ret}" == 0 ]]; then
490    info "Re-signed firmware AU payload in ${loopdev}"
491  else
492    error "Couldn't sign firmware AU payload in ${loopdev}"
493  fi
494  return "${ret}"
495}
496
497# Re-sign the firmware AU payload provided with a new key.
498# Args: firmware_bundle
499resign_firmware_shellball() {
500  local firmware_bundle=$1
501
502  local shellball_dir
503  shellball_dir=$(make_temp_dir)
504
505  # extract_firmware_bundle can fail if the image has no firmware update.
506  if ! extract_firmware_bundle "${firmware_bundle}" "${shellball_dir}"; then
507    # Unmount now to prevent changes.
508    info "Didn't find a firmware update. Not signing firmware."
509    return
510  fi
511  info "Found a valid firmware update shellball."
512
513  # For context on signing firmware for unified builds, see:
514  #   go/cros-unibuild-signing
515  #
516  # This iterates over a signer_config.csv file, which contains the following:
517  #   output_name,image,key_id               (header)
518  #   santa,models/santa/bios.bin,SOME_OEM  (sample line)
519  #
520  # This dictates what output signature blocks to generate based on what
521  # keys/binaries.
522  #
523  # It reuses the LOEM architecture already existing in the signer keysets,
524  # but this could be revisited at a future date.
525  #
526  # Within signer_config.csv, it uses the key_id column to match the key
527  # value in loem.ini (if present) and signs the corresponding firmware
528  # image using that key.
529  #
530  # It then outputs the appropriate signature blocks based on the output_name.
531  # The firmware updater scripts then detects what output_name to use at
532  # runtime based on the platform.
533  local signer_config="${shellball_dir}/signer_config.csv"
534  if [[ -e "${signer_config}" ]]; then
535    info "Using signer_config.csv to determine firmware signatures"
536    info "See go/cros-unibuild-signing for details"
537    {
538      read # Burn the first line (header line)
539      while IFS="," read -r output_name bios_image key_id ec_image brand_code
540      do
541        local extra_args=()
542        local full_command=()
543
544        rootkey="$(get_root_key_vbpubk)"
545
546        info "Signing firmware image ${bios_image} for ${output_name}"
547
548        # If there are OEM specific keys available, we're going to use them.
549        # Otherwise, we're going to ignore key_id from the config file and
550        # just use the common keys present in the keyset.
551        #
552        # The presence of the /keyset subdir in the shellball will indicate
553        # whether dynamic signature blocks are available or not.
554        # This is what updater4.sh currently uses to make the decision.
555        if [[ -e "${KEY_DIR}/loem.ini" ]]; then
556          local match
557          local key_index
558
559          # loem.ini has the format KEY_ID_VALUE = KEY_INDEX
560          if ! match="$(grep -E "^[0-9]+ *= *${key_id}$" "${KEY_DIR}/loem.ini")"; then
561            die "The loem key_id ${key_id} not found in loem.ini! (${KEY_DIR}/loem.ini)"
562          fi
563
564          # shellcheck disable=SC2001
565          key_index="$(echo "${match}" | sed 's/ *= *.*$//g')"
566          info "Detected key index from loem.ini as ${key_index} for ${key_id}"
567          if [[ -z "${key_index}" ]]; then
568            die "Failed to extract key_index ${key_id} in loem.ini file for" \
569              "${output_name}"
570          fi
571
572          shellball_keyset_dir="${shellball_dir}/keyset"
573          mkdir -p "${shellball_keyset_dir}"
574          extra_args+=(
575            --loemdir "${shellball_keyset_dir}"
576            --loemid "${output_name}"
577          )
578          rootkey="$(get_root_key_vbpubk "${key_index}")"
579          cp "${rootkey}" "${shellball_keyset_dir}/rootkey.${output_name}"
580        fi
581
582        info "Using root key: ${rootkey##*/}"
583
584        local temp_fw
585        temp_fw=$(make_temp_file)
586
587        local signprivate
588        local keyblock
589        signprivate="$(get_firmware_vbprivk "${key_index}")"
590        keyblock="$(get_firmware_keyblock "${key_index}")"
591
592        # Path to bios.bin.
593        local bios_path="${shellball_dir}/${bios_image}"
594
595        echo "Before EC signing ${bios_path}: md5 =" \
596          "$(md5sum "${bios_path}" | awk '{print $1}')"
597
598        if [ -n "${ec_image}" ]; then
599          # Path to ec.bin.
600          local ec_path="${shellball_dir}/${ec_image}"
601
602          # Resign ec.bin.
603          if is_ec_rw_signed "${ec_path}"; then
604            local rw_bin="EC_RW.bin"
605            local rw_hash="EC_RW.hash"
606            # futility writes byproduct files to CWD, so we cd to temp dir.
607            pushd "$(make_temp_dir)" > /dev/null
608            full_command=(
609              do_futility sign
610              --type rwsig
611              --prikey "${KEYCFG_KEY_EC_EFS_VBPRIK2}"
612              --ecrw_out "${rw_bin}"
613              "${ec_path}"
614            )
615            echo "Signing EC with: ${full_command[*]}"
616            "${full_command[@]}" || die "Failed to sign ${ec_path}"
617            # Above command produces EC_RW.bin. Compute its hash.
618            openssl dgst -sha256 -binary "${rw_bin}" > "${rw_hash}"
619            # Store EC_RW.bin and its hash in bios.bin.
620            store_file_in_cbfs "${bios_path}" "${rw_bin}" "ecrw" \
621              || die "Failed to store file in ${bios_path}"
622            store_file_in_cbfs "${bios_path}" "${rw_hash}" "ecrw.hash" \
623              || die "Failed to store file in ${bios_path}"
624            popd > /dev/null
625            info "Signed EC image output to ${ec_path}"
626          fi
627        fi
628
629        echo "After EC signing ${bios_path}: md5 =" \
630          "$(md5sum "${bios_path}" | awk '{print $1}')"
631
632        # Resign bios.bin.
633        full_command=(
634          do_futility sign
635          --signprivate "${signprivate}"
636          --keyblock "${keyblock}"
637          --kernelkey "${KEYCFG_KERNEL_SUBKEY_VBPUBK}"
638          --version "${FIRMWARE_VERSION}"
639          "${extra_args[@]}"
640          "${bios_path}"
641          "${temp_fw}"
642        )
643        echo "Signing BIOS with: ${full_command[*]}"
644        "${full_command[@]}"
645
646        echo "After BIOS signing ${temp_fw}: md5 =" \
647          "$(md5sum "${temp_fw}" | awk '{print $1}')"
648
649        # For development phases, when the GBB can be updated still, set the
650        # recovery and root keys in the image.
651        full_command=(
652          do_futility gbb
653          -s
654          --recoverykey="${KEYCFG_RECOVERY_KEY_VBPUBK}"
655          --rootkey="${rootkey}" "${temp_fw}"
656          "${bios_path}"
657        )
658        echo "Setting GBB with: ${full_command[*]}"
659        "${full_command[@]}"
660
661        echo "After setting GBB on ${bios_path}: md5 =" \
662          "$(md5sum "${bios_path}" | awk '{print $1}')"
663
664        if [[ -e "${shellball_dir}/models/guybrush" ]]; then
665          echo "Not looking for RO_GSCVD on guybrush, b/263378945"
666        elif futility dump_fmap -p "${bios_path}" | grep -q RO_GSCVD; then
667          # Attempt AP RO verification signing only in case the FMAP includes
668          # the RO_GSCVD section.
669          local arv_root
670
671          if [[ -z ${brand_code} ]]; then
672            die "No brand code for ${bios_path} in signer_config.csv"
673          fi
674
675          arv_root="${KEYCFG_ARV_ROOT_VBPUBK}"
676          if [[ ! -f ${arv_root} ]]; then
677            die "No AP RO verification keys, could not create RO_GSCVD"
678          fi
679
680          # Resign the RO_GSCVD FMAP area.
681          full_command=(
682            do_futility gscvd
683            --keyblock "${KEYCFG_ARV_PLATFORM_KEYBLOCK}"
684            --platform_priv "${KEYCFG_ARV_PLATFORM_VBPRIVK}"
685            --board_id "${brand_code}"
686            --root_pub_key "${arv_root}"
687            "${bios_path}"
688          )
689          if [[ -n ${shellball_keyset_dir} ]]; then
690            full_command+=(
691              --gscvd_out
692              "${shellball_keyset_dir}/gscvd.${output_name}"
693            )
694          fi
695          echo "Setting RO_GSCVD with: ${full_command[*]}"
696          "${full_command[@]}"
697
698          echo "After signing RO_GSCVD on ${bios_path}: md5 =" \
699               "$(md5sum "${bios_path}" | awk '{print $1}')"
700        else
701          echo "No RO_GSCVD section in the image, skipping AP RO signing"
702        fi
703        info "Signed firmware image output to ${bios_path}"
704      done
705      unset IFS
706    } < "${signer_config}"
707  else
708    local image_file sign_args=() loem_sfx loem_output_dir
709    for image_file in "${shellball_dir}"/bios*.bin; do
710      if [[ -e "${KEY_DIR}/loem.ini" ]]; then
711        # Extract the extended details from "bios.bin" and use that in the
712        # subdir for the keyset.
713        loem_sfx=$(sed -r 's:.*/bios([^/]*)[.]bin$:\1:' <<<"${image_file}")
714        loem_output_dir="${shellball_dir}/keyset${loem_sfx}"
715        sign_args=( "${loem_output_dir}" )
716        mkdir -p "${loem_output_dir}"
717      fi
718      sign_firmware "${image_file}" "${KEY_DIR}" "${FIRMWARE_VERSION}" \
719        "${sign_args[@]}"
720    done
721  fi
722
723  local signer_notes="${shellball_dir}/VERSION.signer"
724  echo "" >"${signer_notes}"
725  echo "Signed with keyset in $(readlink -f "${KEY_DIR}") ." >>"${signer_notes}"
726  # record recovery_key
727  key="${KEYCFG_RECOVERY_KEY_VBPUBK}"
728  sha1=$(do_futility vbutil_key --unpack "${key}" \
729    | grep sha1sum | cut -d" " -f9)
730  echo "recovery: ${sha1}" >>"${signer_notes}"
731  # record root_key(s)
732  if [[ -d "${shellball_keyset_dir}"  ]]; then
733    echo "List sha1sum of all loem/model's signatures:" >>"${signer_notes}"
734    for key in "${shellball_keyset_dir}"/rootkey.*; do
735      model="${key##*.}"
736      sha1=$(do_futility vbutil_key --unpack "${key}" \
737        | grep sha1sum | cut -d" " -f9)
738      echo "  ${model}: ${sha1}" >>"${signer_notes}"
739    done
740  else
741    echo "List sha1sum of single key's signature:" >>"${signer_notes}"
742    key="$(get_root_key_vbpubk)"
743    sha1=$(do_futility vbutil_key --unpack "${key}" \
744      | grep sha1sum | cut -d" " -f9)
745    echo "  root: ${sha1}" >>"${signer_notes}"
746  fi
747
748  local new_shellball
749  new_shellball=$(make_temp_file)
750  cp -f "${firmware_bundle}" "${new_shellball}"
751  chmod a+rx "${new_shellball}"
752  repack_firmware_bundle "${shellball_dir}" "${new_shellball}"
753  sudo cp -f "${new_shellball}" "${firmware_bundle}"
754  sudo chmod a+rx "${firmware_bundle}"
755}
756
757# Remove old container key if it exists.
758# We can drop this logic once all devices that shipped R78 have gone EOL.
759# So probably in like 2025.
760remove_old_container_key() {
761  local loopdev="$1"
762
763  local rootfs_dir
764  rootfs_dir=$(make_temp_dir)
765  mount_loop_image_partition "${loopdev}" 3 "${rootfs_dir}"
766
767  sudo rm -f "${rootfs_dir}/usr/share/misc/oci-container-key-pub.der"
768
769  sudo umount "${rootfs_dir}"
770}
771
772# Re-sign Android image if exists.
773resign_android_image_if_exists() {
774  local loopdev="$1"
775
776  local rootfs_dir
777  rootfs_dir=$(make_temp_dir)
778  mount_loop_image_partition "${loopdev}" 3 "${rootfs_dir}"
779
780  local system_img
781  system_img="$(echo "${rootfs_dir}"/opt/google/*/android/system.raw.img)"
782  local arc_version
783  arc_version=$(grep CHROMEOS_ARC_VERSION= \
784    "${rootfs_dir}/etc/lsb-release" | cut -d= -f2)
785  if [[ ! -e "${system_img}" || -z "${arc_version}" ]]; then
786    info "ARC image not found.  Not signing Android APKs."
787    sudo umount "${rootfs_dir}"
788    return
789  fi
790
791  info "Found ARC image version '${arc_version}', re-signing APKs."
792  "${SCRIPT_DIR}/sign_android_image.sh" "${rootfs_dir}" "${KEY_DIR}/android"
793
794  if ! sudo umount "${rootfs_dir}"; then
795    error "umount ${rootfs_dir} failed"
796    sudo lsof -n "${rootfs_dir}"
797    ps auxf
798    return 1
799  fi
800  info "Re-signed Android image"
801}
802
803# Check whether the image's board is reven or not.
804# Args: LOOPDEV
805# Outputs: "true" if the board is reven, otherwise "false".
806get_is_reven() {
807  local loopdev="$1"
808  local rootfs_dir
809  local board
810
811  rootfs_dir=$(make_temp_dir)
812  mount_loop_image_partition "${loopdev}" 3 "${rootfs_dir}"
813
814  board=$(get_board_from_lsb_release "${rootfs_dir}")
815
816  sudo umount "${rootfs_dir}"
817
818  # When run by the signer, the board name will look like
819  # "reven-signed-mp-v2keys". Also accept plain "reven" for local
820  # testing.
821  if [[ "${board}" == "reven-signed"* || "${board}" == "reven" ]]; then
822    echo "true"
823  else
824    echo "false"
825  fi
826}
827
828# Sign UEFI binaries, if possible.
829# Args: LOOPDEV IS_REVEN
830sign_uefi_binaries() {
831  local loopdev="$1"
832  local is_reven="$2"
833  local efi_glob="*.efi"
834
835  if [[ ! -d "${KEY_DIR}/uefi" ]]; then
836    return 0
837  fi
838
839  local esp_dir
840  if ! esp_dir="$(mount_image_esp "${loopdev}")"; then
841    error "Could not mount EFI partition for signing UEFI binaries"
842    return 1
843  elif [[ -z "${esp_dir}" ]]; then
844    return 0
845  fi
846  if [[ "${is_reven}" == "false" ]]; then
847    "${SCRIPT_DIR}/install_gsetup_certs.sh" "${esp_dir}" "${KEY_DIR}/uefi"
848  else
849    # b/205145491: the reven board's boot*.efi files are already signed,
850    # change the glob so that they don't get resigned.
851    efi_glob="grub*.efi"
852  fi
853
854  local sign_uefi_cmd=(
855      "${SCRIPT_DIR}/sign_uefi.py"
856      --private-key "${KEYCFG_UEFI_PRIVATE_KEY}"
857      --sign-cert "${KEYCFG_UEFI_SIGN_CERT}"
858      --verify-cert "${KEYCFG_UEFI_VERIFY_CERT}"
859      --kernel-subkey-vbpubk "${KEYCFG_KERNEL_SUBKEY_VBPUBK}"
860      --crdyshim-private-key "${KEYCFG_UEFI_CRDYSHIM_PRIVATE_KEY}"
861      --efi-glob "${efi_glob}"
862  )
863
864  "${sign_uefi_cmd[@]}" --target-dir "${esp_dir}"
865  sudo umount "${esp_dir}"
866
867  local rootfs_dir
868  rootfs_dir="$(make_temp_dir)"
869  mount_loop_image_partition "${loopdev}" 3 "${rootfs_dir}"
870  "${sign_uefi_cmd[@]}" --target-dir "${rootfs_dir}/boot"
871  sudo umount "${rootfs_dir}"
872
873  info "Signed UEFI binaries"
874  return 0
875}
876
877# Verify the signatures of UEFI binaries.
878# Args: LOOPDEV
879verify_uefi_signatures() {
880  local loopdev="$1"
881  local succeeded=1
882
883  if [[ ! -d "${KEY_DIR}/uefi" ]]; then
884    return 0
885  fi
886
887  local esp_dir
888  if ! esp_dir="$(mount_image_esp "${loopdev}")"; then
889    error "Could not mount EFI partition for verifying UEFI signatures"
890    return 1
891  elif [[ -z "${esp_dir}" ]]; then
892    return 0
893  fi
894  "${SCRIPT_DIR}/verify_uefi.sh" "${esp_dir}" "${esp_dir}" \
895      "${KEY_DIR}/uefi" || succeeded=0
896
897  local rootfs_dir
898  rootfs_dir="$(make_temp_dir)"
899  mount_loop_image_partition_ro "${loopdev}" 3 "${rootfs_dir}"
900  "${SCRIPT_DIR}/verify_uefi.sh" "${rootfs_dir}/boot" "${esp_dir}" \
901      "${KEY_DIR}/uefi" || succeeded=0
902  sudo umount "${rootfs_dir}"
903
904  sudo umount "${esp_dir}"
905
906  if [[ "${succeeded}" == "0" ]]; then
907    die "UEFI signature verification failed"
908  fi
909}
910
911# Sign a GSC firmware image with the given keys.
912# Args: CONTAINER KEY_DIR [OUTPUT_CONTAINER]
913sign_gsc_firmware() {
914  local image=$1
915  local key_dir=$2
916  local output=$3
917
918  "${SCRIPT_DIR}/sign_gsc_firmware.sh" \
919    "${image}" "${key_dir}" "${output}"
920}
921
922# Verify an image including rootfs hash using the specified keys.
923verify_image() {
924  local loopdev
925  loopdev=$(loopback_partscan "${INPUT_IMAGE}")
926  local loop_rootfs="${loopdev}p3"
927
928  info "Verifying RootFS hash..."
929  # What we get from image.
930  local kernel_config
931  # What we calculate from the rootfs.
932  local new_kernel_config
933  # Depending on the type of image, the verity parameters may
934  # exist in either kernel partition 2 or kernel partition 4
935  local partnum
936  for partnum in 2 4; do
937    info "Considering Kernel partition ${partnum}"
938    kernel_config=$(sudo_futility dump_kernel_config \
939      "${loopdev}p${partnum}")
940    local hash_image
941    hash_image=$(make_temp_file)
942    if ! calculate_rootfs_hash "${loop_rootfs}" "${kernel_config}" \
943      "${hash_image}"; then
944      info "Trying next kernel partition."
945      continue
946    fi
947    new_kernel_config="${CALCULATED_KERNEL_CONFIG}"
948    break
949  done
950
951  # Note: If calculate_rootfs_hash succeeded above, these should
952  # be non-empty.
953  expected_hash=$(get_hash_from_config "${new_kernel_config}")
954  got_hash=$(get_hash_from_config "${kernel_config}")
955
956  if [ -z "${expected_hash}" ] || [ -z "${got_hash}" ]; then
957    die "Couldn't verify RootFS hash on the image."
958  fi
959
960  if [ ! "${got_hash}" = "${expected_hash}" ]; then
961    cat <<EOF
962FAILED: RootFS hash is incorrect.
963Expected: ${expected_hash}
964Got: ${got_hash}
965EOF
966    exit 1
967  else
968    info "PASS: RootFS hash is correct (${expected_hash})"
969  fi
970
971  # Now try and verify kernel partition signature.
972  set +e
973  local try_key="${KEYCFG_RECOVERY_KEY_VBPUBK}"
974  info "Testing key verification..."
975  # The recovery key is only used in the recovery mode.
976  echo -n "With Recovery Key (Recovery Mode ON, Dev Mode OFF): " && \
977  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 2 >/dev/null 2>&1 && \
978    echo "YES"; } || echo "NO"
979  echo -n "With Recovery Key (Recovery Mode ON, Dev Mode ON): " && \
980  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 3 >/dev/null 2>&1 && \
981    echo "YES"; } || echo "NO"
982
983  try_key="${KEYCFG_KERNEL_SUBKEY_VBPUBK}"
984  # The SSD key is only used in non-recovery mode.
985  echo -n "With SSD Key (Recovery Mode OFF, Dev Mode OFF): " && \
986  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 0 >/dev/null 2>&1  && \
987    echo "YES"; } || echo "NO"
988  echo -n "With SSD Key (Recovery Mode OFF, Dev Mode ON): " && \
989  { load_kernel_test "${INPUT_IMAGE}" "${try_key}" -b 1 >/dev/null 2>&1 && \
990    echo "YES"; } || echo "NO"
991  set -e
992
993  verify_image_rootfs "${loop_rootfs}"
994
995  verify_uefi_signatures "${INPUT_IMAGE}"
996
997  # TODO(gauravsh): Check embedded firmware AU signatures.
998}
999
1000# Re-calculate recovery kernel hash.
1001# Args: LOOPDEV RECOVERY_KERNEL_PARTITION KEYBLOCK PRIVKEY
1002update_recovery_kernel_hash() {
1003  local loopdev="$1"
1004  local recovery_kernel_partition="$2"
1005  local keyblock="$3"
1006  local privkey="$4"
1007
1008  local loop_recovery_kernel="${loopdev}p${recovery_kernel_partition}"
1009  local loop_kernb="${loopdev}p4"
1010
1011  # Update the kernel B hash in the recovery kernel command line.
1012  local old_kernel_config
1013  old_kernel_config="$(sudo_futility \
1014    dump_kernel_config "${loop_recovery_kernel}")"
1015  local old_kernb_hash
1016  old_kernb_hash="$(echo "${old_kernel_config}" |
1017    sed -nEe "s#.*kern_b_hash=([a-z0-9]*).*#\1#p")"
1018  local new_kernb_hash
1019  if [[ "${#old_kernb_hash}" -lt 64 ]]; then
1020    new_kernb_hash=$(sudo sha1sum "${loop_kernb}" | cut -f1 -d' ')
1021  else
1022    new_kernb_hash=$(sudo sha256sum "${loop_kernb}" | cut -f1 -d' ')
1023  fi
1024
1025  new_kernel_config=$(make_temp_file)
1026  # shellcheck disable=SC2001
1027  echo "${old_kernel_config}" |
1028    sed -e "s#\(kern_b_hash=\)[a-z0-9]*#\1${new_kernb_hash}#" \
1029      > "${new_kernel_config}"
1030  info "New config for kernel partition ${recovery_kernel_partition} is"
1031  cat "${new_kernel_config}"
1032
1033  # Re-calculate kernel partition signature and command line.
1034  sudo_futility vbutil_kernel --repack "${loop_recovery_kernel}" \
1035    --keyblock "${keyblock}" \
1036    --signprivate "${privkey}" \
1037    --version "${KERNEL_VERSION}" \
1038    --oldblob "${loop_recovery_kernel}" \
1039    --config "${new_kernel_config}"
1040}
1041
1042# Resign a single miniOS kernel partition.
1043# Args: LOOP_MINIOS KEYBLOCK PRIVKEY
1044resign_minios_kernel() {
1045  local loop_minios="$1"
1046  local keyblock="$2"
1047  local priv_key="$3"
1048
1049  if sudo_futility dump_kernel_config "${loop_minios}"; then
1050    # Delay checking that keyblock exists until we are certain of a valid miniOS
1051    # partition. Images that don't support miniOS might not provide these.
1052    # (This check is repeated twice, but that's okay.)
1053    # Update (9/3/24): we no longer check if the private key exists on disk
1054    # because it may live in Cloud KMS instead, opting instead to let futility
1055    # below fail if the key is missing.
1056    if [[ ! -e "${keyblock}" ]]; then
1057      error "Resign miniOS: keyblock doesn't exist: ${keyblock}"
1058      return 1
1059    fi
1060
1061    # Assume this is a miniOS kernel.
1062    local minios_kernel_version=$((KERNEL_VERSION >> 24))
1063    if sudo_futility vbutil_kernel --repack "${loop_minios}" \
1064        --keyblock "${keyblock}" \
1065        --signprivate "${priv_key}" \
1066        --version "${minios_kernel_version}" \
1067        --oldblob "${loop_minios}"; then
1068      echo
1069      info "Resign miniOS ${loop_minios}: done"
1070    else
1071      error "Resign miniOS ${loop_minios}: failed"
1072      return 1
1073    fi
1074  else
1075    info "Skipping empty miniOS partition ${loop_minios}."
1076  fi
1077}
1078
1079# Get the partition type of the loop device.
1080get_partition_type() {
1081  local loopdev=$1
1082  local device=$2
1083  # Prefer cgpt, fall back on lsblk.
1084  if command -v cgpt &> /dev/null; then
1085    echo "$(cgpt show -i "${device}" -t "${loopdev}")"
1086  else
1087    echo "$(sudo lsblk -rnb -o PARTTYPE "${loopdev}p${device}")"
1088  fi
1089}
1090
1091# Re-sign miniOS kernels with new keys.
1092# Args: LOOPDEV MINIOS_A_KEYBLOCK MINIOS_B_KEYBLOCK PRIVKEY
1093resign_minios_kernels() {
1094  local loopdev="$1"
1095  local minios_a_keyblock="$2"
1096  local minios_b_keyblock="$3"
1097  local priv_key="$4"
1098
1099  info "Searching for miniOS kernels to resign..."
1100
1101  # Attempt to sign miniOS A and miniOS B partitions, one at a time.
1102  # miniOS A - loop device 9.
1103  local loop_minios_a="${loopdev}p9"
1104  local part_type_a
1105  part_type_a="$(get_partition_type "${loopdev}" 9)"
1106  # miniOS B - loop device 10.
1107  local loop_minios_b="${loopdev}p10"
1108  local part_type_b
1109  part_type_b="$(get_partition_type "${loopdev}" 10)"
1110
1111  # Make sure the loop devices have a miniOS partition type.
1112  if [[ "${part_type_a^^}" == "${MINIOS_KERNEL_GUID}" ]]; then
1113    if ! resign_minios_kernel "${loop_minios_a}" "${minios_a_keyblock}" "${priv_key}"; then
1114      return 1
1115    fi
1116  fi
1117  if [[ "${part_type_b^^}" == "${MINIOS_KERNEL_GUID}" ]]; then
1118    if ! resign_minios_kernel "${loop_minios_b}" "${minios_b_keyblock}" "${priv_key}"; then
1119      return 1
1120    fi
1121  fi
1122}
1123
1124# Update the legacy bootloader templates in EFI partition if available.
1125# Args: LOOPDEV KERNEL
1126update_legacy_bootloader() {
1127  local loopdev="$1"
1128  local loop_kern="$2"
1129
1130  local esp_dir
1131  if ! esp_dir="$(mount_image_esp "${loopdev}")"; then
1132    error "Could not mount EFI partition for updating legacy bootloader cfg."
1133    return 1
1134  elif [[ -z "${esp_dir}" ]]; then
1135    info "Not updating legacy bootloader configs: ${loopdev}"
1136    return 0
1137  fi
1138
1139  # If we can't find the dm parameter in the kernel config, bail out now.
1140  local kernel_config
1141  kernel_config=$(sudo_futility dump_kernel_config "${loop_kern}")
1142  local root_hexdigest
1143  root_hexdigest="$(get_hash_from_config "${kernel_config}")"
1144  if [[ -z "${root_hexdigest}" ]]; then
1145    error "Couldn't grab root_digest from kernel partition ${loop_kern}"
1146    error " (config: ${kernel_config})"
1147    return 1
1148  fi
1149  # Update syslinux configs for legacy BIOS systems.
1150  if [[ -d "${esp_dir}/syslinux" ]]; then
1151    local cfg=("${esp_dir}"/syslinux/*.cfg)
1152    if ! sudo sed -i -r \
1153      "s/\broot_hexdigest=[a-z0-9]+/root_hexdigest=${root_hexdigest}/g" \
1154      "${cfg[@]}"; then
1155        error "Updating syslinux configs failed: '${cfg[*]}'"
1156        return 1
1157    fi
1158  fi
1159  # Update grub configs for EFI systems.
1160  local grub_cfg="${esp_dir}/efi/boot/grub.cfg"
1161  if [[ -f "${grub_cfg}" ]]; then
1162    if ! sudo sed -i -r \
1163      "s/\broot_hexdigest=[a-z0-9]+/root_hexdigest=${root_hexdigest}/g" \
1164      "${grub_cfg}"; then
1165        error "Updating grub config failed: '${grub_cfg}'"
1166        return 1
1167    fi
1168  fi
1169}
1170
1171# Sign an image file with proper keys.
1172# Args: IMAGE_TYPE INPUT OUTPUT DM_PARTNO KERN_A_KEYBLOCK KERN_A_PRIVKEY \
1173#       KERN_B_KEYBLOCK KERN_B_PRIVKEY KERN_C_KEYBLOCK KERN_C_PRIVKEY \
1174#       MINIOS_KEYBLOCK MINIOS_KEYBLOCK_V1 MINIOS_PRIVKEY
1175#
1176# A ChromiumOS image file (INPUT) always contains 2 partitions (kernel A & B).
1177# This function will rebuild hash data by DM_PARTNO, resign kernel partitions by
1178# their KEYBLOCK and PRIVKEY files, and then write to OUTPUT file. Note some
1179# special images (specified by IMAGE_TYPE, like 'recovery' or 'factory_install')
1180# may have additional steps (ex, tweaking verity hash or not stripping files)
1181# when generating output file.
1182# Some recovery images also have a kernel C, which is identical to kernel A,
1183# but signed with a different key (see b/266502803).
1184sign_image_file() {
1185  local image_type="$1"
1186  local input="$2"
1187  local output="$3"
1188  local dm_partno="$4"
1189  local kernA_keyblock="$5"
1190  local kernA_privkey="$6"
1191  local kernB_keyblock="$7"
1192  local kernB_privkey="$8"
1193  local kernC_keyblock="$9"
1194  local kernC_privkey="${10}"
1195  local minios_keyblock="${11}"
1196  local minios_keyblock_v1="${12}"
1197  local minios_privkey="${13}"
1198
1199  info "Preparing ${image_type} image..."
1200  cp --sparse=always "${input}" "${output}"
1201
1202  local loopdev
1203  loopdev=$(loopback_partscan "${output}")
1204  local loop_kern="${loopdev}p${dm_partno}"
1205  local loop_rootfs="${loopdev}p3"
1206  local is_reven
1207  is_reven=$(get_is_reven "${loopdev}")
1208
1209  # b/266502803: Some devices have a second recovery key which is used to sign:
1210  # - a second recovery kernel KERN-C in recovery images
1211  # - a second installer kernel KERN-B in factory images
1212  # If a device does not have a second recovery key, then these additional
1213  # kernels are not signed. If they are present, they will remain in the image
1214  # signed with dev keys.
1215  #
1216  # Sign KERN-B unless it's a factory image and this device doesn't have a
1217  # second recovery key.
1218  local should_sign_kernB="true"
1219  if [[ "${image_type}" == "factory_install" &&
1220        ! -f "${kernB_keyblock}" ]]; then
1221    should_sign_kernB="false"
1222  fi
1223  # Sign KERN-C unless this image type doesn't have KERN-C, or it's a
1224  # recovery image and this device doesn't have a second recovery key.
1225  local should_sign_kernC="true"
1226  if [[ -z "${kernC_keyblock}" ||
1227        ( "${image_type}" == "recovery" && ! -f "${kernC_keyblock}" ) ]]; then
1228    should_sign_kernC="false"
1229  fi
1230
1231  # The reven board needs to produce recovery images since some
1232  # downstream tools (e.g. the Chromebook Recovery Utility) expect
1233  # them. However, reven's recovery images are not like other boards
1234  # since reven is installed on generic PC hardware, and "recovery"
1235  # actually means reinstalling.
1236  #
1237  # Installation occurs via liveboot, which loads the 'A' partitions.
1238  # The UEFI bootloader expects the kernel partition to be signed with
1239  # the normal board key, not the recovery key, so for reven we sign
1240  # recovery images like base images: using the non-recovery key for
1241  # both the 'A' and 'B' partitions.
1242  local sign_recovery_like_base="${is_reven}"
1243
1244  if [[ "${image_type}" == "recovery" &&
1245        "${sign_recovery_like_base}" == "true" ]]; then
1246    kernA_keyblock="${kernB_keyblock}"
1247    kernA_privkey="${kernB_privkey}"
1248  fi
1249
1250  resign_firmware_payload "${loopdev}"
1251  remove_old_container_key "${loopdev}"
1252  resign_android_image_if_exists "${loopdev}"
1253  sign_uefi_binaries "${loopdev}" "${is_reven}"
1254  # We do NOT strip /boot for factory installer, since some devices need it to
1255  # boot EFI. crbug.com/260512 would obsolete this requirement.
1256  #
1257  # We also do NOT strip /boot for legacy BIOS or EFI devices.  This is because
1258  # "cros_installer postinst" on BIOS or EFI systems relies on presence of
1259  # /boot in rootfs to update kernel.  We infer the BIOS type from the kernel
1260  # config.
1261  local loop_kerna="${loopdev}p2"
1262  local kerna_config
1263  kerna_config="$(sudo_futility dump_kernel_config "${loop_kerna}")"
1264  if [[ "${image_type}" != "factory_install" &&
1265        " ${kerna_config} " != *" cros_legacy "* &&
1266        " ${kerna_config} " != *" cros_efi "* ]]; then
1267    "${SCRIPT_DIR}/strip_boot_from_image.sh" --image "${loop_rootfs}"
1268  fi
1269  update_rootfs_hash "${loopdev}" "${loop_kern}" \
1270    "${kernA_keyblock}" "${kernA_privkey}" \
1271    "${kernB_keyblock}" "${kernB_privkey}" "${should_sign_kernB}" \
1272    "${kernC_keyblock}" "${kernC_privkey}" "${should_sign_kernC}"
1273  update_stateful_partition_vblock "${loopdev}"
1274  if [[ "${image_type}" == "recovery" &&
1275        "${sign_recovery_like_base}" == "false" ]]; then
1276    update_recovery_kernel_hash "${loopdev}" 2 "${kernA_keyblock}" \
1277      "${kernA_privkey}"
1278    if [[ "${should_sign_kernC}" == "true" ]]; then
1279      update_recovery_kernel_hash "${loopdev}" 6 "${kernC_keyblock}" \
1280        "${kernC_privkey}"
1281    fi
1282  fi
1283
1284  if [[ -n "${minios_keyblock}" ]]; then
1285    # b/266502803: If it's a recovery image and minios_kernel.v1.keyblock
1286    # exists, sign MINIOS-A with minios_kernel.v1.keyblock and MINIOS-B with
1287    # minios_kernel.keyblock. Otherwise, sign both with minios_kernel.keyblock.
1288    local miniosA_keyblock="${minios_keyblock}"
1289    local miniosB_keyblock="${minios_keyblock}"
1290    if [[ -f "${minios_keyblock_v1}" ]]; then
1291      miniosA_keyblock="${minios_keyblock_v1}"
1292    fi
1293
1294    if ! resign_minios_kernels "${loopdev}" "${miniosA_keyblock}" \
1295        "${miniosB_keyblock}" "${minios_privkey}"; then
1296      return 1
1297    fi
1298  fi
1299
1300  if ! update_legacy_bootloader "${loopdev}" "${loop_kern}"; then
1301    # Error is already logged.
1302    return 1
1303  fi
1304  info "Signed ${image_type} image output to ${output}"
1305}
1306
1307# Sign a UEFI kernel kernel image with proper keys.
1308# Args: INPUT OUTPUT
1309sign_uefi_kernel() {
1310  local input="$1"
1311  local output="$2"
1312
1313  info "Preparing UEFI kernel image..."
1314  cp --sparse=always "${input}" "${output}"
1315
1316  "${SCRIPT_DIR}/sign_uefi.py" \
1317    --target-file "${output}" \
1318    --private-key "${KEYCFG_UEFI_PRIVATE_KEY}" \
1319    --sign-cert "${KEYCFG_UEFI_SIGN_CERT}" \
1320    --verify-cert "${KEYCFG_UEFI_VERIFY_CERT}" \
1321    --kernel-subkey-vbpubk "${KEYCFG_KERNEL_SUBKEY_VBPUBK}" \
1322    --crdyshim-private-key "${KEYCFG_UEFI_CRDYSHIM_PRIVATE_KEY}"
1323}
1324
1325main() {
1326  # Add to the path since some tools reside here and may not be in the non-root
1327  # system path.
1328  PATH+=:/usr/sbin:/sbin
1329
1330  # Make sure the tools we need are available.
1331  local prereqs
1332  for prereqs in ${FUTILITY} verity load_kernel_test dumpe2fs e2fsck sha1sum; do
1333    type -P "${prereqs}" &>/dev/null || \
1334      die "${prereqs} tool not found."
1335  done
1336
1337  # Parse arguments with positional and optional options.
1338  local script_args=()
1339  FUTILITY_EXTRA_FLAGS=""
1340  while [[ "$#" -gt 0 ]]; do
1341    case $1 in
1342      --debug)
1343        FUTILITY_EXTRA_FLAGS+="--debug "
1344        ;;
1345      -h|--help)
1346        usage
1347        ;;
1348      --)
1349        shift
1350        break
1351        ;;
1352      -*)
1353        usage "Unknown option: $1"
1354        ;;
1355      *)
1356        script_args+=("$1")
1357        ;;
1358    esac
1359    shift
1360  done
1361
1362  set -- "${script_args[@]}"
1363
1364  TYPE=$1
1365  INPUT_IMAGE=$2
1366  KEY_DIR=$3
1367  OUTPUT_IMAGE=$4
1368  VERSION_FILE=$5
1369
1370  setup_keycfg "${KEY_DIR}"
1371
1372  # Verification
1373  case ${TYPE} in
1374  dump_config)
1375    check_argc $# 2
1376    loopdev=$(loopback_partscan "${INPUT_IMAGE}")
1377    for partnum in 2 4; do
1378      info "kernel config in partition number ${partnum}:"
1379      sudo_futility dump_kernel_config "${loopdev}p${partnum}"
1380      echo
1381    done
1382    exit 0
1383    ;;
1384  verify)
1385    check_argc $# 2
1386    verify_image
1387    exit 0
1388    ;;
1389  *)
1390    # All other signing commands take 4 to 5 args.
1391    if [ -z "${OUTPUT_IMAGE}" ]; then
1392      # Friendlier message.
1393      usage "Missing output image name"
1394    fi
1395    check_argc $# 4 5
1396    ;;
1397  esac
1398
1399  # If a version file was specified, read the firmware and kernel
1400  # versions from there.
1401  if [ -n "${VERSION_FILE}" ]; then
1402    FIRMWARE_VERSION=$(sed -n 's#^firmware_version=\(.*\)#\1#pg' \
1403      "${VERSION_FILE}")
1404    KERNEL_VERSION=$(sed -n 's#^kernel_version=\(.*\)#\1#pg' "${VERSION_FILE}")
1405  fi
1406  info "Using firmware version: ${FIRMWARE_VERSION}"
1407  info "Using kernel version: ${KERNEL_VERSION}"
1408
1409  # Make all modifications on output copy.
1410  if [[ "${TYPE}" == "base" ]]; then
1411    sign_image_file "base" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 2 \
1412      "${KEYCFG_KERNEL_KEYBLOCK}" \
1413      "${KEYCFG_KERNEL_VBPRIVK}" \
1414      "${KEYCFG_KERNEL_KEYBLOCK}" \
1415      "${KEYCFG_KERNEL_VBPRIVK}" \
1416      "" \
1417      "" \
1418      "${KEYCFG_MINIOS_KERNEL_KEYBLOCK}" \
1419      "" \
1420      "${KEYCFG_MINIOS_KERNEL_VBPRIVK}"
1421  elif [[ "${TYPE}" == "recovery" ]]; then
1422    sign_image_file "recovery" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 4 \
1423      "${KEYCFG_RECOVERY_KERNEL_KEYBLOCK}" \
1424      "${KEYCFG_RECOVERY_KERNEL_VBPRIVK}" \
1425      "${KEYCFG_KERNEL_KEYBLOCK}" \
1426      "${KEYCFG_KERNEL_VBPRIVK}" \
1427      "${KEYCFG_RECOVERY_KERNEL_V1_KEYBLOCK}" \
1428      "${KEYCFG_RECOVERY_KERNEL_VBPRIVK}" \
1429      "${KEYCFG_MINIOS_KERNEL_KEYBLOCK}" \
1430      "${KEYCFG_MINIOS_KERNEL_V1_KEYBLOCK}" \
1431      "${KEYCFG_MINIOS_KERNEL_VBPRIVK}"
1432  elif [[ "${TYPE}" == "factory" ]]; then
1433    sign_image_file "factory_install" "${INPUT_IMAGE}" "${OUTPUT_IMAGE}" 2 \
1434      "${KEYCFG_INSTALLER_KERNEL_KEYBLOCK}" \
1435      "${KEYCFG_INSTALLER_KERNEL_VBPRIVK}" \
1436      "${KEYCFG_INSTALLER_KERNEL_V1_KEYBLOCK}" \
1437      "${KEYCFG_INSTALLER_KERNEL_VBPRIVK}" \
1438      "" \
1439      "" \
1440      "" \
1441      "" \
1442      ""
1443  elif [[ "${TYPE}" == "firmware" ]]; then
1444    if [[ -e "${KEY_DIR}/loem.ini" ]]; then
1445      die "LOEM signing not implemented yet for firmware images"
1446    fi
1447    cp "${INPUT_IMAGE}" "${OUTPUT_IMAGE}"
1448    sign_firmware "${OUTPUT_IMAGE}" "${KEY_DIR}" "${FIRMWARE_VERSION}"
1449  elif [[ "${TYPE}" == "shellball" ]]; then
1450    cp "${INPUT_IMAGE}" "${OUTPUT_IMAGE}"
1451    resign_firmware_shellball "${OUTPUT_IMAGE}"
1452    info "Signed firmware shellball ${OUTPUT_IMAGE}"
1453  elif [[ "${TYPE}" == "update_payload" ]]; then
1454    sign_update_payload "${INPUT_IMAGE}" "${KEYCFG_UPDATE_KEY_PEM}" "${OUTPUT_IMAGE}"
1455  elif [[ "${TYPE}" == "accessory_usbpd" ]]; then
1456    KEY_NAME="${KEY_DIR}/key_$(basename "$(dirname "${INPUT_IMAGE}")")"
1457    if [[ ! -e "${KEY_NAME}.pem" ]]; then
1458      KEY_NAME="${KEY_DIR}/key"
1459    fi
1460    cp "${INPUT_IMAGE}" "${OUTPUT_IMAGE}"
1461    do_futility sign --type usbpd1 --pem "${KEY_NAME}.pem" "${OUTPUT_IMAGE}"
1462  elif [[ "${TYPE}" == "accessory_rwsig" ]]; then
1463    # If KEYCFG_ACCESSORY_RWSIG_VBPRIK2 is non-empty, use it.
1464    if [[ -n "${KEYCFG_ACCESSORY_RWSIG_VBPRIK2}" ]]; then
1465      PRIV_KEY="${KEYCFG_ACCESSORY_RWSIG_VBPRIK2}"
1466    # If one key is present in this container, assume it's the right one.
1467    # See crbug.com/863464
1468    else
1469      shopt -s nullglob
1470      KEYS=( "${KEY_DIR}"/*.vbprik2 )
1471      shopt -u nullglob
1472      if [[ ${#KEYS[@]} -eq 1 ]]; then
1473        PRIV_KEY="${KEYS[0]}"
1474      else
1475        die "Expected exactly one key present in keyset for accessory_rwsig"
1476      fi
1477    fi
1478    cp "${INPUT_IMAGE}" "${OUTPUT_IMAGE}"
1479    do_futility sign --type rwsig --prikey "${PRIV_KEY}" \
1480             --version "${FIRMWARE_VERSION}" "${OUTPUT_IMAGE}"
1481  elif [[ "${TYPE}" == "gsc_firmware" ]]; then
1482    sign_gsc_firmware "${INPUT_IMAGE}" "${KEY_DIR}" "${OUTPUT_IMAGE}"
1483  elif [[ "${TYPE}" == "hps_firmware" ]]; then
1484    hps-sign-rom --input "${INPUT_IMAGE}" --output "${OUTPUT_IMAGE}" \
1485      --private-key "${KEY_DIR}/key_hps.priv.pem"
1486  elif [[ "${TYPE}" == "uefi_kernel" ]]; then
1487      sign_uefi_kernel "${INPUT_IMAGE}" "${OUTPUT_IMAGE}"
1488  elif [[ "${TYPE}" == "recovery_kernel" ]]; then
1489    cp "${INPUT_IMAGE}" "${OUTPUT_IMAGE}"
1490    if [[ -f "${KEYCFG_RECOVERY_KERNEL_V1_KEYBLOCK}" ]]; then
1491      local output_image_v1="${OUTPUT_IMAGE}${V1_SUFFIX}"
1492      cp "${OUTPUT_IMAGE}" "${output_image_v1}"
1493      do_futility sign -b "${KEYCFG_RECOVERY_KERNEL_V1_KEYBLOCK}" -s \
1494        "${KEYCFG_RECOVERY_KERNEL_VBPRIVK}" "${output_image_v1}"
1495    fi
1496    do_futility sign -b "${KEYCFG_RECOVERY_KERNEL_KEYBLOCK}" -s \
1497      "${KEYCFG_RECOVERY_KERNEL_VBPRIVK}" "${OUTPUT_IMAGE}"
1498  else
1499    die "Invalid type ${TYPE}"
1500  fi
1501}
1502main "$@"
1503