1#!/bin/sh 2# 3# Copyright 2012 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# This script can change key (usually developer keys) in a firmware binary 8# image or system live firmware (EEPROM), and assign proper HWID, FLAGS as well. 9 10SCRIPT_BASE="$(dirname "$0")" 11. "$SCRIPT_BASE/common_minimal.sh" 12load_shflags || exit 1 13 14# Constants used by DEFINE_* 15VBOOT_BASE='/usr/share/vboot' 16DEFAULT_KEYS_FOLDER="$VBOOT_BASE/devkeys" 17DEFAULT_BACKUP_FOLDER='/mnt/stateful_partition/backups' 18 19# DEFINE_string name default_value description flag 20DEFINE_string from "" "Path of input BIOS file (empty for system live BIOS)" "f" 21DEFINE_string to "" "Path of output BIOS file (empty for system live BIOS)" "t" 22DEFINE_string ec_from "" "Path of input EC file (empty for system live EC)" "e" 23DEFINE_string ec_to "" "Path of output EC file (empty for system live EC)" "o" 24DEFINE_string keys "$DEFAULT_KEYS_FOLDER" "Path to folder of dev keys" "k" 25DEFINE_string preamble_flags "" "Override preamble flags value. Known values: 26 0: None. (Using RW to boot in normal. aka, two-stop) 27 1: VB_FIRMWARE_PREAMBLE_USE_RO_NORMAL (one-stop)" "p" 28DEFINE_boolean mod_hwid \ 29 $FLAGS_TRUE "Modify HWID to indicate this is a modified firmware" "" 30DEFINE_boolean mod_gbb_flags \ 31 $FLAGS_TRUE "Modify GBB flags to enable developer friendly features" "" 32DEFINE_boolean change_ec \ 33 $FLAGS_FALSE "Change the key in the EC binary if EC uses EFS boot" "" 34DEFINE_boolean force_backup \ 35 $FLAGS_TRUE "Create backup even if source is not live" "" 36DEFINE_string backup_dir \ 37 "$DEFAULT_BACKUP_FOLDER" "Path of directory to store firmware backups" "" 38 39# Parse command line 40FLAGS "$@" || exit 1 41eval set -- "$FLAGS_ARGV" 42 43# Globals 44# ---------------------------------------------------------------------------- 45set -e 46 47# the image we are (temporary) working with 48IMAGE_BIOS="$(make_temp_file)" 49IMAGE_BIOS="$(readlink -f "${IMAGE_BIOS}")" 50if [ "${FLAGS_change_ec}" = "${FLAGS_TRUE}" ]; then 51 IMAGE_EC="$(make_temp_file)" 52 IMAGE_EC="$(readlink -f "${IMAGE_EC}")" 53fi 54 55# a log file to keep the output results of executed command 56EXEC_LOG="$(make_temp_file)" 57 58# Functions 59# ---------------------------------------------------------------------------- 60 61flashrom_bios() { 62 if is_debug_mode; then 63 flashrom -V -p internal "$@" 64 else 65 flashrom -p internal "$@" 66 fi 67} 68 69flashrom_ec() { 70 if is_debug_mode; then 71 flashrom -V -p ec "$@" 72 else 73 flashrom -p ec "$@" 74 fi 75} 76 77# Execute the given command and log its output to the file ${EXEC_LOG}. 78# If is_debug_mode, also print the output directly. 79execute() { 80 if is_debug_mode; then 81 "$@" 2>&1 | tee "${EXEC_LOG}" 82 else 83 "$@" >"${EXEC_LOG}" 2>&1 84 fi 85} 86 87# Disables write protection status registers 88disable_write_protection() { 89 # No need to change WP status in file mode 90 if [ -n "$FLAGS_to" ]; then 91 return $FLAGS_TRUE 92 fi 93 94 # --wp-disable command may return success even if WP is still enabled, 95 # so we should use --wp-status to verify the results. 96 echo "Disabling system software write protection status..." 97 (flashrom_bios --wp-disable && flashrom_bios --wp-status) 2>&1 | 98 tee "$EXEC_LOG" | 99 grep -q '^Protection mode: disabled$' 100} 101 102# Reads ${IMAGE_BIOS} from ${FLAGS_from} and ${IMAGE_EC} from ${FLAGS_ec_from} 103read_image() { 104 if [ -z "$FLAGS_from" ]; then 105 echo "Reading system live BIOS firmware..." 106 execute flashrom_bios -r "${IMAGE_BIOS}" 107 else 108 debug_msg "reading from file: ${FLAGS_from}" 109 cp -f "${FLAGS_from}" "${IMAGE_BIOS}" 110 fi 111 if [ "${FLAGS_change_ec}" = "${FLAGS_TRUE}" ]; then 112 if [ -z "${FLAGS_ec_from}" ]; then 113 echo "Reading system live EC firmware..." 114 execute flashrom_ec -r "${IMAGE_EC}" 115 else 116 debug_msg "reading from file: ${FLAGS_ec_from}" 117 cp -f "${FLAGS_ec_from}" "${IMAGE_EC}" 118 fi 119 fi 120} 121 122# Writes ${IMAGE_BIOS} to ${FLAGS_to} and ${IMAGE_EC} to ${FLAGS_ec_to} 123write_image() { 124 if [ -z "${FLAGS_to}" ]; then 125 echo "Writing system live BIOS firmware..." 126 # TODO(hungte) we can enable partial write to make this faster 127 execute flashrom_bios -w "${IMAGE_BIOS}" 128 else 129 debug_msg "writing to file: ${FLAGS_to}" 130 cp -f "${IMAGE_BIOS}" "${FLAGS_to}" 131 chmod a+r "${FLAGS_to}" 132 fi 133 if [ "${FLAGS_change_ec}" = "${FLAGS_TRUE}" ]; then 134 if [ -z "${FLAGS_ec_to}" ]; then 135 echo "Writing system live EC firmware..." 136 # TODO(hungte) we can enable partial write to make this faster 137 execute flashrom_ec -w "${IMAGE_EC}" 138 else 139 debug_msg "writing to file: ${FLAGS_ec_to}" 140 cp -f "${IMAGE_EC}" "${FLAGS_ec_to}" 141 chmod a+r "${FLAGS_ec_to}" 142 fi 143 fi 144} 145 146# Converts HWID from $1 to proper format with "DEV" extension 147echo_dev_hwid() { 148 local hwid="$1" 149 local hwid_no_dev="${hwid% DEV}" 150 151 # NOTE: Some DEV firmware image files may put GUID in HWID. 152 # These are not officially supported and they will see "{GUID} DEV". 153 154 if [ "$hwid" != "$hwid_no_dev" ]; then 155 hwid="$hwid_no_dev" 156 fi 157 local hwid_dev="$hwid DEV" 158 debug_msg "echo_dev_hwid: [$1] -> [$hwid_dev]" 159 echo "$hwid_dev" 160} 161 162# Main 163# ---------------------------------------------------------------------------- 164main() { 165 # Check parameters 166 local root_pubkey="${FLAGS_keys}/root_key.vbpubk" 167 local recovery_pubkey="${FLAGS_keys}/recovery_key.vbpubk" 168 local firmware_keyblock="${FLAGS_keys}/firmware.keyblock" 169 local firmware_prvkey="${FLAGS_keys}/firmware_data_key.vbprivk" 170 local kernel_sub_pubkey="${FLAGS_keys}/kernel_subkey.vbpubk" 171 local ec_efs_pubkey="${FLAGS_keys}/key_ec_efs.vbpubk2" 172 local ec_efs_prvkey="${FLAGS_keys}/key_ec_efs.vbprik2" 173 local is_from_live=0 174 local backup_bios_image='' 175 local backup_ec_image='' 176 177 debug_msg "Prerequisite check" 178 ensure_files_exist \ 179 "${root_pubkey}" \ 180 "${recovery_pubkey}" \ 181 "${firmware_keyblock}" \ 182 "${firmware_prvkey}" \ 183 "${kernel_sub_pubkey}" \ 184 "${ec_efs_pubkey}" \ 185 "${ec_efs_prvkey}" || 186 exit 1 187 188 if [ -z "${FLAGS_from}" ]; then 189 is_from_live=1 190 else 191 ensure_files_exist "${FLAGS_from}" || exit 1 192 fi 193 194 if [ -z "${FLAGS_ec_from}" ]; then 195 is_from_live=1 196 else 197 ensure_files_exist "${FLAGS_ec_from}" || exit 1 198 fi 199 200 debug_msg "Checking software write protection status" 201 disable_write_protection || 202 if is_debug_mode; then 203 die "Failed to disable WP. Diagnose Message: $(cat "${EXEC_LOG}")" 204 else 205 die "Write protection is still enabled. " \ 206 "Please verify that hardware write protection is disabled." 207 fi 208 209 debug_msg "Pulling image" 210 (read_image && 211 [ -s "${IMAGE_BIOS}" ] && 212 [ "${FLAGS_change_ec}" = "${FLAGS_FALSE}" -o -s "${IMAGE_EC}" ]) || 213 die "Failed to read image. Error message: $(cat "${EXEC_LOG}")" 214 215 216 debug_msg "Prepare to backup the file" 217 if [ -n "${is_from_live}" -o ${FLAGS_force_backup} = ${FLAGS_TRUE} ]; then 218 backup_bios_image="$(make_temp_file)" 219 debug_msg "Creating BIOS backup file to ${backup_bios_image}..." 220 cp -f "${IMAGE_BIOS}" "${backup_bios_image}" 221 222 if [ "${FLAGS_change_ec}" = "${FLAGS_TRUE}" ]; then 223 backup_ec_image="$(make_temp_file)" 224 debug_msg "Creating EC backup file to ${backup_ec_image}..." 225 cp -f "${IMAGE_EC}" "${backup_ec_image}" 226 fi 227 fi 228 229 local expanded_firmware_dir="$(make_temp_dir)" 230 if [ "${FLAGS_change_ec}" = "${FLAGS_TRUE}" ]; then 231 if is_ec_rw_signed "${IMAGE_EC}"; then 232 # TODO(waihong): These are duplicated from sign_official_build.sh. We need 233 # to move them to a single place for share. 234 debug_msg "Resign EC firmware with new EC EFS key" 235 local rw_bin="EC_RW.bin" 236 local rw_hash="EC_RW.hash" 237 # futility writes byproduct files to CWD, so we cd to temp dir. 238 local old_cwd=$(pwd) 239 cd "${expanded_firmware_dir}" 240 241 ${FUTILITY} sign --type rwsig --prikey "${ec_efs_prvkey}" \ 242 --ecrw_out "${rw_bin}" "${IMAGE_EC}" || die "Failed to sign EC image" 243 # Above command produces EC_RW.bin. Compute its hash. 244 openssl dgst -sha256 -binary "${rw_bin}" > "${rw_hash}" 245 246 debug_msg "Store ecrw and its hash to BIOS firmware" 247 store_file_in_cbfs "${IMAGE_BIOS}" "${rw_bin}" "ecrw" || 248 die "Failed to store ecrw in BIOS image" 249 store_file_in_cbfs "${IMAGE_BIOS}" "${rw_hash}" "ecrw.hash" || 250 die "Failed to store ecrw.hash in BIOS image" 251 252 cd "${old_cwd}" 253 # Continuous the code below to resign the BIOS image. 254 else 255 echo "EC image is not signed. Skip changing its key." 256 fi 257 fi 258 259 debug_msg "Detecting developer firmware keyblock" 260 local use_devfw_keyblock="$FLAGS_FALSE" 261 (cd "${expanded_firmware_dir}"; "${FUTILITY}" dump_fmap -x "${IMAGE_BIOS}" \ 262 >/dev/null 2>&1) || die "Failed to extract firmware image." 263 if [ -f "$expanded_firmware_dir/VBLOCK_A" ]; then 264 local has_dev=$FLAGS_TRUE has_norm=$FLAGS_TRUE 265 # In output of vbutil_keyblock, "!DEV" means "bootable on normal mode" and 266 # "DEV" means "bootable on developer mode". Here we try to match the pattern 267 # in output of vbutil_block, and disable the flags (has_dev, has_norm) if 268 # the pattern was not found. 269 "${FUTILITY}" vbutil_keyblock --unpack "$expanded_firmware_dir/VBLOCK_A" | 270 grep -qw '!DEV' || has_norm=$FLAGS_FALSE 271 "${FUTILITY}" vbutil_keyblock --unpack "$expanded_firmware_dir/VBLOCK_A" | 272 grep -qw '[^!]DEV' || has_dev=$FLAGS_FALSE 273 if [ "$has_norm" = "$FLAGS_FALSE" -a "$has_dev" = "$FLAGS_TRUE" ]; then 274 use_devfw_keyblock=$FLAGS_TRUE 275 fi 276 fi 277 278 if [ "$use_devfw_keyblock" = "$FLAGS_TRUE" ]; then 279 echo "Using keyblocks (developer, normal)..." 280 else 281 echo "Using keyblocks (normal, normal)..." 282 fi 283 284 debug_msg "Extract firmware version and data key version" 285 286 local data_key_version firmware_version 287 # When we are going to flash directly from or to system, the versions stored 288 # in TPM can be found by crossystem; otherwise we'll need to guess from source 289 # firmware (FLAGS_from). 290 if [ -z "$FLAGS_to" -o -z "$FLAGS_from" ]; then 291 debug_msg "Reading TPM version from crossystem tpm_fwver." 292 data_key_version="$(( $(crossystem tpm_fwver) >> 16 ))" 293 firmware_version="$(( $(crossystem tpm_fwver) & 0xFFFF ))" 294 else 295 # TODO(hungte) On Vboot2, A/B slot may contain different firmware so we may 296 # need to check both and decide from largest number. 297 debug_msg "Guessing TPM version from original firmware." 298 local fw_info 299 fw_info="$(futility show -P "${IMAGE_BIOS}" 2>/dev/null)" \ 300 || die "Failed to show firmware info" 301 data_key_version="$(echo "${fw_info}" | \ 302 sed -nE 's/bios::VBLOCK_A::keyblock::data_key::version::(.*)/\1/p')" 303 firmware_version="$(echo "${fw_info}" | \ 304 sed -nE 's/bios::VBLOCK_A::preamble::firmware_version::(.*)/\1/p')" 305 fi 306 307 local new_data_key_version="$( 308 vbutil_keyblock --unpack "$firmware_keyblock" | 309 sed -n '/^ *Data key version:/s/.*:[ \t]*//p')" 310 311 # TODO(hungte) Change keyblock by data_key_version. 312 if [ "$data_key_version" -gt "$new_data_key_version" ]; then 313 echo "$(tput bold)$(tput setaf 1) 314 Warning: firmware data key version <$new_data_key_version> in your new keys 315 [$FLAGS_keys] is smaller than original firmware <$data_key_version> and 316 will boot into only recovery mode due to TPM anti-rollback detection. 317 318 After reboot with dev recovery key, you will need to reset TPM by booting a 319 test or dev image in recovery mode (NOT Ctrl-U), switch to VT2 and run 320 command <chromeos-tpm-recovery>; or use a factory install shim image 321 (build_image factory_install). 322 $(tput sgr 0)" >&2 323 fi 324 325 echo "Signing with Data Key Version: $data_key_version, " \ 326 "Firmware Version: $firmware_version" 327 328 echo "Preparing new firmware image..." 329 330 debug_msg "Resign the firmware code (A/B) with new keys" 331 # Note resign_firmwarefd.sh needs the original rootkey to determine firmware 332 # body size, so we must resign image before changing GBB rootkey. 333 334 local unsigned_image="$(make_temp_file)" 335 local optional_opts="" 336 if [ -n "$FLAGS_preamble_flags" ]; then 337 debug_msg "Setting FLAGS=$FLAGS_preamble_flags" 338 optional_opts="$FLAGS_preamble_flags" 339 fi 340 cp -f "${IMAGE_BIOS}" "$unsigned_image" 341 execute "$SCRIPT_BASE/resign_firmwarefd.sh" \ 342 "${unsigned_image}" \ 343 "${IMAGE_BIOS}" \ 344 "${firmware_prvkey}" \ 345 "${firmware_keyblock}" \ 346 "${kernel_sub_pubkey}" \ 347 "${firmware_version}" \ 348 ${optional_opts} || 349 die "Failed to re-sign firmware. (message: $(cat "${EXEC_LOG}"))" 350 351 debug_msg "Extract current HWID" 352 local old_hwid 353 old_hwid="$(${FUTILITY} gbb --get --hwid "${IMAGE_BIOS}" 2>"${EXEC_LOG}" | 354 sed -rne 's/^hardware_id: (.*)$/\1/p')" 355 356 debug_msg "Decide new HWID" 357 [ -z "$old_hwid" ] && 358 die "Cannot find current HWID. (message: $(cat "${EXEC_LOG}"))" 359 local new_hwid="$old_hwid" 360 if [ "$FLAGS_mod_hwid" = "$FLAGS_TRUE" ]; then 361 new_hwid="$(echo_dev_hwid "$old_hwid")" 362 fi 363 364 local old_gbb_flags 365 old_gbb_flags="$(${FUTILITY} gbb --get --flags "${IMAGE_BIOS}" \ 366 2>"${EXEC_LOG}" | sed -rne 's/^flags: (.*)$/\1/p')" 367 debug_msg "Decide new GBB flags from: $old_gbb_flags" 368 [ -z "$old_gbb_flags" ] && 369 die "Cannot find GBB flags. (message: $(cat "${EXEC_LOG}"))" 370 # 0x30: VB2_GBB_FLAG_FORCE_DEV_BOOT_USB | 371 # VB2_GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK 372 local new_gbb_flags="$((old_gbb_flags | 0x30))" 373 374 debug_msg "Replace GBB parts (futility gbb allows changing on-the-fly)" 375 ${FUTILITY} gbb --set \ 376 --hwid="${new_hwid}" \ 377 --rootkey="${root_pubkey}" \ 378 --recoverykey="${recovery_pubkey}" \ 379 "${IMAGE_BIOS}" >"${EXEC_LOG}" 2>&1 || 380 die "Failed to change GBB Data. (message: $(cat "${EXEC_LOG}"))" 381 382 # Old firmware does not support GBB flags, so let's make it an exception. 383 if [ "${FLAGS_mod_gbb_flags}" = "${FLAGS_TRUE}" ]; then 384 debug_msg "Changing GBB flags from ${old_gbb_flags} to ${new_gbb_flags}" 385 ${FUTILITY} gbb --set \ 386 --flags="${new_gbb_flags}" \ 387 "${IMAGE_BIOS}" >"${EXEC_LOG}" 2>&1 || 388 echo "Warning: Can't set GBB flags ${old_gbb_flags} -> ${new_gbb_flags}." 389 fi 390 391 # TODO(hungte) compare if the image really needs to be changed. 392 393 debug_msg "Check if we need to make backup file(s)" 394 if [ -n "${backup_bios_image}" ]; then 395 local backup_hwid_name="$(echo "${old_hwid}" | sed 's/ /_/g')" 396 local backup_date_time="$(date +'%Y%m%d_%H%M%S')" 397 local backup_bios_name="bios_${backup_hwid_name}_${backup_date_time}.fd" 398 local backup_bios_path="${FLAGS_backup_dir}/${backup_bios_name}" 399 local backup_ec_name="ec_${backup_hwid_name}_${backup_date_time}.fd" 400 local backup_ec_path='' 401 if mkdir -p "${FLAGS_backup_dir}" && 402 cp -f "${backup_bios_image}" "${backup_bios_path}"; then 403 if [ -n "${backup_ec_image}" ]; then 404 backup_ec_path="${FLAGS_backup_dir}/${backup_ec_name}" 405 cp -f "${backup_ec_image}" "${backup_ec_path}" 406 fi 407 elif cp -f "${backup_bios_image}" "/tmp/${backup_bios_name}"; then 408 backup_bios_path="/tmp/${backup_bios_name}" 409 if [ -n "${backup_ec_image}" ]; then 410 cp -f "${backup_ec_image}" "/tmp/${backup_ec_name}" 411 backup_ec_path="/tmp/${backup_ec_name}" 412 fi 413 else 414 backup_bios_path='' 415 fi 416 if [ -n "${backup_bios_path}" ]; then 417 # TODO(hungte) maybe we can wrap the flashrom by 'make_dev_firmware.sh -r' 418 # so that the only command to remember would be make_dev_firmware.sh. 419 echo " 420 Backup of current firmware image is stored in: 421 ${backup_bios_path} 422 ${backup_ec_path} 423 Please copy the backup file to a safe place ASAP. 424 425 To stop using devkeys and restore original BIOS, execute command: 426 flashrom -p internal -w [PATH_TO_BACKUP_BIOS] 427 Ex: flashrom -p internal -w ${backup_bios_path}" 428 if [ -n "${backup_ec_image}" ]; then 429 echo " 430 To stop using devkeys and restore original EC, execute command: 431 flashrom -p ec -w [PATH_TO_BACKUP_EC] 432 Ex: flashrom -p ec -w ${backup_ec_path}" 433 fi 434 echo "" 435 else 436 echo "WARNING: Can't create file in ${FLAGS_backup_dir}. Ignore backups." 437 fi 438 fi 439 440 # TODO(hungte) use 'futility verify' to check if the new firmware is valid. 441 # Or, do verification in resign_firmwarefd.sh and trust it. 442 443 debug_msg "Write the image" 444 write_image || 445 die "Failed to write image. Error message: $(cat "${EXEC_LOG}")" 446 447 debug_msg "Complete." 448 if [ -z "$FLAGS_to" ]; then 449 echo "Successfully changed firmware to Developer Keys. New HWID: $new_hwid" 450 else 451 echo "Firmware '$FLAGS_to' now uses Developer Keys. New HWID: $new_hwid" 452 fi 453} 454 455main 456