#!/bin/sh -u
# Copyright (c) 2011 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.
#
# Usage:  dev_debug_vboot [ --cleanup | DIRECTORY ]
#
# This extracts some useful debugging information about verified boot. A short
# summary is printed on stdout, more detailed information and working files are
# left in a log directory.
#

PATH=/bin:/sbin:/usr/bin:/usr/sbin

TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX)
LOGFILE="${TMPDIR}/noisy.log"
PUBLOGFILE="/var/log/debug_vboot_noisy.log"

ARCH="$(crossystem arch)"
ROOT_BASE=$(rootdev -s -d)

if [ "${ARCH}" = "arm" ]; then
  HD='/dev/mmcblk0'
  PARTITION_PREFIX='p'
else
  HD='/dev/sda'
  PARTITION_PREFIX=''
fi

cleanup() {
  if [ -z "${USE_EXISTING:-}" ]; then
    cp -f "${LOGFILE}" "${PUBLOGFILE}"
    info "exporting log file as ${PUBLOGFILE}"
  fi
  if [ -n "${CLEANUP:-}" ]; then
    cd /
    rm -rf "${TMPDIR}"
  fi
}

die() {
  echo "$*" 1>&2
  exit 1
}

info() {
  echo "$@"
  echo "#" "$@" >> "$LOGFILE"
}

infon() {
  echo -n "$@"
  echo "#" "$@" >> "$LOGFILE"
}

log() {
  echo "+" "$@" >> "$LOGFILE"
  "$@" >> "$LOGFILE" 2>&1
}

loghead() {
  echo "+" "$@" "| head" >> "$LOGFILE"
  "$@" | head >> "$LOGFILE" 2>&1
}

logdie() {
  echo "+" "$@" >> "$LOGFILE"
  die "$@"
}

result() {
  if [ "$?" = "0" ]; then
    info "OK"
  else
    info "FAILED"
  fi
}

require_chromeos_bios() {
  log cgpt show "${HD}"
  log rootdev -s
  log crossystem --all
  log ls -aCF /root
  log ls -aCF /mnt/stateful_partition
}

# Search for files from the FMAP, in the order listed. Return the first one
# found or die if none are there.
find_name() {
  for fn in "$@"; do
    if [ -e "$fn" ]; then
      echo "$fn"
      return
    fi
  done
  echo "+ no files named $@" >> "$LOGFILE"
  exit 1
}

# Iterate through block devices, as soon as the first removable USB device
# which is not mounted as root file system is found - consider it the USB
# flash stick and return its device name.
find_usb_device() {
  dm_dev=$(rootdev | cut -f3 -d/)
  real_dev=$(echo ${ROOT_BASE} | cut -f3 -d/)
  block_dir='/sys/block'
  for d in $(ls ${block_dir}); do
    if [ "$d" = "${dm_dev}" -o "$d" = "${real_dev}" ]; then
      continue  # Skip root file system.
    fi
    if [ "$(readlink -f ${block_dir}/$d | grep usb)" = "" ]; then
      continue # Not a usb device.
    fi
    r=${block_dir}/$d/removable
    if [ -f $r -a "$(cat $r)" = "1" ]; then
      echo /dev/$d
      return
    fi
  done
}

# Here we go...
umask 022
trap cleanup EXIT

# Parse args
if [ -n "${1:-}" ]; then
  if [ "$1" = "--cleanup" ]; then
    CLEANUP=1
  else
    TMPDIR="$1"
    [ -d ${TMPDIR} ] || die "${TMPDIR} doesn't exist"
    USE_EXISTING=yes
  fi
fi

[ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1
cd ${TMPDIR} || exit 1
echo "$0 $*" > "$LOGFILE"
log date
echo "Saving verbose log as $LOGFILE"

BIOS=bios.rom

# Find BIOS and kernel images
if [ -n "${USE_EXISTING:-}" ]; then
  info "Using images in $(pwd)/"
else
  require_chromeos_bios
  info "Extracting BIOS image from flash..."
  log flashrom -p internal:bus=spi --wp-status
  log flashrom -p internal:bus=spi -r ${BIOS}

  HD_KERN_A="${HD}${PARTITION_PREFIX}2"
  HD_KERN_B="${HD}${PARTITION_PREFIX}4"

  if [ "${ROOT_BASE}" != "${HD}" ]; then
    # Not running off HD, must be ruining off USB.
    USB_KERN_A="${ROOT_BASE}2"
  else
    USB_DEV="$(find_usb_device)"
    if [ -n "${USB_DEV}" ]; then
      USB_KERN_A="${USB_DEV}2"
    fi
  fi
  info "Extracting kernel images from drives..."
  log dd if=${HD_KERN_A} of=hd_kern_a.blob
  log dd if=${HD_KERN_B} of=hd_kern_b.blob
  if [ -n "${USB_KERN_A:-}" ]; then
    log dd if=${USB_KERN_A} of=usb_kern_a.blob
  fi
fi

# Make sure we have something to work on
[ -f "$BIOS" ] || logdie "no BIOS image found"
ls *kern*.blob >/dev/null 2>&1 || logdie "no kernel images found"

info "Extracting BIOS components..."
log dump_fmap -x ${BIOS} || logdie "Unable to extract BIOS components"

# Find the FMAP regions we're interested in. Look first for the new names, then
# the old names.
area_gbb=$(find_name       GBB       GBB_Area) || \
  logdie "no area_gbb"
area_vblock_a=$(find_name  VBLOCK_A  Firmware_A_Key) || \
  logdie "no area_vblock_a"
area_vblock_b=$(find_name  VBLOCK_B  Firmware_B_Key) || \
  logdie "no area_vblock_b"
area_fw_main_a=$(find_name FW_MAIN_A Firmware_A_Data) || \
  logdie "no area_fw_main_a"
area_fw_main_b=$(find_name FW_MAIN_B Firmware_B_Data) || \
  logdie "no area_fw_main_a"

info "Pulling root and recovery keys from GBB..."
log gbb_utility -g --rootkey rootkey.vbpubk --recoverykey recoverykey.vbpubk \
  "$area_gbb" || logdie "Unable to extract keys from GBB"
log vbutil_key --unpack rootkey.vbpubk
log vbutil_key --unpack recoverykey.vbpubk

infon "Verify firmware A with root key... "
log vbutil_firmware --verify "$area_vblock_a" --signpubkey rootkey.vbpubk \
  --fv "$area_fw_main_a" --kernelkey kernel_subkey_a.vbpubk ; result
infon "Verify firmware B with root key... "
log vbutil_firmware --verify "$area_vblock_b" --signpubkey rootkey.vbpubk \
  --fv "$area_fw_main_b" --kernelkey kernel_subkey_b.vbpubk ; result

for key in kernel_subkey_a.vbpubk kernel_subkey_b.vbpubk; do
  infon "Test $key... "
  log vbutil_key --unpack $key ; result
done

for keyblock in *kern*.blob; do
  infon "Test $keyblock... "
  log vbutil_keyblock --unpack $keyblock ; result
  loghead od -Ax -tx1 $keyblock
done

# Test each kernel with each key
for key in kernel_subkey_a.vbpubk kernel_subkey_b.vbpubk recoverykey.vbpubk; do
  for kern in *kern*.blob; do
    infon "Verify $kern with $key... "
    log vbutil_kernel --verify $kern --signpubkey $key ; result
  done
done
