1#!/bin/bash 2# Copyright 2018 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. "$(dirname "$0")/common.sh" 7 8load_shflags || exit 1 9 10DEFINE_boolean override_keyid "${FLAGS_TRUE}" \ 11 "Override keyid from manifest." "" 12 13FLAGS_HELP="Usage: ${PROG} [options] <input_dir> <key_dir> <output_image> 14 15Signs <input_dir> with keys in <key_dir>. 16" 17 18# Parse command line. 19FLAGS "$@" || exit 1 20eval set -- "${FLAGS_ARGV}" 21 22# Abort on error and uninitialized variables. 23set -e 24set -u 25 26PRE_PVT_BID_FLAG=0x10 27MP_BID_FLAG=0x10000 28NIGHTLY_BID_FLAG=0x20000 29 30# Convert unsigned 32 bit value into a signed one. 31to_int32() { 32 local inp="$1" 33 python -c \ 34 "import struct; \ 35 d=struct.pack('I', $inp); \ 36 print (struct.unpack('i', d)[0])" 37} 38 39# Functions allowing to determine the base address of a binary blob in ihex 40# format. Invoked in a subprocess through () to be able to use stdout as the 41# return values. 42 43# In ihex format binary data is represented as a set of records. Each record 44# is a text string of hex values in ASCII. All records start with a header 45# which determines the record contents. 46# 47# The most common record type is the data record, its header includes the 16 48# bit address of where the record data will have to be placed in the physical 49# address space. Naturally 16 bits is not enough as of last thirty years, some 50# special types of record are used to specify the segment base of there the 51# 16 bit address is used as the offset. 52# 53# The segment base is still represented as a 16 bit value, depending on the 54# record type the base is shifted right ether 4 (record type 02) or 16 (record 55# type 04) bits. 56# 57# The first two records of the ihex binary blob are a segment record and a 58# data record. Combining the segment value from the first record and the 59# address value from the second record one can determine the base address 60# where the blob is supposed to be placed. 61# 62# See https://en.wikipedia.org/wiki/Intel_HEX for further details. 63parse_segment() { 64 local string="$1" 65 66 if [[ "${string}" =~ ^:020000 && "${#string}" -eq 15 ]]; then 67 local type="${string:7:2}" 68 local value="0x${string:9:4}" 69 local segment 70 71 case "${type}" in 72 (02) 73 segment=$(( value << 4 )) 74 ;; 75 (04) 76 segment=$(( value << 16 )) 77 ;; 78 (*) 79 error "unknown segment record type ${type}" 80 ;; 81 esac 82 printf "0x%x" "${segment}" 83 else 84 error "unexpected segment record: ${string}" 85 fi 86} 87 88# The second record in the ihex binary blob is mapped to the lowest 16 bit 89# address in the segment. 90parse_data() { 91 local string="$1" 92 93 if [[ "${string}" =~ ^:10 && "${#string}" -eq 43 ]]; then 94 echo "0x${string:3:4}" 95 else 96 error "unexpected data record: ${string}" 97 fi 98} 99 100# Given an ihex binary blob determine its base address as a sum of the segment 101# address and the offset of the first record into the segment. 102get_hex_base() { 103 local hexf="$1" 104 local strings 105 local segment 106 local base_offset 107 108 # Some ihex blobs include <cr><lf>, drop <cr> to allow for fixed size check. 109 mapfile -t strings < <(head -2 "${hexf}" | sed 's/\x0d//') 110 111 if [[ ${#strings[@]} != 2 ]]; then 112 error "input file ${hexf} too short" 113 return 114 fi 115 segment="$(parse_segment "${strings[0]}")" 116 base_offset="$(parse_data "${strings[1]}")" 117 118 if [[ -n "${segment}" && -n "${base_offset}" ]]; then 119 printf "%d\n" $(( segment + base_offset )) 120 else 121 error "${hexf} does not seem to be a valid ihex module." 122 fi 123} 124 125# Paste a binary blob into a larger binary file at a given offset. 126paste_bin() { 127 local file="${1}" 128 local blob="${2}" 129 local image_base="${3}" 130 local hex_base="${4}" 131 local file_size 132 local blob_size 133 local offset 134 135 136 file_size="$(stat -c '%s' "${file}")" 137 blob_size="$(stat -c '%s' "${blob}")" 138 offset="$(( hex_base - image_base ))" 139 140 if [[ $(( blob_size + offset )) -ge ${file_size} ]];then 141 die \ 142 "Can't fit ${blob_size} bytes at offset ${offset} into ${file_size} bytes" 143 fi 144 dd if="${blob}" of="${file}" seek="${offset}" bs=1 conv=notrunc 145} 146 147# This function accepts two arguments, the name of the GSC manifest file which 148# needs to be verified and in certain cases altered and the generation of the 149# chip (h or g). 150# 151# The function verifies that the input manifest is a proper json file, and 152# that the manifest conforms to GSC board ID flags conventions for various 153# build images: 154 155# - board IDs for node locked images come from signing instructions, and the 156# config1 manifest field value must have the 0x80000000 bit set. 157# 158# - when signing pre-pvt binaries (major version number is even) the 0x10 159# flags bit must be set. 160# 161# - when signing mp images (major version number is odd), the 0x10000 flags 162# bit must be set (this can be overridden by signing instructions). 163# 164# - when signing nightly images (major version number is 26), the flags are 165# 0x20000 166verify_and_prepare_gsc_manifest() { 167 if [[ $# -ne 2 ]]; then 168 die "Usage: verify_and_prepare_gsc_manifest <manifest .json file> <generation>" 169 fi 170 171 local manifest_json="$1" 172 local generation="$2" 173 174 local bid_flags 175 local config1 176 local epoch 177 local major 178 local minor 179 local values 180 181 mapfile -t values < <(jq '.config1,.epoch,.major,.minor,.board_id_flags' \ 182 "${manifest_json}") 183 184 config1="${values[0]}" 185 epoch="${values[1]}" 186 major="${values[2]}" 187 minor="${values[3]}" 188 bid_flags="${values[4]}" 189 190 if [[ ${major} == null ]]; then 191 die "Major version number not found in ${manifest_json}" 192 fi 193 194 if [[ ${bid_flags} == null ]]; then 195 die "bid_flags not found in ${manifest_json}" 196 fi 197 198 case "${INSN_TARGET:-}" in 199 200 (Nightly) 201 # At this point Nightly builds must have the major version 26 and 202 # 0x20000 board id flags, so it can't run on released devices. 203 if (( (major == 26 ) && (bid_flags == NIGHTLY_BID_FLAG) )); then 204 # The Nightly target is only valid for ti50 devices. 205 if [[ "${generation}" == "d" ]] ; then 206 return 0 207 fi 208 fi 209 ;; 210 211 (NodeLocked) 212 if [[ -z ${INSN_DEVICE_ID:-} ]]; then 213 die "Node locked target without Device ID value" 214 fi 215 216 local sub 217 local devid0 218 local devid1 219 220 devid0="$(to_int32 "0x${INSN_DEVICE_ID/-*}")" 221 devid1="$(to_int32 "0x${INSN_DEVICE_ID/*-}")" 222 cf1="$(to_int32 $(( 0x80000000 + config1 )))" 223 sub="$(printf " \"DEV_ID0\": %s,\\\n \"DEV_ID1\": %s," \ 224 "${devid0}" "${devid1}")" 225 226 # Manifest fields must be modified as follows: 227 # 228 # - board_id related fields removed 229 # - 'config1' field bit 0x80000000 set 230 # - least significant bit of the 'tag' field originally set to all zeros 231 # changed from zero to one 232 # - DEV_ID values spliced in into the 'fuses' section 233 sed -i "/board_id/d;\ 234 s/\"config1\":.*/\"config1\": ${cf1},/;\ 235 s/\(tag.*0\+\)0/\11/;\ 236 /\"fuses\":/ a\ 237 $sub" "${manifest_json}" || die "Failed to edit the manifest" 238 return 0 239 ;; 240 241 (PrePVT) 242 # All we care about for pre pvt images is that major version number is 243 # even and the 0x10 Board ID flag is set. 244 if (( !(major & 1 ) && (bid_flags & PRE_PVT_BID_FLAG) )); then 245 return 0 246 fi 247 ;; 248 249 (ReleaseCandidate|GeneralRelease) 250 if (( (bid_flags & MP_BID_FLAG) && (major & 1) )); then 251 if [[ ${INSN_TARGET} == GeneralRelease ]]; then 252 # Strip Board ID information for approved for release MP images. 253 sed -i "/board_id/d" "${manifest_json}" 254 fi 255 return 0 256 fi 257 ;; 258 259 (*) 260 die "Unsupported target '${INSN_TARGET:-}': " \ 261 "generation = '${generation}' major = '${major}'" 262 esac 263 264 die "Inconsistent manifest ${manifest_json}: generation = '${generation}'," \ 265 "major = '${major}',board_id_flags = '${bid_flags}' " \ 266 "target = '${INSN_TARGET}'" 267} 268 269# This function accepts two arguments, names of two binary files. 270# 271# It searches the first passed-in file for the first 8 bytes of the second 272# passed in file. The od utility is used to generate full hex dump of the 273# first file (16 bytes per line) and the first 8 bytes of the second file. 274# grep is used to check if the pattern is present in the full dump. 275find_blob_in_blob() { 276 if [[ $# -ne 2 ]]; then 277 die "Usage: find_blob_in_blob <haystack> <needle>" 278 fi 279 280 local main_blob="$1" 281 local pattern_blob="$2" 282 local pattern 283 # Show without offsets, single byte hex, no compression of zero runs. 284 local od_options=("-An" "-tx1" "-v") 285 286 # Get the first 8 bytes of the pattern blob. 287 pattern="$(od "${od_options[@]}" -N8 "${pattern_blob}")" 288 289 # Eliminate all newlines to be able to search the entire body as one unit. 290 if od "${od_options[@]}" "${main_blob}" | \ 291 tr -d '\n' | 292 grep -q -F "${pattern}"; then 293 return 0 294 fi 295 296 return 1 297} 298 299# This function accepts two arguments, names of the two ELF files. 300# 301# The files are searched for test RMA public key patterns - x25519 or p256, 302# both files are supposed to have pattern of one of these keys and not the 303# other. If this holds true the function prints the public key base name. If 304# not both files include the same key, or include more than one key, the 305# function reports failure and exits the script. 306determine_rma_key_base() { 307 if [[ $# -ne 3 ]]; then 308 die "Usage: determine_rma_key_base <rma_key_dir> <rw_a> <rw_b>" 309 fi 310 311 local rma_key_dir="$1" 312 local elfs=( "$2" "$3" ) 313 local base_name="${rma_key_dir}/rma_key_blob" 314 local curve 315 local curves=( "x25519" "p256" ) 316 local elf 317 local key_file 318 local mask=1 319 local result=0 320 local rma_key_base 321 322 for curve in "${curves[@]}"; do 323 key_file="${base_name}.${curve}.test" 324 for elf in "${elfs[@]}"; do 325 if find_blob_in_blob "${elf}" "${key_file}"; then 326 : $(( result |= mask )) 327 fi 328 : $(( mask <<= 1 )) 329 done 330 done 331 332 case "${result}" in 333 (3) curve="x25519";; 334 (12) curve="p256";; 335 (*) die "could not determine key type in the ELF files";; 336 esac 337 338 echo "${base_name}.${curve}" 339} 340 341# Sign GSC RW firmware ELF images into a combined GSC firmware image 342# using the provided production keys and manifests. 343sign_rw() { 344 if [[ $# -ne 9 ]]; then 345 die "Usage: sign_rw <key_file> <manifest> <fuses>" \ 346 "<rma_key_dir> <rw_a> <rw_b> <output> <generation> <image_base>" 347 fi 348 349 local key_file="$1" 350 local manifest_file="$2" 351 local fuses_file="$3" 352 local rma_key_dir="$4" 353 local rws=( "$5" "$6" ) 354 local result_file="$7" 355 local generation="$8" 356 local image_base="$9" 357 local base_name 358 local rma_key_base="" 359 local signer_command_params 360 local temp_dir 361 local prohibited_blobs=() 362 363 temp_dir="$(make_temp_dir)" 364 signer_command_params=(-x "${fuses_file}" --key "${key_file}") 365 366 case "${generation}" in 367 (h) 368 # H1 image might require some tweaking. 369 # If signing a chip factory image (version 0.0.22) do not try figuring 370 # out the RMA keys. 371 local gsc_version 372 373 gsc_version="$(jq '.epoch * 10000 + .major * 100 + .minor' \ 374 "${manifest_file}")" 375 376 if [[ "${gsc_version}" != "22" ]]; then 377 rma_key_base="$(determine_rma_key_base "${rma_key_dir}" "${rws[@]}")" 378 else 379 warn "Ignoring RMA keys for factory branch ${gsc_version}" 380 fi 381 382 # Swap test public RMA server key with the prod version. 383 if [[ -n "${rma_key_base}" ]]; then 384 signer_command_params+=( 385 --swap "${rma_key_base}.test,${rma_key_base}.prod" 386 ) 387 fi 388 389 # Indicate H1 signing. 390 signer_command_params+=( "--b" ) 391 base_name="cr50" 392 ;; 393 (d) 394 # Indicate D1 signing. 395 signer_command_params+=( "--dauntless" "--ihex" ) 396 base_name="ti50" 397 # Key and hashes used in dev, must not leak into prod signed images. 398 prohibited_blobs=( 399 "${rma_key_dir}/rma_test_pub_key.bin" 400 "${rma_key_dir}/arv_2k_test_key_hash.bin" 401 "${rma_key_dir}/arv_4k_test_key_hash.bin" 402 ) 403 ;; 404 (*) 405 die "Unknown generation value \"${generation}\"" 406 ;; 407 esac 408 409 signer_command_params+=(--json "${manifest_file}") 410 411 412 if [[ "${FLAGS_override_keyid}" == "${FLAGS_TRUE}" ]]; then 413 signer_command_params+=(--override-keyid) 414 fi 415 416 for rw in "${rws[@]}"; do 417 local hex_signed="${temp_dir}/hex_signed" 418 local bin_signed="${temp_dir}/bin_signed" 419 local hex_base 420 local blob 421 422 # Make sure output files are not owned by root. 423 touch "${bin_signed}" "${hex_signed}" 424 if ! gsc-codesigner "${signer_command_params[@]}" \ 425 -i "${rw}" -o "${hex_signed}"; then 426 die "gsc-codesigner ${signer_command_params[*]}" \ 427 "-i ${rw} -o ${hex_signed} failed" 428 fi 429 430 if ! objcopy -I ihex "${hex_signed}" -O binary "${bin_signed}"; then 431 die "Failed to convert ${rw} from hex to bin" 432 fi 433 434 if [[ -n "${rma_key_base}" ]]; then 435 if find_blob_in_blob "${bin_signed}" "${rma_key_base}.test"; then 436 die "test RMA key in the signed image!" 437 fi 438 439 if ! find_blob_in_blob "${bin_signed}" "${rma_key_base}.prod"; then 440 die "prod RMA key not in the signed image!" 441 fi 442 fi 443 444 for blob in "${prohibited_blobs[@]}"; do 445 if [[ ! -f ${blob} ]]; then 446 die "${blob} not found in the GSC tarball" 447 fi 448 if find_blob_in_blob "${bin_signed}" "${blob}"; then 449 die "${blob} found in signed image" 450 fi 451 done 452 453 hex_base="$(get_hex_base "${hex_signed}")" 454 paste_bin "${result_file}" "${bin_signed}" "${image_base}" "${hex_base}" 455 done 456 457 if strings "${rw}" | grep -q "DBG/${base_name}"; then 458 die "Will not sign debug image with prod keys" 459 fi 460 461} 462 463# A very crude RO verification function. The key signature found at a fixed 464# offset into the RO blob must match the RO type. Prod keys have bit D2 set to 465# one, dev keys have this bit set to zero. 466# 467# The check is bypassed if the key file directory name includes string 'test'. 468verify_ro() { 469 if [[ $# -ne 2 ]]; then 470 die "Usage: verify_ro <ro_bin> <key_file>" 471 fi 472 473 local ro_bin="$1" 474 local key_file="$2" 475 local key_byte 476 local key_path 477 478 if [[ ! -f "${ro_bin}" ]]; then 479 die "${ro_bin} not a file!" 480 fi 481 482 key_path="$(dirname "${key_file}")" 483 if [[ ${key_path##*/} == *"test"* ]]; then 484 info "Test run, ignoring key type verification" 485 return 0 486 fi 487 488 # Key signature's lowest byte is byte #5 in the line at offset 0001a0. 489 key_byte="$(od -Ax -t x1 -v "${ro_bin}" | awk '/0001a0/ {print $6}')" 490 case "${key_byte}" in 491 (?[4567cdef]) 492 return 0 493 ;; 494 esac 495 496 die "RO key (${key_byte}) in ${ro_bin} does not match type prod" 497} 498 499# This function prepares a full GSC image, consisting of two ROs and two RWs 500# placed at their respective offsets into the resulting blob. It invokes the 501# bs (binary signer) script to actually convert ELF versions of RWs into 502# binaries and sign them. 503# 504# The signed image is placed in the directory named as concatenation of RO and 505# RW version numbers and board ID fields, if set to non-default. The ebuild 506# downloading the tarball from the BCS expects the image to be in that 507# directory. 508sign_gsc_firmware() { 509 if [[ $# -ne 10 ]]; then 510 die "Usage: sign_gsc_firmware <key_file> <manifest> <fuses>" \ 511 "<rma_key_dir> <ro_a> <ro_b> <rw_a> <rw_b> <output> <generation>" 512 fi 513 514 local key_file="$1" 515 local manifest_file="$2" 516 local fuses_file="$3" 517 local rma_key_dir="$4" 518 local ro_a_hex="$5" 519 local ro_b_hex="$6" 520 local rw_a="$7" 521 local rw_b="$8" 522 local output_file="$9" 523 local generation="${10}" 524 local temp_dir 525 local chip_name 526 527 temp_dir="$(make_temp_dir)" 528 529 case "${generation}" in 530 (h) 531 # H1 flash size, image size must match. 532 IMAGE_SIZE="$(( 512 * 1024 ))" 533 IMAGE_BASE="0x40000" 534 chip_name="cr50" 535 ;; 536 (d) 537 # D2 flash size, image size must match. 538 IMAGE_SIZE="$(( 1024 * 1024 ))" 539 IMAGE_BASE="0x80000" 540 chip_name="ti50" 541 ;; 542 esac 543 544 verify_and_prepare_gsc_manifest "${manifest_file}" "${generation}" 545 546 dd if=/dev/zero bs="${IMAGE_SIZE}" count=1 status=none | 547 tr '\000' '\377' > "${output_file}" 548 if [[ "$(stat -c '%s' "${output_file}")" != "${IMAGE_SIZE}" ]]; then 549 die "Failed creating ${output_file}" 550 fi 551 552 if ! sign_rw "${key_file}" "${manifest_file}" "${fuses_file}" \ 553 "${rma_key_dir}" "${rw_a}" "${rw_b}" \ 554 "${output_file}" "${generation}" "${IMAGE_BASE}"; then 555 die "Failed invoking sign_rw for ${rw_a} and ${rw_b}" 556 fi 557 558 local f 559 for f in "${ro_a_hex}" "${ro_b_hex}"; do 560 local hex_base 561 local bin 562 563 hex_base="$(get_hex_base "${f}")" 564 bin="${temp_dir}/bin" 565 566 if [[ -z ${hex_base} ]]; then 567 die "Failed retrieving base address from ${f}" 568 fi 569 570 if ! objcopy -I ihex "${f}" -O binary "${bin}"; then 571 die "Failed to convert ${f} from hex to bin" 572 fi 573 if [[ "${generation}" == "h" ]]; then 574 verify_ro "${bin}" "${key_file}" 575 fi 576 577 paste_bin "${output_file}" "${bin}" "${IMAGE_BASE}" "${hex_base}" 578 done 579 580 # Tell the signer how to rename the @CHIP@ portion of the output. 581 echo "${chip_name}" > "${output_file}.rename" 582 583 info "Image successfully signed to ${output_file}" 584} 585 586# Sign the directory holding GSC firmware. 587sign_gsc_firmware_dir() { 588 if [[ $# -ne 3 ]]; then 589 die "Usage: sign_gsc_firmware_dir <input> <key dir> <output>" 590 fi 591 592 local input="${1%/}" 593 local key_dir="$2" 594 local output="$3" 595 local generation 596 local rw_a 597 local rw_b 598 local manifest_source 599 local manifest_file 600 local key_file 601 local base_name 602 603 manifest_source="${input}/prod.json" 604 manifest_file="${manifest_source}.updated" 605 606 # Verify signing manifest. 607 jq . < "${manifest_source}" > "${manifest_file}" || \ 608 die "basic validation of ${manifest_source} failed" 609 610 611 # Retrieve chip type from the manifest, if present, otherwise use h1. 612 generation="$(jq ".generation" "${input}/prod.json" | sed 's/"//g')" 613 case "${generation}" in 614 (h|null) 615 generation="h" 616 base_name="cr50" 617 rw_a="${input}/ec.RW.elf" 618 rw_b="${input}/ec.RW_B.elf" 619 ;; 620 (d) 621 base_name="ti50" 622 rw_a="${input}/rw_A.hex" 623 rw_b="${input}/rw_B.hex" 624 ;; 625 (*) 626 die "Unknown generation value \"${generation}\" in signing manifest" 627 ;; 628 esac 629 630 key_file="${key_dir}/${base_name}.pem" 631 if [[ ! -e "${key_file}" ]]; then 632 die "Missing key file: ${key_file}" 633 fi 634 635 if [[ -d "${output}" ]]; then 636 output="${output}/${base_name}.bin.prod" 637 fi 638 639 sign_gsc_firmware \ 640 "${key_file}" \ 641 "${manifest_file}" \ 642 "${input}/fuses.xml" \ 643 "${input}" \ 644 "${input}/prod.ro.A" \ 645 "${input}/prod.ro.B" \ 646 "${rw_a}" \ 647 "${rw_b}" \ 648 "${output}" \ 649 "${generation}" 650} 651 652main() { 653 if [[ $# -ne 3 ]]; then 654 flags_help 655 exit 1 656 fi 657 658 local input="${1%/}" 659 local key_dir="$2" 660 local output="$3" 661 662 local signing_instructions="${input}/signing_instructions.sh" 663 664 if [[ -f ${signing_instructions} ]]; then 665 . "${signing_instructions}" 666 else 667 die "${signing_instructions} not found" 668 fi 669 670 if [[ ! -d "${input}" ]]; then 671 die "Missing input directory: ${input}" 672 fi 673 674 sign_gsc_firmware_dir "${input}" "${key_dir}" "${output}" 675} 676main "$@" 677