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