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