#!/bin/bash
# Copyright 2020 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.

# Abort on error.
set -e -o pipefail

# Determine script directory.
SCRIPT_DIR=$(dirname "$0")

# Load common constants and variables.
. "${SCRIPT_DIR}/common.sh"

usage() {
  cat <<EOF
Usage: ${PROG} <input_firmware> <key_dir> <output_firmware>

Signs <input_firmware> with keys in <key_dir>. Outputs signed firmware to
<output_firmware>.  The <input_firmware> and <output_firmware> paths should not
be the same.

For detail, reference the AMD documentation titled "OEM PSP VERSTAGE
BL FW Signing Key Pair Generation and Certificate Request Process"
http://dr/corp/drive/folders/1ySJyDgbH73W1lqrhxMvM9UYl5TtJt_mw

EOF

  if [[ $# -ne 0 ]]; then
    echo "$*" >&2
    exit 1
  else
    exit 0
  fi
}

# Check the arguments to make sure we have the correct number.
if [[ $# -lt 3 ]]; then
  usage "Error: Too few arguments"
fi
if [[ $# -gt 3 ]]; then
  usage "Error: Too many arguments"
fi

read_byte() {
  local position="$1"
  local file="$2"

  dd if="${file}" bs=1 count=1 skip="$((16#${position}))" status=none |
    hexdump -e '/1 "%X"'
}

write_byte() {
  local position="$1"
  local value="$2"
  local file="$3"

  printf "\x${value}" |
    dd of="${file}" bs=1 count=1 seek="${position}" conv=notrunc status=none
}

read_dword_le() {
  local position
  position=$(printf -- "%d" "$1")
  local file="$2"
  read_byte "${position}" "${file}"
  read_byte $((position + 1)) "${file}"
  read_byte $((position + 2)) "${file}"
  read_byte $((position + 3))
}

write_dword_le() {
  local position
  position=$(printf -- "%d" "$1")
  local value
  value=$(printf -- "%08x" "$2")
  local file="$3"

  write_byte "${position}" "${value:6:2}" "$file"
  write_byte $((position + 1)) "${value:4:2}" "$file"
  write_byte $((position + 2)) "${value:2:2}" "$file"
  write_byte $((position + 3)) "${value:0:2}" "$file"
}

filesize() {
  local file="$1"
  stat -c %s -- "${file}"
}

copy_key_id() {
  local input_file="$1"
  local input_offset
  input_offset=$(printf -- "%d" "$2")
  local output_file="$3"
  local output_offset
  output_offset=$(printf -- "%d" "$4")

  local id_size=16

  dd if="${input_file}" skip="${input_offset}" \
    of="${output_file}" seek="${output_offset}" \
    bs=1 count="${id_size}" conv=notrunc status=none
}

KEYNAME=psp_verstagebl_fw_signing

main() {
  local input_firmware="$1"
  local key_dir="$2"
  local output_firmware="$3"

  if [[ "${input_firmware}" == "${output_firmware}" ]]; then
    usage "Error: input and output files must not be the same"
  fi

  local amd_key="${key_dir}/${KEYNAME}.stkn"
  local hashtype
  local sig_size
  local temp_sig
  local temp_fw
  local fw_size
  local image_size

  temp_sig=$(make_temp_file)
  temp_fw=$(make_temp_file)

  # TODO: Differentiate sizes for Picasso vs later chips
  hashtype="-sha256"
  sig_size=256

  fw_size="$(($(filesize "${input_firmware}")))"
  fw_minus_header_size="$((fw_size - 256))"
  image_size="$((fw_size + sig_size))"

  cp "${input_firmware}" "${temp_fw}"

  # Refer to Appendix D in the AMD BIOS Signing Key Pair and Certification
  # Process document for what needs to be changed in the psp_verstage header.
  write_dword_le "0x14" "${fw_minus_header_size}" "${temp_fw}"
  write_dword_le "0x30" "1" "${temp_fw}"
  write_dword_le "0x6c" "${image_size}" "${temp_fw}"

  # TODO: Need the PSP verstage signing token from AMD to verify.
  copy_key_id "${amd_key}" "0x04" "${temp_fw}" "0x38"

  local cmd=(
    openssl dgst
    "${hashtype}"
    -sign "${key_dir}/${KEYNAME}.pem"
    -sigopt rsa_padding_mode:pss
    -sigopt rsa_pss_saltlen:-1
    -out "${temp_sig}"
  )

  "${cmd[@]}" "${temp_fw}"

  cat "${temp_fw}" "${temp_sig}" >"${output_firmware}"

  echo "Complete"
}

main "$@"
