1#!/bin/sh
2#
3# Copyright (c) 2012 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# 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 file (empty for system live firmware)" "f"
21DEFINE_string to "" "Path of output file (empty for system live firmware)" "t"
22DEFINE_string keys "$DEFAULT_KEYS_FOLDER" "Path to folder of dev keys" "k"
23DEFINE_string preamble_flags "" "Override preamble flags value. Known values:
24                        0: None. (Using RW to boot in normal. aka, two-stop)
25                        1: VB_FIRMWARE_PREAMBLE_USE_RO_NORMAL (one-stop)" "p"
26DEFINE_boolean mod_gbb_flags \
27  $FLAGS_TRUE "Modify GBB flags to enable developer friendly features" ""
28DEFINE_boolean force_backup \
29  $FLAGS_TRUE "Create backup even if source is not live" ""
30DEFINE_string backup_dir \
31  "$DEFAULT_BACKUP_FOLDER" "Path of directory to store firmware backups" ""
32
33# Parse command line
34FLAGS "$@" || exit 1
35eval set -- "$FLAGS_ARGV"
36
37# Globals
38# ----------------------------------------------------------------------------
39set -e
40
41# the image we are (temporary) working with
42IMAGE="$(make_temp_file)"
43IMAGE="$(readlink -f "$IMAGE")"
44
45# a log file to keep the output results of executed command
46EXEC_LOG="$(make_temp_file)"
47
48# Functions
49# ----------------------------------------------------------------------------
50
51# Disables write protection status registers
52disable_write_protection() {
53  # No need to change WP status in file mode
54  if [ -n "$FLAGS_to" ]; then
55    return $FLAGS_TRUE
56  fi
57
58  # --wp-disable command may return success even if WP is still enabled,
59  # so we should use --wp-status to verify the results.
60  echo "Disabling system software write protection status..."
61  (flashrom --wp-disable && flashrom --wp-status) 2>&1 |
62    tee "$EXEC_LOG" |
63    grep -q '^WP: .* is disabled\.$'
64}
65
66# Reads $IMAGE from $FLAGS_from
67read_image() {
68  if [ -z "$FLAGS_from" ]; then
69    echo "Reading system live firmware..."
70    if is_debug_mode; then
71      flashrom -V -r "$IMAGE"
72    else
73      flashrom -r "$IMAGE" >"$EXEC_LOG" 2>&1
74    fi
75  else
76    debug_msg "reading from file: $FLAGS_from"
77    cp -f "$FLAGS_from" "$IMAGE"
78  fi
79}
80
81# Writes $IMAGE to $FLAGS_to
82write_image() {
83  if [ -z "$FLAGS_to" ]; then
84    echo "Writing system live firmware..."
85    # TODO(hungte) we can enable partial write to make this faster
86    if is_debug_mode; then
87      flashrom -V -w "$IMAGE"
88    else
89      flashrom -w "$IMAGE" >"$EXEC_LOG" 2>&1
90    fi
91  else
92    debug_msg "writing to file: $FLAGS_to"
93    cp -f "$IMAGE" "$FLAGS_to"
94    chmod a+r "$FLAGS_to"
95  fi
96}
97
98# Converts HWID from $1 to proper format with "DEV" extension
99echo_dev_hwid() {
100  local hwid="$1"
101  local hwid_no_dev="${hwid% DEV}"
102
103  # NOTE: Some DEV firmware image files may put GUID in HWID.
104  # These are not officially supported and they will see "{GUID} DEV".
105
106  if [ "$hwid" != "$hwid_no_dev" ]; then
107    hwid="$hwid_no_dev"
108  fi
109  local hwid_dev="$hwid DEV"
110  debug_msg "echo_dev_hwid: [$1] -> [$hwid_dev]"
111  echo "$hwid_dev"
112}
113
114# Main
115# ----------------------------------------------------------------------------
116main() {
117  # Check parameters
118  local root_pubkey="$FLAGS_keys/root_key.vbpubk"
119  local recovery_pubkey="$FLAGS_keys/recovery_key.vbpubk"
120  local firmware_keyblock="$FLAGS_keys/firmware.keyblock"
121  local firmware_prvkey="$FLAGS_keys/firmware_data_key.vbprivk"
122  local dev_firmware_keyblock="$FLAGS_keys/dev_firmware.keyblock"
123  local dev_firmware_prvkey="$FLAGS_keys/dev_firmware_data_key.vbprivk"
124  local kernel_sub_pubkey="$FLAGS_keys/kernel_subkey.vbpubk"
125  local is_from_live=0
126  local backup_image=
127
128  debug_msg "Prerequisite check"
129  ensure_files_exist \
130    "$root_pubkey" \
131    "$recovery_pubkey" \
132    "$firmware_keyblock" \
133    "$firmware_prvkey" \
134    "$kernel_sub_pubkey" ||
135    exit 1
136
137  if [ -z "$FLAGS_from" ]; then
138    is_from_live=1
139  else
140    ensure_files_exist "$FLAGS_from" || exit 1
141  fi
142
143  debug_msg "Checking software write protection status"
144  disable_write_protection ||
145    if is_debug_mode; then
146      err_die "Failed to disable WP. Diagnose Message: $(cat "$EXEC_LOG")"
147    else
148      err_die "Write protection is still enabled. " \
149              "Please verify that hardware write protection is disabled."
150    fi
151
152  debug_msg "Pulling image to $IMAGE"
153  (read_image && [ -s "$IMAGE" ]) ||
154    err_die "Failed to read image. Error message: $(cat "$EXEC_LOG")"
155
156  debug_msg "Prepare to backup the file"
157  if [ -n "$is_from_live" -o $FLAGS_force_backup = $FLAGS_TRUE ]; then
158    backup_image="$(make_temp_file)"
159    debug_msg "Creating backup file to $backup_image..."
160    cp -f "$IMAGE" "$backup_image"
161  fi
162
163  debug_msg "Detecting developer firmware keyblock"
164  local expanded_firmware_dir="$(make_temp_dir)"
165  local use_devfw_keyblock="$FLAGS_FALSE"
166  (cd "$expanded_firmware_dir"; dump_fmap -x "$IMAGE" >/dev/null 2>&1) ||
167    err_die "Failed to extract firmware image."
168  if [ -f "$expanded_firmware_dir/VBLOCK_A" ]; then
169    local has_dev=$FLAGS_TRUE has_norm=$FLAGS_TRUE
170    # In output of vbutil_keyblock, "!DEV" means "bootable on normal mode" and
171    # "DEV" means "bootable on developer mode". Here we try to match the pattern
172    # in output of vbutil_block, and disable the flags (has_dev, has_norm) if
173    # the pattern was not found.
174    vbutil_keyblock --unpack "$expanded_firmware_dir/VBLOCK_A" |
175      grep -qw '!DEV' || has_norm=$FLAGS_FALSE
176    vbutil_keyblock --unpack "$expanded_firmware_dir/VBLOCK_A" |
177      grep -qw '[^!]DEV' || has_dev=$FLAGS_FALSE
178    if [ "$has_norm" = "$FLAGS_FALSE" -a "$has_dev" = "$FLAGS_TRUE" ]; then
179      use_devfw_keyblock=$FLAGS_TRUE
180    fi
181  fi
182
183  if [ "$use_devfw_keyblock" = "$FLAGS_TRUE" ]; then
184    echo "Using keyblocks (developer, normal)..."
185  else
186    echo "Using keyblocks (normal, normal)..."
187    dev_firmware_prvkey="$firmware_prvkey"
188    dev_firmware_keyblock="$firmware_keyblock"
189  fi
190
191  # TODO(hungte) We can use vbutil_firmware to check if the current firmware is
192  # valid so that we know keys and vbutil_firmware are all working fine.
193
194  echo "Preparing new firmware image..."
195
196  debug_msg "Resign the firmware code (A/B) with new keys"
197  # Note resign_firmwarefd.sh needs the original rootkey to determine firmware
198  # body size, so we must resign image before changing GBB rootkey.
199
200  local unsigned_image="$(make_temp_file)"
201  local optional_opts=""
202  if [ -n "$FLAGS_preamble_flags" ]; then
203    # optional_opts: VERSION FLAGS
204    debug_msg "Setting new VERSION=1, FLAGS=$FLAGS_preamble_flags"
205    optional_opts="1 $FLAGS_preamble_flags"
206  fi
207  cp -f "$IMAGE" "$unsigned_image"
208  "$SCRIPT_BASE/resign_firmwarefd.sh" \
209    "$unsigned_image" \
210    "$IMAGE" \
211    "$firmware_prvkey" \
212    "$firmware_keyblock" \
213    "$dev_firmware_prvkey" \
214    "$dev_firmware_keyblock" \
215    "$kernel_sub_pubkey" \
216    $optional_opts >"$EXEC_LOG" 2>&1 ||
217    err_die "Failed to re-sign firmware. (message: $(cat "$EXEC_LOG"))"
218    if is_debug_mode; then
219      cat "$EXEC_LOG"
220    fi
221
222  debug_msg "Extract current HWID"
223  local old_hwid
224  old_hwid="$(gbb_utility --get --hwid "$IMAGE" 2>"$EXEC_LOG" |
225              sed -rne 's/^hardware_id: (.*)$/\1/p')"
226
227  debug_msg "Decide new HWID"
228  [ -z "$old_hwid" ] &&
229    err_die "Cannot find current HWID. (message: $(cat "$EXEC_LOG"))"
230  local new_hwid="$(echo_dev_hwid "$old_hwid")"
231
232  local old_gbb_flags
233  old_gbb_flags="$(gbb_utility --get --flags "$IMAGE" 2>"$EXEC_LOG" |
234                   sed -rne 's/^flags: (.*)$/\1/p')"
235  debug_msg "Decide new GBB flags from: $old_gbb_flags"
236  [ -z "$old_gbb_flags" ] &&
237    err_die "Cannot find GBB flags. (message: $(cat "$EXEC_LOG"))"
238  # 0x30: GBB_FLAG_FORCE_DEV_BOOT_USB | GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK
239  local new_gbb_flags="$((old_gbb_flags | 0x30))"
240
241  debug_msg "Replace GBB parts (gbb_utility allows changing on-the-fly)"
242  gbb_utility --set \
243    --hwid="$new_hwid" \
244    --rootkey="$root_pubkey" \
245    --recoverykey="$recovery_pubkey" \
246    "$IMAGE" >"$EXEC_LOG" 2>&1 ||
247    err_die "Failed to change GBB Data. (message: $(cat "$EXEC_LOG"))"
248
249  # Old firmware does not support GBB flags, so let's make it an exception.
250  if [ "$FLAGS_mod_gbb_flags" = "$FLAGS_TRUE" ]; then
251    debug_msg "Changing GBB flags from $old_gbb_flags to $new_gbb_flags"
252    gbb_utility --set \
253      --flags="$new_gbb_flags" \
254      "$IMAGE" >"$EXEC_LOG" 2>&1 ||
255      echo "Warning: GBB flags ($old_gbb_flags -> $new_gbb_flags) can't be set."
256  fi
257
258  # TODO(hungte) compare if the image really needs to be changed.
259
260  debug_msg "Check if we need to make backup file(s)"
261  if [ -n "$backup_image" ]; then
262    local backup_hwid_name="$(echo "$old_hwid" | sed 's/ /_/g')"
263    local backup_date_time="$(date +'%Y%m%d_%H%M%S')"
264    local backup_file_name="firmware_${backup_hwid_name}_${backup_date_time}.fd"
265    local backup_file_path="$FLAGS_backup_dir/$backup_file_name"
266    if mkdir -p "$FLAGS_backup_dir" &&
267       cp -f "$backup_image" "$backup_file_path"; then
268      true
269    elif cp -f "$backup_image" "/tmp/$backup_file_name"; then
270      backup_file_path="/tmp/$backup_file_name"
271    else
272      backup_file_path=''
273    fi
274    if [ -n "$backup_file_path" -a -s "$backup_file_path" ]; then
275      # TODO(hungte) maybe we can wrap the flashrom by 'make_dev_firmware.sh -r'
276      # so that the only command to remember would be make_dev_firmware.sh.
277      echo "
278      Backup of current firmware image is stored in:
279        $backup_file_path
280      Please copy the backup file to a safe place ASAP.
281
282      To stop using devkeys and restore original firmware, execute command:
283        flashrom -w [PATH_TO_BACKUP_IMAGE]
284      Ex: flashrom -w $backup_file_path
285      "
286    else
287      echo "WARNING: Cannot create file in $FLAGS_backup_dir... Ignore backups."
288    fi
289  fi
290
291  # TODO(hungte) use vbutil_firmware to check if the new firmware is valid.
292  # Or, do verification in resign_firmwarefd.sh and trust it.
293
294  debug_msg "Write the image"
295  write_image ||
296    err_die "Failed to write image. Error message: $(cat "$EXEC_LOG")"
297
298  debug_msg "Complete."
299  if [ -z "$FLAGS_to" ]; then
300    echo "Successfully changed firmware to Developer Keys. New HWID: $new_hwid"
301  else
302    echo "Firmware '$FLAGS_to' now uses Developer Keys. New HWID: $new_hwid"
303  fi
304}
305
306main
307