[unibuild] Support for unibuild firmware signing

For design context, see go/cros-unibuild-signing

This adds support for multiple, shared firmware images from a unified
build that needs to be signed with different OEM specific keys.

It uses a signer_config.csv file (that is generated by pack_firmware.py)
to determine which images need to be signed with which keys.

BUG=b:64842314
TEST=./build_image --board=coral dev
&& ./mod_image_for_recovery.sh --board=coral
&& ~/trunk/src/platform/vboot_reference/scripts/image_signing/sign_official_build.sh
recovery ../build/images/coral/latest/recovery_image.bin
../platform/vboot_reference/tests/devkeys
BRANCH=None

Change-Id: Id3711bbe73dfe652184bc046b5f642c30b8d1627
Reviewed-on: https://chromium-review.googlesource.com/626718
Commit-Ready: C Shapiro <shapiroc@google.com>
Tested-by: C Shapiro <shapiroc@google.com>
Reviewed-by: C Shapiro <shapiroc@google.com>
diff --git a/scripts/image_signing/sign_official_build.sh b/scripts/image_signing/sign_official_build.sh
index 579c581..cf0b334 100755
--- a/scripts/image_signing/sign_official_build.sh
+++ b/scripts/image_signing/sign_official_build.sh
@@ -20,6 +20,9 @@
 # Load common constants and variables.
 . "$(dirname "$0")/common.sh"
 
+# Which futility to run?
+[ -z "${FUTILITY}" ] && FUTILITY=futility
+
 # Print usage string
 usage() {
   cat <<EOF
@@ -544,7 +547,98 @@
   fi
   info "Found a valid firmware update shellball."
 
-  if [[ -d "${shellball_dir}/models" ]]; then
+  # For context on signing firmware for unified builds, see:
+  #   go/cros-unibuild-signing
+  #
+  # This iterates over a signer_config.csv file, which contains the following:
+  #   model_name,image,key_id               (header)
+  #   santa,models/santa/bios.bin,SOME_OEM  (sample line)
+  #
+  # This dictates the keys that should be used for which models and images.
+  #
+  # It reuses the LOEM architecture already existing in the signer keysets,
+  # but this could be revisited at a future date.
+  #
+  # Within signer_config.csv, it used the key_id column to match the key
+  # value in loem.ini (if present) and signs the corresponding firmware
+  # image using that key.
+  #
+  # For output, it uses the model name for the signed vblocks, which is then
+  # detected/used by the firmware updater script.
+  local signer_config="${shellball_dir}/signer_config.csv"
+  if [[ -e "${signer_config}" ]]; then
+    info "Using signer_config.csv to determine firmware signatures"
+    info "See go/cros-unibuild-signing for details"
+    {
+      read # Burn the first line (header line)
+      while IFS="," read -r model_name image key_id
+      do
+        local key_suffix=''
+
+        # If there are OEM specific keys available, we're going to use them.
+        # Otherwise, we're going to ignore key_id from the config file and
+        # just use the common keys present in the keyset.
+        # Regardless, a model specific vblock will be generated, which the
+        # updater script will be looking for.
+        if [[ -e "${KEY_DIR}/loem.ini" ]]; then
+          # loem.ini has the format KEY_ID_VALUE = KEY_INDEX
+          local key_index="$(grep '[0-9]\+ = ${key_id}' ${KEY_DIR}/loem.ini " \
+            "| cut -d ' ' -f 1)"
+          if [[ -z "${key_index}" ]]; then
+            die "Failed to find key_id ${key_id} in loem.ini file for model " \
+              "${model_name}"
+          fi
+          key_suffix=".loem${key_index}"
+        fi
+
+        info "Signing firmware image ${image} for model ${model_name} " \
+          "with key suffix ${key_suffix}"
+
+        local temp_fw=$(make_temp_file)
+
+        local signprivate="${KEY_DIR}/firmware_data_key${key_suffix}.vbprivk"
+        local keyblock="${KEY_DIR}/firmware${key_suffix}.keyblock"
+        local devsign="${KEY_DIR}/dev_firmware_data_key${key_suffix}.vbprivk"
+        local devkeyblock="${KEY_DIR}/dev_firmware${key_suffix}.keyblock"
+
+        if [ ! -e "${devsign}" ] || [ ! -e "${devkeyblock}" ] ; then
+          echo "No dev firmware keyblock/datakey found. Reusing normal keys."
+          devsign="${signprivate}"
+          devkeyblock="${keyblock}"
+        fi
+
+        mkdir -p "${shellball_dir}/keyset"
+        local image_path="${shellball_dir}/${image}"
+        ${FUTILITY} sign \
+          --signprivate "${signprivate}" \
+          --keyblock "${keyblock}" \
+          --devsign "${devsign}" \
+          --devkeyblock "${devkeyblock}" \
+          --kernelkey "${KEY_DIR}/kernel_subkey.vbpubk" \
+          --version "${FIRMWARE_VERSION}" \
+          --loemdir "${shellball_dir}/keyset" \
+          --loemid "${model_name}" \
+          ${image_path} \
+          ${temp_fw}
+
+        rootkey="${KEY_DIR}/root_key${key_suffix}.vbpubk"
+
+        # For development phases, when the GBB can be updated still, set the
+        # recovery and root keys in the image.
+        ${FUTILITY} gbb \
+          -s \
+          --recoverykey="${KEY_DIR}/recovery_key.vbpubk" \
+          --rootkey="${rootkey}" \
+          "${temp_fw}" \
+          "${image_path}"
+
+        info "Signed firmware image output to ${image_path}"
+      done
+      unset IFS
+    } < "${signer_config}"
+  # TODO(shapiroc): Delete this case once the build is migrated to use
+  # signer_config.csv
+  elif [[ -d "${shellball_dir}/models" ]]; then
     info "Signing firmware for all of the models in the unified build."
     local model_dir
     for model_dir in "${shellball_dir}"/models/*; do