#!/bin/bash

# Copyright (c) 2012 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.

CROS_LOG_PREFIX=${0##*/}

SCRIPT_ROOT=$(dirname "$(readlink -f "$0")")
. "${SCRIPT_ROOT}/build_library/build_common.sh" || exit 1

# Developer-visible flags.
DEFINE_string board "${DEFAULT_BOARD}" \
  "The board to build an image for."
DEFINE_string image "" \
  "Source release image to use (${CHROMEOS_RECOVERY_IMAGE_NAME} by default)."
DEFINE_string baselines "" \
  "Directory to load security baselines from (default from cros-signing)."
DEFINE_string vboot_hash "" \
  "The git rev of the vboot tree to checkout (default to the signer hash)."

FLAGS_HELP="USAGE: security_test_image [flags]
This script is used to run security tests on a Chrome OS images.

Note: You probably will need an internal checkout by default for these
      tests to be useful.  You can provide your own baselines, but you
      can certainly provide your own set of configs.

Note: These tests will fail on dev images.  They are designed to
      check recovery images only.
"
show_help_if_requested "$@"

# Parse command line.
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# Only now can we die on error.  shflags functions leak non-zero error codes,
# so will die prematurely if 'switch_to_strict_mode' is specified before now.
switch_to_strict_mode

SIGNER_DIR="${CHROOT_TRUNK_DIR}/cros-signing"
SIGNER_CONFIG="${SIGNER_DIR}/signer/configs/cros_common.config"
SIGNING_TOOLS_DIR="${SIGNER_DIR}/signer/signingtools-bin"
SECURITY_BASELINE_DIR="${SIGNER_DIR}/security_test_baselines"
VBOOT_SRC_DIR="${CHROOT_TRUNK_DIR}/src/platform/vboot_reference/.git"
VBOOT_TMP_DIR="$(mktemp -d -u -t --tmpdir vboot.XXXXXX)"
VBOOT_DIR="${VBOOT_TMP_DIR}/scripts/image_signing"

# No security baselines provided.  Use the standard one.
if [[ -z ${FLAGS_baselines} ]]; then
  FLAGS_baselines=${SECURITY_BASELINE_DIR}
  if [[ ! -d ${FLAGS_baselines} ]]; then
    if [[ ! -d ${SIGNER_DIR} ]]; then
      warn "Skipping security tests with public manifest"
      exit 0
    else
      die "Could not locate security baselines from" \
          "${FLAGS_baselines} with private manifest"
    fi
  fi
fi
info "Loading baselines from ${FLAGS_baselines}"

# No image was provided.  Use the standard latest image.
if [[ -z ${FLAGS_image} ]]; then
  DEFAULT_IMAGE_DIR=$("${SCRIPT_ROOT}"/get_latest_image.sh \
                        --board="${FLAGS_board}")
  FLAGS_image="${DEFAULT_IMAGE_DIR}/${CHROMEOS_RECOVERY_IMAGE_NAME}"
fi
info "Using ${FLAGS_image}"

# See what vboot git rev we need.
if [[ -z ${FLAGS_vboot_hash} ]]; then
  FLAGS_vboot_hash=$(sed -n '/^vboot_stable_hash/s:.*=[[:space:]]*::p' \
    "${SIGNER_CONFIG}")
fi
if [[ -z ${FLAGS_vboot_hash} ]]; then
  die "Could not detect vboot_stable_hash in ${SIGNER_CONFIG}"
fi
info "Using vboot_reference.git rev ${FLAGS_vboot_hash}"
git_fail=false
git clone -q -s -n "${VBOOT_SRC_DIR}" "${VBOOT_TMP_DIR}" || git_fail=true
pushd "${VBOOT_TMP_DIR}" >/dev/null
git checkout -q ${FLAGS_vboot_hash} || git_fail=true
popd >/dev/null
if ${git_fail}; then
  rm -rf "${VBOOT_TMP_DIR}"
  die "checking out ${FLAGS_vboot_hash} from ${VBOOT_SRC_DIR} failed"
fi

# The signer uses these binaries, so we should too.
PATH="${SIGNING_TOOLS_DIR}:${PATH}"

# Run all the security tests.
failed_count=0
run_check() {
  local cmd=(
    "${VBOOT_DIR}/ensure_$1.sh"
    "${FLAGS_image}"
  )
  if [[ $# -ge 2 ]]; then
    cmd+=( "${FLAGS_baselines}/ensure_$1.config" )
  fi
  info "Running ensure_$1.sh"
  if ! "${cmd[@]}"; then
    error "$1: test failed"
    : $(( ++failed_count ))
  fi
}

sec_checks=(
  no_nonrelease_files
  sane_lsb-release
  secure_kernelparams
)
for check in "${sec_checks[@]}"; do
  run_check "${check}" "${check}"
done

sec_checks=(
  not_ASAN
  # This test requires an update key to be inserted
  # first which the signer itself currently does.
  #update_verification
)
for check in "${sec_checks[@]}"; do
  run_check "${check}"
done

rm -rf "${VBOOT_TMP_DIR}"
if [[ ${failed_count} -gt 0 ]]; then
  die_notrace "${failed_count} tests failed"
else
  info "All tests passed!"
fi
