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