diff options
author | David Riley <davidriley@chromium.org> | 2018-10-31 14:43:58 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-01-13 13:52:16 -0800 |
commit | 575e14b1623572236204d606664a20d22bca8e94 (patch) | |
tree | 6fbd89ee1c0240725e78a099dbd10c8b806e3fd5 | |
parent | 8c0f3e10a7c9a0ac8041b203a9147d1d2938038f (diff) | |
download | vboot-575e14b1623572236204d606664a20d22bca8e94.tar.gz |
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>
-rwxr-xr-x | scripts/image_signing/sign_cr50_firmware.sh | 313 | ||||
-rwxr-xr-x | scripts/image_signing/sign_official_build.sh | 14 | ||||
-rw-r--r-- | tests/devkeys/cr50.pem | 27 |
3 files changed, 354 insertions, 0 deletions
diff --git a/scripts/image_signing/sign_cr50_firmware.sh b/scripts/image_signing/sign_cr50_firmware.sh new file mode 100755 index 00000000..e4f33bdb --- /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 1ec4f125..e2b0ce1c 100755 --- a/scripts/image_signing/sign_official_build.sh +++ b/scripts/image_signing/sign_official_build.sh @@ -42,6 +42,7 @@ where <type> is one of: 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 @@ sign_oci_container() { "${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 @@ elif [[ "${TYPE}" == "accessory_rwsig" ]]; then --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 00000000..ea16e603 --- /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----- |