1#!/bin/bash 2# Copyright 2023 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6# This script allows to quickly re-sign a GSC image to a different set of 7# Device IDs. The script works with both Cr50 and Ti50, it must be run in the 8# appropriate top level directory, and the respective GSC binary has to be 9# present in the respective ./build directory. 10# 11# Two command line parameters are required, the device IDs of the GSC. 12# The image to re-sign can be supplied as an optional third param. 13# 14# The generated binary is saved in {cr,ti}50.<sha>.<devid0>.<devid1>.bin where 15# <sha> is the git sha of the original image. The suffix '-dirty' is added to 16# the <sha> component if "+" is in the {cr,ti}50 version string. "+" means that 17# there were changes in some of the files under git control when the original 18# image was built. 19 20set -ue 21 22SCRIPT_NAME="$(basename "$0")" 23SCRIPT_DIR="$(dirname "$0")" 24TMPD="$(mktemp -d "/tmp/${SCRIPT_NAME}.XXXXX")" 25NOCLEAN="${NOCLEAN:-}" 26if [[ -z ${NOCLEAN} ]]; then 27 trap 'rm -rf "${TMPD}"' EXIT 28fi 29 30# PKCS11 connector library needed for codesigner to access keys in Cloud KMS. 31PKCS11_MODULE_PATH="/usr/lib64/libkmsp11.so" 32 33# Cloud KMS path to the location of the GSC signing keys. 34KMS_PROJECT_PATH="projects/gsc-cloud-kms-signing/locations/us/keyRings/gsc-node-locked-signing-keys/cryptoKeys" 35 36# Make sure there is a codesigner in the path. 37CODESIGNER="" 38for f in cr50-codesigner \ 39 "${SCRIPT_DIR}/../../cr50-utils/software/tools/codesigner/codesigner" \ 40 codesigner; do 41 if command -v "${f}" > /dev/null 2>&1; then 42 CODESIGNER="${f}" 43 break 44 fi 45done 46if [[ -z ${CODESIGNER} ]]; then 47 echo "SCRIPT_NAME error: can't find codesigner" >&2 48 exit 1 49fi 50 51# Make sure there is a gsctool in the path. 52GSCTOOL="" 53for f in gsctool \ 54 "${SCRIPT_DIR}/../extra/usb_updater/gsctool"; do 55 if command -v "${f}" > /dev/null 2>&1; then 56 GSCTOOL="${f}" 57 break 58 fi 59done 60if [[ -z ${GSCTOOL} ]]; then 61 echo "SCRIPT_NAME error: can't find gsctool" >&2 62 exit 1 63fi 64 65# Update the manifest 66update_manifest() { 67 local full_bin="${1}" 68 local manifest="${2}" 69 local epoch 70 local major 71 local minor 72 local rw_ver 73 74 # Remove the board id and info rollback bits. 75 sed -i -zE 's/"board_[^,]+,\s*//g;s/"info"[^}]+},\s*/"info": { },/' \ 76 "${manifest}" 77 78 rw_ver="$("${GSCTOOL}" "-M" "-b" "${full_bin}" | \ 79 awk -F= '/IMAGE_RW_FW_VER/ {print $2}')" 80 IFS='.' read -r epoch major minor <<<"${rw_ver}" 81 echo "RW: ${rw_ver}" 82 sed "s/epoch\": [0-9]*/epoch\": ${epoch}/" "${manifest}" -i 83 sed "s/major\": [0-9]*/major\": ${major}/" "${manifest}" -i 84 sed "s/minor\": [0-9]*/minor\": ${minor}/" "${manifest}" -i 85} 86 87# Re-sign a single RW section. 88re_sign_rw() { 89 local tmp_file="$1" 90 local flash_base="$2" 91 local rw_base="$3" 92 local codesigner_params 93 local rw_size 94 local rw_bin_base 95 local rw_bin_size 96 local skip 97 98 codesigner_params=() 99 100 # Retrieve the rest of the codesigner command line arguments, which are this 101 # function's arguments after the three fixed ones. 102 shift 3 103 while [[ $# != 0 ]]; do 104 codesigner_params+=( "$1" ) 105 shift 106 done 107 108 # Determine RW size. It is 4 bytes at offset 808 into the signed header. 109 skip=$(( rw_base + 808 )) 110 rw_size="$(hexdump -s "${skip}" -n4 -e '1/4 "%d"' "${tmp_file}")" 111 112 # Extract the RW section to re-sign, dropping the existing header. 113 rw_bin_base=$(( rw_base + 1024 )) 114 rw_bin_size=$(( rw_size - 1024 )) 115 dd if="${full_bin}" of="${TMPD}/rw.bin" skip="${rw_bin_base}" bs=1 \ 116 count="${rw_bin_size}" status="none" 117 118 # Convert it to hex for signing 119 objcopy -I binary -O ihex --change-addresses $(( rw_bin_base + flash_base )) \ 120 "${TMPD}/rw.bin" "${TMPD}/rw.hex" 121 122 # Sign. 123 "${CODESIGNER}" "${codesigner_params[@]}" --input="${TMPD}/rw.hex" \ 124 --output="${TMPD}/rw.signed" 125 126 # Paste the result back into the original binary. 127 dd if="${TMPD}/rw.signed" of="${tmp_file}" seek="${rw_base}" conv="notrunc" \ 128 status="none" bs=1 129} 130 131main () { 132 local bin_size 133 local dev_id0 134 local dev_id1 135 local flash_base 136 local full_bin 137 local gsc_dir 138 local manifest 139 local output 140 local prefix 141 local rw_a_base 142 local rw_b_base 143 local rw_key 144 local rw_ver 145 local sha 146 local tmp_file 147 local xml 148 149 full_bin="" 150 if [[ $# -eq 3 ]]; then 151 full_bin="$3" 152 elif [[ $# -ne 2 ]]; then 153 echo "${SCRIPT_NAME} error:" >&2 154 echo " Two command line arguments are required, dev_id0 and dev_id1" >&2 155 echo " The image path is an optional third argument" >&2 156 exit 1 157 fi 158 159 dev_id0="$1" 160 dev_id1="$2" 161 162 if [[ -z ${full_bin} ]] ; then 163 for f in build/ti50/dauntless/dauntless/full_image.signed.bin \ 164 build/cr50/ec.bin; do 165 if [[ -f ${f} ]]; then 166 full_bin="${f}" 167 break 168 fi 169 done 170 if [[ -z ${full_bin} ]]; then 171 echo "${SCRIPT_NAME} error: GSC binary not found" >&2 172 exit 1 173 fi 174 else 175 if [[ -f ${full_bin} ]] ; then 176 echo "resigning supplied bin ${full_bin}" 177 else 178 echo "could not find ${full_bin}" 179 exit 1 180 fi 181 fi 182 183 codesigner_params=( 184 --dev_id0="${dev_id0}" 185 --dev_id1="${dev_id1}" 186 --format=bin 187 --ihex 188 --no-icache 189 --override-keyid 190 --padbank 191 ) 192 193 bin_size="$(stat -c '%s' "${full_bin}")" 194 case "${bin_size}" in 195 (524288) rw_a_base=16384 # RO area size is fixed at 16K 196 rw_b_base=$(( bin_size / 2 + rw_a_base )) 197 gsc_dir="${SCRIPT_DIR}/../../cr50" 198 key_name="cr50-hsm-backed-node-locked-key" 199 rw_key="${gsc_dir}/util/signer/${key_name}.pem.pub" 200 manifest="${gsc_dir}/util/signer/ec_RW-manifest-dev.json" 201 xml="${gsc_dir}/util/signer/fuses.xml" 202 codesigner_params+=( 203 --b 204 --pkcs11_engine="${PKCS11_MODULE_PATH}:0:${KMS_PROJECT_PATH}/${key_name}/cryptoKeyVersions/1" 205 ) 206 flash_base=262144 207 prefix="cr50" 208 KMS_PKCS11_CONFIG="$(readlink -f "${gsc_dir}/chip/g/config.yaml")" 209 ;; 210 (1048576) local rw_bases 211 # Third and sixths lines showing signed header magic are base 212 # addresses of RW_A and RW_B. 213 mapfile -t rw_bases < <(od -Ax -t x1 "${full_bin}" |awk ' 214 /^....00 fd ff ff ff/ { 215 line = line + 1; 216 if (line % 3 == 0) { 217 print strtonum("0x"$1) 218 } 219 }') 220 rw_a_base="${rw_bases[0]}" 221 rw_b_base="${rw_bases[1]}" 222 gsc_dir="${SCRIPT_DIR}/../../ti50/common" 223 rw_key="${gsc_dir}/ports/dauntless/signing/ti50_dev.key" 224 manifest="${gsc_dir}/ports/dauntless/signing/manifest.TOT.json" 225 xml="${gsc_dir}/ports/dauntless/signing/fuses.xml" 226 codesigner_params+=( 227 --dauntless 228 --pkcs11_engine="${PKCS11_MODULE_PATH}:0:${KMS_PROJECT_PATH}/ti50-node-locked-key/cryptoKeyVersions/1" 229 ) 230 flash_base=524288 231 prefix="ti50" 232 KMS_PKCS11_CONFIG="$(readlink -f "${gsc_dir}/ports/dauntless/config.yaml")" 233 ;; 234 (*) echo "What is ${full_bin}?" >&2 235 exit 1 236 ;; 237 esac 238 239 # Extract the sha from the original image version string. Find the version 240 # string from the image. Use the sha from the ti50 or cr50 repo. 241 rw_ver="$(strings "${full_bin}" | grep -m 1 "${prefix}_.*tpm" | \ 242 sed -E "s/.*(${prefix}\S*).*/\1/")" 243 sha="${rw_ver/*[-+]/}" 244 # If the rw version contains a "+" then the repo was not clean when the image 245 # was built. Always re-sign these images. 246 if [[ "${rw_ver}" =~ \+ ]] ; then 247 sha="${sha}-dirty" 248 fi 249 250 # Check if the output file already exists. 251 output="$(printf "${prefix}.${sha}.%08x-%08x.bin" "${dev_id0}" "${dev_id1}")" 252 if [[ -f ${output} ]]; then 253 echo "${output} is already there" 254 exit 0 255 fi 256 257 # Make sure all necessary files are present. 258 for f in "${rw_key}" "${manifest}" "${xml}"; do 259 if [[ ! -f ${f} ]]; then 260 echo "File ${f} not found" >&2 261 exit 1 262 fi 263 done 264 265 tmp_file="${TMPD}/full.bin" 266 cp "${full_bin}" "${tmp_file}" 267 268 cp "${manifest}" "${TMPD}/manifest.json" 269 # Clear the board id and rollback info mask. Update the manifest to use 270 # the same version as the original image. 271 update_manifest "${tmp_file}" "${TMPD}/manifest.json" 272 273 codesigner_params+=( 274 --json "${TMPD}/manifest.json" 275 --key "${rw_key}" 276 -x "${xml}" 277 ) 278 279 echo "Re-signing a ${prefix} image" 280 281 export KMS_PKCS11_CONFIG 282 283 re_sign_rw "${tmp_file}" "${flash_base}" "${rw_a_base}" \ 284 "${codesigner_params[@]}" 285 re_sign_rw "${tmp_file}" "${flash_base}" "${rw_b_base}" \ 286 "${codesigner_params[@]}" 287 288 cp "${tmp_file}" "${output}" 289 echo "signed image at ${output}" 290} 291 292main "$@" 293