#!/bin/sh -u
# Copyright (c) 2010 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.
#
# Test the chromeos TPM recovery script by faking the entire execution
# environment.

rm -rf tpm-recovery-test-workdir
mkdir  tpm-recovery-test-workdir
cd     tpm-recovery-test-workdir

test_kind=
if [ $# -ge 1 ]; then
  test_kind="$1"
fi

if [ "$test_kind" != "" -a "$test_kind" != "fake" ]; then
  echo "$0: usage: $0 [fake]"
  echo "With fake as the argument, use a simulated TPM instead of the real one"
fi

if [ "$test_kind" = "fake" ]; then
  export USR_BIN=.
  export USR_SBIN=.
  export USR_LOCAL_BIN=.
  export USR_LOCAL_SBIN=.
  export DOT_RECOVERY=.recovery
  export ACPI_DIR=.
  ctr=../chromeos-tpm-recovery
  tpmc=./tpmc
else
  ctr=chromeos-tpm-recovery
  tpmc=tpmc
fi

# For simplicity, build the permanent environment as if we prepared to run the
# fake test, even if we're running the test on a real TPM.

echo > .recovery
echo 3 > BINF.0
echo 0 > CHSW

export NVRAM_SPACE_OVERHEAD=200
space_overhead=$NVRAM_SPACE_OVERHEAD

# build tpmc
cat > tpmc <<"EOF"
#!/bin/sh -u
# Fake tpmc program

definespace () {
  index=$2
  size=$3
  permissions=$4
  space_overhead=$NVRAM_SPACE_OVERHEAD

  if [ -e space.$index.data -a -e tpm-owned ]; then
    echo "cannot redefine space without auth"
  fi

  totalsize=$(( $size + $space_overhead ))
  free=$(cat nvram.freespace)

  if [ $totalsize -gt $free ]; then
    return 17  # NO_SPACE
  fi

  if [ $index != 0xf004 ]; then
    echo $size > space.$index.size
    echo $permissions > space.$index.perm
    for i in $(seq 1 $(($size))); do
      echo -n "ff " >> space.$index.data
    done
    echo $(( $free - $totalsize )) > nvram.freespace
  fi
  return 0
}

case $1 in

  clear)
    rm -f tpm-owned
  ;;

  enable)
    # boring
  ;;

  activate)
    # boring
  ;;

  definespace)
    definespace $*
  ;;

  getp)
    echo space blah has permissions $(cat space.$2.perm)
  ;;

  read)
    index=$2
    size=$3
    maxsize=$(cat space.$index.size)
    if [ $(($size > $maxsize)) -eq 1  ]; then
      echo "size $size too large for space (max is $maxsize)"
      exit 1
    fi
    dd if=space.$index.data bs=1 count=$(($3 * 3)) 2> /dev/null
  ;;

  write)
    args="$@"
    index=$2
    bytes="$(echo $args | sed 's/[^ ]* [^ ]* //')"
    size=$(echo $bytes | wc -w)
    maxsize=$(cat space.$index.size)
    if [ $(($size > $maxsize)) -eq 1  ]; then
      echo "size $size too large for space (max is $(($maxsize)))"
      exit 1
    fi
    re=$(echo "$bytes " | sed 's/././g')
    sed "s/$re/$bytes /" < space.$index.data > _tmp_
    mv _tmp_ space.$index.data
  ;;

  getpf)
    echo "disable 0"
    echo "deactivated 0"
    echo "nvLocked 1"
    echo "physicalPresenceLifetimeLock 1"
    echo "physicalPresenceHWEnable 0"
    echo "physicalPresenceCMDEnable 1"
  ;;

  getvf)
    echo "bGlobalLock 1"
    echo "physicalPresence 1"
    echo "physicalPresenceLock 0"
  ;;

  ppfin)
    # boring
  ;;

  ppon)
    # boring
  ;;

  *)
    echo "tpmc: invalid command $1"
    exit 1
  ;;
esac

EOF

# build nvtool
cat > tpm-nvtool <<"EOF"
#!/bin/sh -u

space_overhead=$NVRAM_SPACE_OVERHEAD

print_space () {
  local index=$1
  printf "# NV Index 0x%08x" $(( $index ))
  echo " uninteresting random garbage"
  echo " further random garbage"
  echo ""
}

if [ "$1" = "--release" ]; then
  if [ "$2" != "--index" -o \
       "$4" != "--owner_password" ]; then
    echo "sorry, picky tpm-nvtool"
    exit 1
  fi
  index=$3
  if [ ! -f tpm-owned ]; then
    echo "tpm is unowned"
    exit 1
  fi
  size=$(cat space.$index.size)
  free=$(cat nvram.freespace)
  rm space.$index.*
  echo $(( $size + $space_overhead + $free )) > nvram.freespace
elif [ "$1" = "--list" ]; then
  for s in space.*.data; do
    print_space $(echo $s | sed -e "s/[^.]*\.//" -e "s/\..*//")
  done
fi
EOF

# build tpm_takeownership
cat > tpm_takeownership <<"EOF"
#!/bin/sh -u
if [ -f tpm-owned ]; then
  echo "tpm is already owned"
  exit 1
fi
echo > tpm-owned
EOF

# build tcsd
cat > tcsd <<"EOF"
#!/bin/sh -u
trap "{ rm tcsd_is_running; }" EXIT
echo > tcsd_is_running
sleep 365d
EOF

tcsd_pid=0

start_tcsd () {
  if [ $tcsd_pid -ne 0 ]; then
    echo TCSD is already started
    exit 1
  fi
  tcsd -f &
  tcsd_pid=$!
  sleep 2
}

stop_tcsd () {
  if [ $tcsd_pid -eq 0 ]; then
    echo TCSD is already stopped
    exit 1
  fi
  kill $tcsd_pid
  sleep 0.5
  kill $tcsd_pid > /dev/null 2>&1
  sleep 0.5
  wait $tcsd_pid > /dev/null 2>&1  # we trust that tcsd will agree to die
  tcsd_pid=0
}

tpm_clear_and_reenable () {
  tpmc clear
  tpmc enable
  tpmc activate
}

takeownership () {
  if [ "$test_kind" = "fake" ]; then
    touch tpm_owned
  else
    tpm_clear_and_reenable
    start_tcsd
    tpm_takeownership -y -z
    stop_tcsd
  fi
}

remove_chromeos_spaces () {
  if [ "$test_kind" = "fake" ]; then
    rm -f space.*
    echo 1500 > nvram.freespace
  else
    takeownership
    start_tcsd
    tpm-nvtool --release --index 0x1007 --owner_password ""
    tpm-nvtool --release --index 0x1008 --owner_password ""
    stop_tcsd
    tpm_clear_and_reenable
  fi
}

chmod 755 tpmc tpm-nvtool tpm_takeownership tcsd

echo "starting test, results in $(pwd)/log"
echo "starting TPM recovery test" > log

if ps ax | grep "tcs[d]"; then
  echo "a tcsd is process appears to be running, please kill it first"
  exit 1
fi

# normal run
test_normal_run () {
  echo "TEST: normal run" >> log

  remove_chromeos_spaces
  $tpmc definespace 0x1007 0xa 0x8001
  $tpmc definespace 0x1008 0xd 0x1
  $tpmc write 0x1008 01 4c 57 52 47
  takeownership

  $ctr log
}

# Kernel space with wrong ID
test_wrong_id () {
  echo "TEST: bad kernel space ID" >> log

  remove_chromeos_spaces
  $tpmc definespace 0x1007 0xa 0x8001
  $tpmc definespace 0x1008 0xd 0x1
  takeownership

  $ctr log
}

# Kernel space with wrong size
test_wrong_size () {
  echo "TEST: bad kernel space size" >> log

  remove_chromeos_spaces
  $tpmc definespace 0x1007 0xa 0x8001
  $tpmc definespace 0x1008 0xc 0x1
  takeownership

  $ctr log
}

# Kernel space with wrong size AND bogus space to exhaust nvram
test_wrong_size_hog () {
  echo "TEST: bad kernel space size and no room" >> log

  remove_chromeos_spaces
  $tpmc definespace 0x1007 0xa 0x8001
  $tpmc definespace 0x1008 0x1 0x1
  if [ "$test_kind" = "fake" ]; then
    space_hog_size=$(( $(cat nvram.freespace) - $space_overhead - 1 ))
    echo "remaining $(cat nvram.freespace) bytes" >> log
  else
    space_hog_size=$(( $(tpm-nvsize) - 2 ))
  fi
  echo "hogging $(( $space_hog_size )) bytes" >> log
  $tpmc definespace 0xcafe $(printf "0x%x" $space_hog_size) 0x1 \
    || echo "hogging failed!" >> log
  takeownership

  $ctr log
}

test_normal_run
test_wrong_id
test_wrong_size
test_wrong_size_hog

echo "test completed" >> log
echo "test completed"
