#!/bin/sh

# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Script to resign a firmware image using a different set of keys
# for use on signing servers.
#
# arguments: src_fd, dst_fd, firmware_datakey, and firmware_keyblock
#
# src_fd: Input firmware image (in .fd format)
# dst_fd: output firmware image name
# firmware_datakey: Key used to sign firmware data (in .vbprivk format)
# firmware_keyblock: Key block for firmware data key (in .keyblock format)
#
# Both the dump_fmap tool and vbutil_firmware should be in the system path.
#
# This script parses the output of dump_fmap tool
#
# to determine the regions in the image containing "Firmware [A|B] Data" and
# "Firmware [A|B] Key", which contain firmware data and firmware vblocks
# respectively. It will then generate new vblocks using the set of keys
# passed as arguments and output a new firmware image, with this new firmware
# vblocks the old ones.
#
# Here is an example output of "dump_fmap -p" (section name, offset, size):
# Boot Stub 3932160 262144
# GBB Area 3670016 262144
# Recovery Firmware 2490368 1048576
# RO VPD 2359296 131072
# Firmware A Key 196608 65536
# Firmware A Data 262144 851968
# Firmware B Key 1114112 65536
# Firmware B Data 1179648 851968
# RW VPD 152064 4096
# Log Volume 0 131072
#
# This shows that Firmware A Data is at offset 262144(0x40000) in the .fd image
# and is of size 851968(0xd0000) bytes. This can be extracted to generate new
# vblocks which can then replace old vblock for Firmware A ("Firmware A Key"
# region at offset 196608(0x30000) and size 65536(0x10000) ).

# Load common constants and variables.
. "$(dirname "$0")/common_minimal.sh"

# Abort on error
set -e

# Check arguments
if [ $# -lt 7 ] || [ $# -gt 9 ]; then
  echo "Usage: $PROG src_fd dst_fd firmware_datakey firmware_keyblock"\
   "dev_firmware_datakey dev_firmware_keyblock kernel_subkey [version [flag]]"
  exit 1
fi

# Make sure the tools we need are available.
for prog in cut dump_fmap md5sum vbutil_firmware; do
  type "${prog}" &>/dev/null || \
    { echo "${prog} tool not found."; exit 1; }
done

SRC_FD=$1
DST_FD=$2
FIRMWARE_DATAKEY=$3
FIRMWARE_KEYBLOCK=$4
DEV_FIRMWARE_DATAKEY=$5
DEV_FIRMWARE_KEYBLOCK=$6
KERNEL_SUBKEY=$7
VERSION=$8
# This is the --flags in vbutil_firmware. It currently has only two values:
# 0 for RW-NORMAL firmware, and 1 for RO-NORMAL firmware (search "two_stop
# firmware" for more information).
PREAMBLE_FLAG=$9

# Disables using developer keyblocks
disable_dev_keyblock() {
  DEV_FIRMWARE_KEYBLOCK=$FIRMWARE_KEYBLOCK
  DEV_FIRMWARE_DATAKEY=$FIRMWARE_DATAKEY
}

# Compares if the contents in given files are the same.
is_the_same_binary_file() {
  local hash1="$(md5sum -b "$1" | cut -d' ' -f1)"
  local hash2="$(md5sum -b "$2" | cut -d' ' -f1)"
  [ "$hash1" = "$hash2" ]
}

# Extract firmware body section from SRC_FD and truncate it to its body size
extract_firmware_image() {
  local label="$1"
  local root_key="$2"
  local vblock_offset="$3"
  local vblock_size="$4"
  local vblock_image="$5"
  local fw_offset="$6"
  local fw_size="$7"
  local fw_image="$8"
  local fw_body_size=""

  dd if="${SRC_FD}" of="${vblock_image}" skip="${vblock_offset}" bs=1 \
    count="${vblock_size}" 2>/dev/null
  dd if="${SRC_FD}" of="${fw_image}" skip="${fw_offset}" bs=1 \
    count="${fw_size}" 2>/dev/null
  fw_body_size="$(vbutil_firmware \
    --verify "${vblock_image}" \
    --signpubkey "${root_key}" \
    --fv "${fw_image}" |
    grep "Firmware body size:" |
    sed 's/.*: *//')" || fw_body_size="${fw_size}"
  if [ "${fw_body_size}" -gt "${fw_size}" ]; then
    echo -n "Firmware ${label} body size exceeds its section: "
    echo    "${fw_body_size} > ${fw_size}"
    return 1
  elif [ "${fw_body_size}" -lt "${fw_size}" ]; then
    dd if="${SRC_FD}" of="${fw_image}" skip="${fw_offset}" bs=1 \
      count="${fw_body_size}" 2>/dev/null
  fi
}

if [ -z "$VERSION" ]; then
  VERSION=1
fi
echo "Using firmware version: $VERSION"

if [ ! -e $DEV_FIRMWARE_KEYBLOCK ] || [ ! -e $DEV_FIRMWARE_DATAKEY ] ; then
  echo "No dev firmware keyblock/datakey found. Reusing normal keys."
  disable_dev_keyblock
fi

# Parse offsets and size of firmware data and vblocks
for i in "A" "B"
do
  line=$(dump_fmap -p $1 | grep "^Firmware $i Key") ||
  line=$(dump_fmap -p $1 | grep "^VBLOCK_$i") ||
   { echo "Couldn't parse vblock section $i from dump_fmap output";
     exit 1; }

  offset="$(echo $line | sed -r -e 's/.* ([0-9]+) [0-9]+$/\1/')"
  eval fw${i}_vblock_offset=$((offset))
  size="$(echo $line | sed -r -e 's/.* [0-9]+ ([0-9]+)$/\1/')"
  eval fw${i}_vblock_size=$((size))

  line=$(dump_fmap -p $1 | grep "^Firmware $i Data") ||
  line=$(dump_fmap -p $1 | grep "^FW_MAIN_$i") ||
  { echo "Couldn't parse Firmware $i section from dump_fmap output";
    exit 1; }

  offset="$(echo $line | sed -r -e 's/.* ([0-9]+) [0-9]+$/\1/')"
  eval fw${i}_offset=$((offset))
  size="$(echo $line | sed -r -e 's/.* [0-9]+ ([0-9]+)$/\1/')"
  eval fw${i}_size=$((size))
done

temp_fwimage_a=$(make_temp_file)
temp_fwimage_b=$(make_temp_file)
temp_out_vb=$(make_temp_file)
temp_root_key=$(make_temp_file)

echo "Reading Root Key from GBB"
gbb_utility -g --rootkey="$temp_root_key" "${SRC_FD}"

echo "Extracting Firmware A and B"
extract_firmware_image "A" "${temp_root_key}" \
  "${fwA_vblock_offset}" "${fwA_vblock_size}" "${temp_out_vb}" \
  "${fwA_offset}" "${fwA_size}" "${temp_fwimage_a}"
extract_firmware_image "B" "${temp_root_key}" \
  "${fwB_vblock_offset}" "${fwB_vblock_size}" "${temp_out_vb}" \
  "${fwB_offset}" "${fwB_size}" "${temp_fwimage_b}"

echo "Determining preamble flag from existing firmware"
if [ -n "$PREAMBLE_FLAG" ]; then
  PREAMBLE_FLAG="--flags $PREAMBLE_FLAG"
else
  dd if="${SRC_FD}" of="${temp_out_vb}" skip="${fwA_vblock_offset}" bs=1 \
    count="${fwA_vblock_size}" 2>/dev/null
  flag="$(vbutil_firmware \
    --verify "${temp_out_vb}" \
    --signpubkey "${temp_root_key}" \
    --fv "${temp_fwimage_a}" |
    grep "Preamble flags:" |
    sed 's/.*: *//')" || flag=""
  [ -z "$flag" ] || PREAMBLE_FLAG="--flags $flag"
fi
echo "Using firmware preamble flag: $PREAMBLE_FLAG"

# Sanity check firmware type: "developer key block" should be only used if the
# content in firmware A/B are different; otherwise always use normal key blocks.
if is_the_same_binary_file "${temp_fwimage_a}" "${temp_fwimage_b}"; then
  echo "Found firmware with same A/B content - ignoring DEV keyblock."
  disable_dev_keyblock
fi

echo "Re-calculating Firmware A vblock"
vbutil_firmware \
  --vblock "${temp_out_vb}" \
  --keyblock "${DEV_FIRMWARE_KEYBLOCK}" \
  --signprivate "${DEV_FIRMWARE_DATAKEY}" \
  --version "${VERSION}" \
  $PREAMBLE_FLAG \
  --fv "${temp_fwimage_a}" \
  --kernelkey "${KERNEL_SUBKEY}"

# Create a copy of the input image and put in the new vblock for firmware A
cp "${SRC_FD}" "${DST_FD}"
dd if="${temp_out_vb}" of="${DST_FD}" seek="${fwA_vblock_offset}" bs=1 \
  count="${fwA_vblock_size}" conv=notrunc 2>/dev/null

echo "Re-calculating Firmware B vblock"
vbutil_firmware \
  --vblock "${temp_out_vb}" \
  --keyblock "${FIRMWARE_KEYBLOCK}" \
  --signprivate "${FIRMWARE_DATAKEY}" \
  --version "${VERSION}" \
  $PREAMBLE_FLAG \
  --fv "${temp_fwimage_b}" \
  --kernelkey "${KERNEL_SUBKEY}"

# Destination image has already been created.
dd if="${temp_out_vb}" of="${DST_FD}" seek="${fwB_vblock_offset}" bs=1 \
  count="${fwB_vblock_size}" conv=notrunc 2>/dev/null

echo "New signed image was output to ${DST_FD}"
