sign_gsc_firmware: refactor and extend to support D2

The major difference between Cr50 and Ti50 signing is that the RW
sections are represented differently: elf files in Cr50 case and ihex
files in Ti50 case.

Other differences include the produced signed final image size and the
offsets of the components in the final image.

The signing script is being updated to figure out all these
differences at run time. A new optional field is introduced in the
signing manifest, the 'generation'. If this field is absent or set to
'h' (for H1), the script proceeds with the Cr50 signing process. If
'generation' is set to 'd' (for D2), the script proceeds with the Ti50
signing process.

Instead of using fixed offsets into the final image, the base
addresses of the components in ihex format are used, the only fixed
value is the base address of the flash image in the chip address space
(0x40000 for H1 vs 0x80000 for D2).

To make this work for H1 the output format of the signed blob produced
by gsc-codesigner is changed from binary to ihex.

BRANCH=none
BUG=b:173049030
TEST=using this script and the signing_istructions.sh module produced
     by the real Cr50 signer was able to produce functional images for
     both Cr50 and Ti50.

Change-Id: I845be1101b09c9476fa27fbddb72607dc6cea901
Signed-off-by: Vadim Bendebury <vbendeb@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2570009
Tested-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-by: George Engelbrecht <engeg@google.com>
Auto-Submit: Vadim Bendebury <vbendeb@chromium.org>
Commit-Queue: Vadim Bendebury <vbendeb@chromium.org>
diff --git a/scripts/image_signing/sign_gsc_firmware.sh b/scripts/image_signing/sign_gsc_firmware.sh
index a464b9b..30e2795 100755
--- a/scripts/image_signing/sign_gsc_firmware.sh
+++ b/scripts/image_signing/sign_gsc_firmware.sh
@@ -122,6 +122,28 @@
   fi
 }
 
+# Paste a binary blob into a larger binary file at a given offset.
+paste_bin() {
+  local file="${1}"
+  local blob="${2}"
+  local image_base="${3}"
+  local hex_base="${4}"
+  local file_size
+  local blob_size
+  local offset
+
+
+  file_size="$(stat -c '%s' "${file}")"
+  blob_size="$(stat -c '%s' "${blob}")"
+  offset="$(( hex_base - image_base ))"
+
+  if [[ $(( blob_size + offset )) -ge ${file_size} ]];then
+    die \
+      "Can't fit ${blob_size} bytes at offset ${offset} into ${file_size} bytes"
+  fi
+  dd if="${blob}" of="${file}" seek="${offset}" bs=1 conv=notrunc
+}
+
 # This function accepts one argument, the name of the GSC manifest file which
 # needs to be verified and in certain cases altered.
 #
@@ -306,30 +328,26 @@
 # Sign GSC RW firmware ELF images into a combined GSC firmware image
 # using the provided production keys and manifests.
 sign_rw() {
-  if [[ $# -ne 8 ]]; then
+  if [[ $# -ne 9 ]]; then
     die "Usage: sign_rw <key_file> <manifest> <fuses>" \
-        "<rma_key_dir> <rw_a> <rw_b> <output> <generation>"
+        "<rma_key_dir> <rw_a> <rw_b> <output> <generation> <image_base>"
   fi
 
   local key_file="$1"
   local manifest_file="$2"
   local fuses_file="$3"
   local rma_key_dir="$4"
-  local elfs=( "$5" "$6" )
+  local rws=( "$5" "$6" )
   local result_file="$7"
   local generation="$8"
-  local temp_dir
+  local image_base="$9"
+  local base_name
   local rma_key_base=""
-  local rw_a_offset
-  local rw_b_offset
+  local signer_command_params
+  local temp_dir
 
   temp_dir="$(make_temp_dir)"
-
-  if [[ ! -f "${result_file}" ]]; then
-    die "${result_file} not found."
-  fi
-
-  local signer_command_params=(-x "${fuses_file}" --key "${key_file}")
+  signer_command_params=(-x "${fuses_file}" --key "${key_file}")
 
   case "${generation}"  in
     (h)
@@ -342,9 +360,9 @@
         "${manifest_file}")"
 
       if [[ "${gsc_version}" != "22" ]]; then
-        rma_key_base="$(determine_rma_key_base "${rma_key_dir}" "${elfs[@]}")"
+        rma_key_base="$(determine_rma_key_base "${rma_key_dir}" "${rws[@]}")"
       else
-        echo "Ignoring RMA keys for factory branch ${gsc_version}"
+        warn "Ignoring RMA keys for factory branch ${gsc_version}"
       fi
 
       # Swap test public RMA server key with the prod version.
@@ -355,15 +373,13 @@
       fi
 
       # Indicate H1 signing.
-      signer_command_params+=( '--b' )
-      # Fixed offsets into the binary blob where RW sections start.
-      rw_a_offset=16384
-      rw_b_offset=278528
+      signer_command_params+=( "--b" )
+      base_name="cr50"
       ;;
     (d)
       # Indicate D1 signing.
-      signer_command_params+=( '--dauntless' )
-      die "Need to figure out D2 RW sections offsets"
+      signer_command_params+=( "--dauntless" "--ihex" )
+      base_name="ti50"
       ;;
     (*)
       die "Unknown generation value \"${generation}\""
@@ -372,46 +388,46 @@
 
   signer_command_params+=(--json "${manifest_file}")
 
-  signer_command_params+=(--format=bin)
-  dst_suffix='flat'
 
   if [[ "${FLAGS_override_keyid}" == "${FLAGS_TRUE}" ]]; then
     signer_command_params+=(--override-keyid)
   fi
 
-  local count=0
-  for elf in "${elfs[@]}"; do
-    if strings "${elf}" | grep -q "DBG/cr50"; then
-      die "Will not sign debug image with prod keys"
-    fi
-    signed_file="${temp_dir}/${count}.${dst_suffix}"
+  for rw in "${rws[@]}"; do
+    local hex_signed="${temp_dir}/hex_signed"
+    local bin_signed="${temp_dir}/bin_signed"
+    local hex_base
 
-    # Make sure output file is not owned by root.
-    touch "${signed_file}"
+    # Make sure output files are not owned by root.
+    touch "${bin_signed}" "${hex_signed}"
     if ! gsc-codesigner "${signer_command_params[@]}" \
-        -i "${elf}" -o "${signed_file}"; then
+        -i "${rw}" -o "${hex_signed}"; then
       die "gsc-codesigner ${signer_command_params[*]}" \
-        "-i ${elf} -o ${signed_file} failed"
+        "-i ${rw} -o ${hex_signed} failed"
+    fi
+
+    if ! objcopy -I ihex "${hex_signed}" -O binary "${bin_signed}"; then
+      die "Failed to convert ${rw} from hex to bin"
     fi
 
     if [[ -n "${rma_key_base}" ]]; then
-      if find_blob_in_blob  "${signed_file}" "${rma_key_base}.test"; then
+      if find_blob_in_blob  "${bin_signed}" "${rma_key_base}.test"; then
         die "test RMA key in the signed image!"
       fi
 
-      if ! find_blob_in_blob "${signed_file}" "${rma_key_base}.prod"; then
+      if ! find_blob_in_blob "${bin_signed}" "${rma_key_base}.prod"; then
         die "prod RMA key not in the signed image!"
       fi
     fi
-    : $(( count++ ))
+
+    hex_base="$(get_hex_base "${hex_signed}")"
+    paste_bin "${result_file}" "${bin_signed}" "${image_base}" "${hex_base}"
   done
 
-  # Full binary image is required, paste the newly signed blobs into the
-  # output image.
-  dd if="${temp_dir}/0.${dst_suffix}" of="${result_file}" \
-    seek="${rw_a_offset}" bs=1 conv=notrunc
-  dd if="${temp_dir}/1.${dst_suffix}" of="${result_file}" \
-    seek="${rw_b_offset}" bs=1 conv=notrunc
+  if strings "${rw}" | grep -q "DBG/${base_name}"; then
+    die "Will not sign debug image with prod keys"
+  fi
+
 }
 
 # A very crude RO verification function. The key signature found at a fixed
@@ -460,13 +476,13 @@
 # downloading the tarball from the BCS expects the image to be in that
 # directory.
 sign_gsc_firmware() {
-  if [[ $# -ne 9 ]]; then
+  if [[ $# -ne 10 ]]; then
     die "Usage: sign_gsc_firmware <key_file> <manifest> <fuses>" \
-        "<rma_key_dir> <ro_a> <ro_b> <rw_a> <rw_b> <output>"
+        "<rma_key_dir> <ro_a> <ro_b> <rw_a> <rw_b> <output> <generation>"
   fi
 
   local key_file="$1"
-  local manifest_source="$2"
+  local manifest_file="$2"
   local fuses_file="$3"
   local rma_key_dir="$4"
   local ro_a_hex="$5"
@@ -474,33 +490,21 @@
   local rw_a="$7"
   local rw_b="$8"
   local output_file="$9"
-  local generation
-  local manifest_file
+  local generation="${10}"
   local temp_dir
-  local ro_b_base
 
-  manifest_file="${manifest_source}.updated"
   temp_dir="$(make_temp_dir)"
 
-  # Prepare file for inline editing.
-  jq . < "${manifest_source}" > "${manifest_file}" || \
-    die "basic validation of ${manifest_json} failed"
-
-  # Retrieve chip type from the manifest, if preset, otherwise use h1.
-  generation="$(jq '.generation' "${manifest_file}")"
   case "${generation}"  in
-    (h|null)
-      generation="h"  # Just in case this is a legacy manifest.
-
+    (h)
       # H1 flash size, image size must match.
       IMAGE_SIZE="$(( 512 * 1024 ))"
+      IMAGE_BASE="0x40000"
       ;;
     (d)
       # D2 flash size, image size must match.
-      IMAGE_SIZE="$(( 512 * 1024 ))"
-      ;;
-    (*)
-      die "Unknown generation value \"${generation}\" in signing manifest"
+      IMAGE_SIZE="$(( 1024 * 1024 ))"
+      IMAGE_BASE="0x80000"
       ;;
   esac
 
@@ -512,54 +516,101 @@
     die "Failed creating ${output_file}"
   fi
 
-  local f
-  local count=0
-  for f in "${ro_a_hex}" "${ro_b_hex}"; do
-    if ! objcopy -I ihex "${f}" -O binary "${temp_dir}/${count}.bin"; then
-      die "Failed to convert ${f} from hex to bin"
-    fi
-    verify_ro "${temp_dir}/${count}.bin" "${key_file}"
-    : $(( count++ ))
-  done
-
   if ! sign_rw "${key_file}" "${manifest_file}" "${fuses_file}" \
        "${rma_key_dir}" "${rw_a}" "${rw_b}" \
-       "${output_file}" "${generation}"; then
-    die "Failed invoking sign_rw for ELF files ${rw_a} ${rw_b}"
+       "${output_file}" "${generation}" "${IMAGE_BASE}"; then
+    die "Failed invoking sign_rw for ${rw_a} and ${rw_b}"
   fi
 
-  ro_b_base=$(( IMAGE_SIZE / 2 ))
-  dd if="${temp_dir}/0.bin" of="${output_file}" conv=notrunc
-  dd if="${temp_dir}/1.bin" of="${output_file}" seek="${ro_b_base}" bs=1 \
-     conv=notrunc
+  local f
+  for f in "${ro_a_hex}" "${ro_b_hex}"; do
+    local hex_base
+    local bin
 
-  echo "Image successfully signed to ${output_file}"
+    hex_base="$(get_hex_base "${f}")"
+    bin="${temp_dir}/bin"
+
+    if [[ -z ${hex_base} ]]; then
+      die "Failed retrieving base address from ${f}"
+    fi
+
+    if ! objcopy -I ihex "${f}" -O binary "${bin}"; then
+      die "Failed to convert ${f} from hex to bin"
+    fi
+    if [[ "${generation}" == "h" ]]; then
+      verify_ro "${bin}" "${key_file}"
+    fi
+
+    paste_bin "${output_file}" "${bin}" "${IMAGE_BASE}" "${hex_base}"
+  done
+
+  info "Image successfully signed to ${output_file}"
 }
 
 # Sign the directory holding GSC firmware.
 sign_gsc_firmware_dir() {
   if [[ $# -ne 3 ]]; then
-    die "Usage: sign_gsc_firmware_dir <input> <key> <output>"
+    die "Usage: sign_gsc_firmware_dir <input> <key dir> <output>"
   fi
 
   local input="${1%/}"
-  local key_file="$2"
+  local key_dir="$2"
   local output="$3"
+  local generation
+  local rw_a
+  local rw_b
+  local manifest_source
+  local manifest_file
+  local key_file
+  local base_name
+
+  manifest_source="${input}/prod.json"
+  manifest_file="${manifest_source}.updated"
+
+  # Verify signing manifest.
+  jq . < "${manifest_source}" > "${manifest_file}" || \
+    die "basic validation of ${manifest_source} failed"
+
+
+  # Retrieve chip type from the manifest, if present, otherwise use h1.
+  generation="$(jq ".generation" "${input}/prod.json" | sed 's/"//g')"
+  case "${generation}"  in
+    (h|null)
+      generation="h"
+      base_name="cr50"
+      rw_a="${input}/ec.RW.elf"
+      rw_b="${input}/ec.RW_B.elf"
+      ;;
+    (d)
+      base_name="ti50"
+      rw_a="${input}/rw_A.hex"
+      rw_b="${input}/rw_B.hex"
+      ;;
+    (*)
+      die "Unknown generation value \"${generation}\" in signing manifest"
+      ;;
+  esac
+
+  key_file="${key_dir}/${base_name}.pem"
+  if [[ ! -e "${key_file}" ]]; then
+    die "Missing key file: ${key_file}"
+  fi
 
   if [[ -d "${output}" ]]; then
-    output="${output}/cr50.bin.prod"
+    output="${output}/${base_name}.bin.prod"
   fi
 
   sign_gsc_firmware \
           "${key_file}" \
-          "${input}/prod.json" \
+          "${manifest_file}" \
           "${input}/fuses.xml" \
           "${input}" \
           "${input}/prod.ro.A" \
           "${input}/prod.ro.B" \
-          "${input}/ec.RW.elf" \
-          "${input}/ec.RW_B.elf" \
-          "${output}"
+          "${rw_a}" \
+          "${rw_b}" \
+          "${output}" \
+          "${generation}"
 }
 
 main() {
@@ -572,7 +623,6 @@
   local key_dir="$2"
   local output="$3"
 
-  local key_file="${key_dir}/cr50.pem"
   local signing_instructions="${input}/signing_instructions.sh"
 
   if [[ -f ${signing_instructions} ]]; then
@@ -581,14 +631,10 @@
     die "${signing_instructions} not found"
   fi
 
-  if [[ ! -e "${key_file}" ]]; then
-    die "Missing key file: ${key_file}"
-  fi
-
   if [[ ! -d "${input}" ]]; then
     die "Missing input directory: ${input}"
   fi
 
-  sign_gsc_firmware_dir "${input}" "${key_file}" "${output}"
+  sign_gsc_firmware_dir "${input}" "${key_dir}" "${output}"
 }
 main "$@"