image_signing: Add cr50 firmware signing support.

This introduces a script for signing Cr50 images on the build server.

BRANCH=cr50
TEST=sign_official_build.sh cr50_firmware input tests/devkeys output
BUG=b:74100307

Change-Id: I741b8532980b0a7a0b32fbacff235c38661c7668
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1313573
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/scripts/image_signing/sign_cr50_firmware.sh b/scripts/image_signing/sign_cr50_firmware.sh
new file mode 100755
index 0000000..e4f33bd
--- /dev/null
+++ b/scripts/image_signing/sign_cr50_firmware.sh
@@ -0,0 +1,313 @@
+#!/bin/bash
+# Copyright 2018 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.
+
+. "$(dirname "$0")/common.sh"
+
+load_shflags || exit 1
+
+DEFINE_boolean override_keyid "${FLAGS_TRUE}" \
+  "Override keyid from manifest." ""
+
+FLAGS_HELP="Usage: ${PROG} [options] <input_dir> <key_dir> <output_image>
+
+Signs <input_dir> with keys in <key_dir>.
+"
+
+# Parse command line.
+FLAGS "$@" || exit 1
+eval set -- "${FLAGS_ARGV}"
+
+# Abort on error and uninitialized variables.
+set -e
+set -u
+
+# This function accepts two arguments, names of two binary files.
+#
+# It searches the first passed-in file for the first 8 bytes of the second
+# passed in file. The od utility is used to generate full hex dump of the
+# first file (16 bytes per line) and the first 8 bytes of the second file.
+# grep is used to check if the pattern is present in the full dump.
+find_blob_in_blob() {
+  if [[ $# -ne 2 ]]; then
+    die "Usage: find_blob_in_blob <haystack> <needle>"
+  fi
+
+  local main_blob="$1"
+  local pattern_blob="$2"
+  local pattern
+  # Show without offsets, single byte hex, no compression of zero runs.
+  local od_options=("-An" "-tx1" "-v")
+
+  # Get the first 8 bytes of the pattern blob.
+  pattern="$(od "${od_options[@]}" -N8 "${pattern_blob}")"
+
+  # Eliminate all newlines to be able to search the entire body as one unit.
+  if od "${od_options[@]}" "${main_blob}" | \
+     tr -d '\n' |
+     grep -q -F "${pattern}"; then
+    return 0
+  fi
+
+  return 1
+}
+
+# This function accepts two arguments, names of the two ELF files.
+#
+# The files are searched for test RMA public key patterns - x25519 or p256,
+# both files are supposed to have pattern of one of these keys and not the
+# other. If this holds true the function prints the public key base name. If
+# not both files include the same key, or include more than one key, the
+# function reports failure and exits the script.
+determine_rma_key_base() {
+  if [[ $# -ne 3 ]]; then
+    die "Usage: determine_rma_key_base <rma_key_dir> <rw_a> <rw_b>"
+  fi
+
+  local rma_key_dir="$1"
+  local elfs=( "$2" "$3" )
+  local base_name="${rma_key_dir}/rma_key_blob"
+  local curve
+  local curves=( "x25519" "p256" )
+  local elf
+  local key_file
+  local mask=1
+  local result=0
+  local rma_key_base
+
+  for curve in "${curves[@]}"; do
+    key_file="${base_name}.${curve}.test"
+    for elf in "${elfs[@]}"; do
+      if find_blob_in_blob "${elf}" "${key_file}"; then
+        : $(( result |= mask ))
+      fi
+      : $(( mask <<= 1 ))
+    done
+  done
+
+  case "${result}" in
+    (3)  curve="x25519";;
+    (12) curve="p256";;
+    (*)  die "could not determine key type in the ELF files";;
+  esac
+
+  echo "${base_name}.${curve}"
+}
+
+# Sign cr50 RW firmware ELF images into a combined cr50 firmware image
+# using the provided production keys and manifests.
+sign_rw() {
+  if [[ $# -ne 7 ]]; then
+    die "Usage: sign_rw <key_file> <manifest> <fuses>" \
+        "<rma_key_dir> <rw_a> <rw_b> <output>"
+  fi
+
+  local key_file="$1"
+  local manifest_file="$2"
+  local fuses_file="$3"
+  local rma_key_dir="$4"
+  local elfs=( "$5" "$6" )
+  local result_file="$7"
+
+  local temp_dir="$(make_temp_dir)"
+
+  if [[ ! -f "${result_file}" ]]; then
+    die "${result_file} not found."
+  fi
+
+  # If signing a chip factory image (version 0.0.22) do not try figuring out the
+  # RMA keys.
+  local cr50_verson="$(jq '.epoch * 10000 + .major * 100 + .minor' \
+     "${manifest_file}")
+
+  if [[ "${cr50_verson}" != "22" ]]; then
+    rma_key_base="$(determine_rma_key_base "${rma_key_dir}" "${elfs[@]}")"
+  else
+    echo "Ignoring RMA keys for factory branch ${cr50_verson}"
+  fi
+
+  local signer_command_params=(--b -x "${fuses_file}" --key "${key_file}")
+
+  # Swap test public RMA server key with the prod version.
+  if [[ "${ignore_rma_keys}" != "yes" ]]; then
+    signer_command_params+=(
+      --swap "${rma_key_base}.test","${rma_key_base}.prod"
+    )
+  fi
+  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}"
+
+    # Make sure output file is not owned by root.
+    touch "${signed_file}"
+    if ! cr50-codesigner "${signer_command_params[@]}" \
+        -i "${elf}" -o "${signed_file}"; then
+      die "cr50-codesigner ${signer_command_params[@]}" \
+        "-i ${elf} -o ${signed_file} failed"
+    fi
+
+    if [[ "${ignore_rma_keys}" != "yes" ]]; then
+      if find_blob_in_blob  "${signed_file}" "${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
+        die "prod RMA key not in the signed image!"
+      fi
+    fi
+    : $(( count++ ))
+  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=16384 bs=1 conv=notrunc
+  dd if="${temp_dir}/1.${dst_suffix}" of="${result_file}" \
+    seek=278528 bs=1 conv=notrunc
+}
+
+# A very crude RO verification function. The key signature found at a fixed
+# offset into the RO blob must match the RO type. Prod keys have bit D2 set to
+# one, dev keys have this bit set to zero.
+verify_ro() {
+  if [[ $# -ne 1 ]]; then
+    die "Usage: verify_ro <ro_bin>"
+  fi
+
+  local ro_bin="$1"
+  local key_byte
+
+  if [[ ! -f "${ro_bin}" ]]; then
+    die "${ro_bin} not a file!"
+  fi
+
+  # Key signature's lowest byte is byte #5 in the line at offset 0001a0.
+  key_byte="$(od -Ax -t x1 -v "${ro_bin}" | awk '/0001a0/ {print $6}')"
+  case "${key_byte}" in
+    (?[4567cdef])
+      return 0
+      ;;
+  esac
+
+  die "RO key (${key_byte}) in ${ro_bin} does not match type prod"
+}
+
+# This function prepares a full CR50 image, consisting of two ROs and two RWs
+# placed at their respective offsets into the resulting blob. It invokes the
+# bs (binary signer) script to actually convert ELF versions of RWs into
+# binaries and sign them.
+#
+# The signed image is placed in the directory named as concatenation of RO and
+# RW version numbers and board ID fields, if set to non-default. The ebuild
+# downloading the tarball from the BCS expects the image to be in that
+# directory.
+sign_cr50_firmware() {
+  if [[ $# -ne 9 ]]; then
+    die "Usage: sign_cr50_firmware <key_file> <manifest> <fuses>" \
+        "<rma_key_dir> <ro_a> <ro_b> <rw_a> <rw_b> <output>"
+  fi
+
+  local key_file="$1"
+  local manifest_file="$2"
+  local fuses_file="$3"
+  local rma_key_dir="$4"
+  local ro_a_hex="$5"
+  local ro_b_hex="$6"
+  local rw_a="$7"
+  local rw_b="$8"
+  local output_file="$9"
+
+  local temp_dir="$(make_temp_dir)"
+
+  # The H1 chip where Cr50 firmware runs has 512K of flash, the generated
+  # image must match the flash size.
+  IMAGE_SIZE="$(( 512 * 1024 ))"
+
+  dd if=/dev/zero bs="${IMAGE_SIZE}" count=1 status=none |
+    tr '\000' '\377' > "${output_file}"
+  if [[ "$(stat -c '%s' "${output_file}")" != "${IMAGE_SIZE}" ]]; then
+    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"
+    : $(( count++ ))
+  done
+
+  if ! sign_rw "${key_file}" "${manifest_file}" "${fuses_file}" \
+               "${rma_key_dir}" "${rw_a}" "${rw_b}" "${output_file}"; then
+    die "Failed invoking sign_rw for ELF files ${rw_a} ${rw_b}"
+  fi
+
+  dd if="${temp_dir}/0.bin" of="${output_file}" conv=notrunc
+  dd if="${temp_dir}/1.bin" of="${output_file}" seek=262144 bs=1 conv=notrunc
+
+  echo "Image successfully signed to ${output_file}"
+}
+
+# Sign the directory holding cr50 firmware.
+sign_cr50_firmware_dir() {
+  if [[ $# -ne 3 ]]; then
+    die "Usage: sign_cr50_firmware_dir <input> <key> <output>"
+  fi
+
+  local input="${1%/}"
+  local key_file="$2"
+  local output="$3"
+
+  if [[ -d "${output}" ]]; then
+    output="${output}/cr50.bin.prod"
+  fi
+
+  sign_cr50_firmware \
+          "${key_file}" \
+          "${input}/ec_RW-manifest-prod.json" \
+          "${input}/fuses.xml" \
+          "${input}" \
+          "${input}/prod.ro.A" \
+          "${input}/prod.ro.B" \
+          "${input}/ec.RW.elf" \
+          "${input}/ec.RW_B.elf" \
+          "${output}"
+}
+
+main() {
+  if [[ $# -ne 3 ]]; then
+    flags_help
+    exit 1
+  fi
+
+  local input="${1%/}"
+  local key_dir="$2"
+  local output="$3"
+
+  local key_file="${key_dir}/cr50.pem"
+  if [[ ! -e "${key_file}" ]]; then
+    die "Missing key file: ${key_file}"
+  fi
+
+  if [[ ! -d "${input}" ]]; then
+    die "Missing input directory: ${input}"
+  fi
+
+  sign_cr50_firmware_dir "${input}" "${key_file}" "${output}"
+}
+main "$@"
diff --git a/scripts/image_signing/sign_official_build.sh b/scripts/image_signing/sign_official_build.sh
index 1ec4f12..e2b0ce1 100755
--- a/scripts/image_signing/sign_official_build.sh
+++ b/scripts/image_signing/sign_official_build.sh
@@ -42,6 +42,7 @@
              accessory_usbpd (sign USB-PD accessory firmware)
              accessory_rwsig (sign accessory RW firmware)
              oci-container (sign an OCI container)
+             cr50_firmware (sign a cr50 firmware image)
 
 output_image: File name of the signed output image
 version_file: File name of where to read the kernel and firmware versions.
@@ -818,6 +819,17 @@
     "${image}" "${key_dir}" --output "${output}"
 }
 
+# Sign a cr50 firmware image with the given keys.
+# Args: CONTAINER KEY_DIR [OUTPUT_CONTAINER]
+sign_cr50_firmware() {
+  local image=$1
+  local key_dir=$2
+  local output=$3
+
+  "${SCRIPT_DIR}/sign_cr50_firmware.sh" \
+    "${image}" "${key_dir}" "${output}"
+}
+
 # Verify an image including rootfs hash using the specified keys.
 verify_image() {
   local loopdev=$(loopback_partscan "${INPUT_IMAGE}")
@@ -1131,6 +1143,8 @@
            --version "${FIRMWARE_VERSION}" "${OUTPUT_IMAGE}"
 elif [[ "${TYPE}" == "oci-container" ]]; then
   sign_oci_container "${INPUT_IMAGE}" "${KEY_DIR}" "${OUTPUT_IMAGE}"
+elif [[ "${TYPE}" == "cr50_firmware" ]]; then
+  sign_cr50_firmware "${INPUT_IMAGE}" "${KEY_DIR}" "${OUTPUT_IMAGE}"
 else
   die "Invalid type ${TYPE}"
 fi
diff --git a/tests/devkeys/cr50.pem b/tests/devkeys/cr50.pem
new file mode 100644
index 0000000..ea16e60
--- /dev/null
+++ b/tests/devkeys/cr50.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAp/kh8/NGr1GUMA6c0tq9cRhVMaMwhYCF6mkpeW/D+1k3lL5q
+pkjqDcYBZG4xbhdCgEH9ppPYKzwKBVieWuqf7uymLBlCLmaPA6P4J+IwhS001WoD
+0kACEhnbL4xeP21fwuz9/u6ucoM8kJsFV/gacADmuOKTrU89Kyj2J5iLWVQPMMAM
+BOk+3BNamWwnCRk+CvcT+EQHtzcFkK2avm4HUQNSzhL407NbvsHwUjv7N6wtjeu5
+VLaTLTHxk9Z5savcn2jgxWASn4M59dpD7KSTYi4LsY8NPUWswz0E2a0vk8rfthtA
+amTkU4MT9ohVYq2JTCj5DC3DV/0Z7xiZ+ZsYPQIBAwKCAQBv+2v394R04Q11XxM3
+PH5LZY4hF3WuVa6cRhumSoKnkM+4fvHEMJwJLquYSXZJZNcAK/5vDTrHfVwDkGmR
+8b/0ncQdZiwe7woCbVAalssDc3iORq021Va2u+d1CD7U85Usnf6p9HRMV321vK46
+pWb1Ve8l7GJziijHcKQaZbI7jEq4JKyk9lL7seEWjf2zHyiLnh8wxQK7Ebizrqw9
+EIH3tmC6JKvbGJPizQ6tz1O0bVwiaHmZObouRxBTE8fL2zuSmJunqsYK4xqWfRsb
++RcSDndzBTW89qZr7i3h22g8jUsMiPBqV9/l9w1dOxnWwAtQSHfebcCA2u3OxUGM
+9dpTAoGBANhm0GYySwuCJc8lJpsBUl2tbuw7pzRdDe8BuqGv2aaEHx7arwFat1AA
+ZHVlQquWaKxwCuyFY/QlGq4uTNHhkBgygnFeEvtZ0KaKSVBBXY0Fbhq+N6rsX7FQ
+eRb4sz7We/aFR2K1V52dHaetOjMBfLhX1e7dZRwX8xnSSKuQeB6DAoGBAMa1uKLb
+LLbgYrnScI97GCOMGvjzdU9BjoGBbPay+53ZUqLcLPWwVy3qKeToQlISn3bqRBZp
+fAfCrKro6/weUusRAYXrzO41XeuJ1UsBUWPBqj3Gz5G1dAHQ3qkOMNRievieBnUV
+iXbdctg9dXufEL/75lZhJAZ+wZtmqAwVsjI/AoGBAJBEiu7MMgesGTTDbxIA4ZPI
+9J19GiLos/Sr0cEf5m8Cv2nnH1Y8ejVVmE5Dgce5mx2gB0hY7U1uEcl0MzaWYBAh
+rEuUDKeRNcRcMOArk7NY9BHUJRydlSDgULn7IinkUqRY2kHOOmkTaRpzfCIA/dA6
+jp8+Q2gP92aMMHJgUBRXAoGBAIR5JcHncySVlyaMSwpSEBeyvKX3o4ortFZWSKR3
+Umk7jGySyKPK5MlGxpia1uFhv6ScLWRGUq/XHcdF8qgUN0dgq66dM0l46UexONyr
+i5fWcX6EimEjoqvglHC0II2W/KW+rvi5Bk8+TJAo+P0UtdVSmY7rbVmp1meZxV1j
+zCF/AoGAcm2nAn275kfGZjXkTCYTZ6IXJgxcc4vXhv573UfNIJnC0Sg9rsgFiXHc
+nuQwFh5pTm4hU7uEknc/IobFLdCqM9mqujuYmboj0pmbRfOsjV9hqcmuo1OrSbJa
+gozzsNqU2I6srVW5SlCwWu1c4rBlBZvcdUtBRRb2b6bnhe29ykg=
+-----END RSA PRIVATE KEY-----