xref: /aosp_15_r20/external/vboot_reference/scripts/image_signing/make_dev_firmware.sh (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
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