• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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