| #!/usr/bin/env bash |
| #;**********************************************************************; |
| # Copyright (c) 2017 - 2018, Intel Corporation |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright notice, |
| # this list of conditions and the following disclaimer in the documentation |
| # and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| # THE POSSIBILITY OF SUCH DAMAGE. |
| #;**********************************************************************; |
| set -u |
| |
| usage_error () |
| { |
| echo "$0: $*" >&2 |
| print_usage >&2 |
| exit 2 |
| } |
| print_usage () |
| { |
| cat <<END |
| Usage: |
| int-log-compiler.sh TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS] |
| END |
| } |
| while test $# -gt 0; do |
| case $1 in |
| --help) print_usage; exit $?;; |
| --) shift; break;; |
| -*) usage_error "invalid option: '$1'";; |
| *) break;; |
| esac |
| shift |
| done |
| |
| # Verify the running shell and OS environment is sufficient to run these tests. |
| sanity_test () |
| { |
| # Check special file |
| if [ ! -e /dev/urandom ]; then |
| echo "Missing file /dev/urandom; exiting" |
| exit 1 |
| fi |
| |
| # Check ps |
| PS_LINES=$(ps -e 2>/dev/null | wc -l) |
| if [ "$PS_LINES" -eq 0 ] ; then |
| echo "Command ps not listing processes; exiting" |
| exit 1 |
| fi |
| |
| if [ -z "$(which tpm_server)" ]; then |
| echo "tpm_server not on PATH; exiting" |
| exit 1 |
| fi |
| |
| if [ -z "$(which ss)" ]; then |
| echo "ss not on PATH; exiting" |
| exit 1 |
| fi |
| } |
| |
| # This function takes a PID as a parameter and determines whether or not the |
| # process is currently running. If the daemon is running 0 is returned. Any |
| # other value indicates that the daemon isn't running. |
| daemon_status () |
| { |
| local pid=$1 |
| |
| if [ $(kill -0 "${pid}" 2> /dev/null) ]; then |
| echo "failed to detect running daemon with PID: ${pid}"; |
| return 1 |
| fi |
| return 0 |
| } |
| |
| # This is a generic function to start a daemon, setup the environment |
| # variables, redirect output to a log file, store the PID of the daemon |
| # in a file and disconnect the daemon from the parent shell. |
| daemon_start () |
| { |
| local daemon_bin="$1" |
| local daemon_opts="$2" |
| local daemon_log_file="$3" |
| local daemon_pid_file="$4" |
| local daemon_env="$5" |
| |
| env ${daemon_env} stdbuf -o0 -e0 ${daemon_bin} ${daemon_opts} > ${daemon_log_file} 2>&1 & |
| local ret=$? |
| local pid=$! |
| if [ ${ret} -ne 0 ]; then |
| echo "failed to start daemon: \"${daemon_bin}\" with env: \"${daemon_env}\"" |
| exit ${ret} |
| fi |
| sleep 1 |
| daemon_status "${pid}" |
| if [ $? -ne 0 ]; then |
| echo "daemon died after successfully starting in background, check " \ |
| "log file: ${daemon_log_file}" |
| return 1 |
| fi |
| echo ${pid} > ${daemon_pid_file} |
| disown ${pid} |
| echo "successfully started daemon: ${daemon_bin} with PID: ${pid}" |
| return 0 |
| } |
| # function to start the simulator |
| # This also that we have a private place to store the NVChip file. Since we |
| # can't tell the simulator what to name this file we must generate a random |
| # directory under /tmp, move to this directory, start the simulator, then |
| # return to the old pwd. |
| simulator_start () |
| { |
| local sim_bin="$1" |
| local sim_port="$2" |
| local sim_log_file="$3" |
| local sim_pid_file="$4" |
| local sim_tmp_dir="$5" |
| # simulator port is a random port between 1024 and 65535 |
| |
| cd ${sim_tmp_dir} |
| daemon_start "${sim_bin}" "-port ${sim_port}" "${sim_log_file}" \ |
| "${sim_pid_file}" "" |
| local ret=$? |
| cd - |
| return $ret |
| } |
| # function to stop a running daemon |
| # This function takes a single parameter: a file containing the PID of the |
| # process to be killed. The PID is extracted and the daemon killed. |
| daemon_stop () |
| { |
| local pid_file=$1 |
| local pid=0 |
| local ret=0 |
| |
| if [ ! -f ${pid_file} ]; then |
| echo "failed to stop daemon, no pid file: ${pid_file}" |
| return 1 |
| fi |
| pid=$(cat ${pid_file}) |
| daemon_status "${pid}" |
| ret=$? |
| if [ ${ret} -ne 0 ]; then |
| echo "failed to detect running daemon with PID: ${pid}"; |
| return ${ret} |
| fi |
| kill ${pid} |
| ret=$? |
| if [ ${ret} -ne 0 ]; then |
| echo "failed to kill daemon process with PID: ${pid}" |
| fi |
| return ${ret} |
| } |
| |
| OS=$(uname) |
| |
| if [ "$OS" == "Linux" ]; then |
| sanity_test |
| fi |
| |
| # Once option processing is done, $@ should be the name of the test executable |
| # followed by all of the options passed to the test executable. |
| TEST_BIN=$(realpath "$1") |
| TEST_DIR=$(dirname "$1") |
| TEST_NAME=$(basename "${TEST_BIN}") |
| |
| # start an instance of the simulator for the test, have it use a random port |
| SIM_LOG_FILE=${TEST_BIN}_simulator.log |
| SIM_PID_FILE=${TEST_BIN}_simulator.pid |
| SIM_TMP_DIR=$(mktemp -d /tmp/tpm_server_XXXXXX) |
| PORT_MIN=1024 |
| PORT_MAX=65534 |
| BACKOFF_FACTOR=2 |
| BACKOFF_MAX=6 |
| BACKOFF=1 |
| |
| sock_tool="unknown" |
| |
| if [ "$OS" == "Linux" ]; then |
| sock_tool="ss -lntp4" |
| elif [ "$OS" == "FreeBSD" ]; then |
| sock_tool="sockstat -l4" |
| fi |
| |
| for i in $(seq ${BACKOFF_MAX}); do |
| SIM_PORT_DATA=$(od -A n -N 2 -t u2 /dev/urandom | awk -v min=${PORT_MIN} -v max=${PORT_MAX} '{print ($1 % (max - min)) + min}') |
| if [ $(expr ${SIM_PORT_DATA} % 2) -eq 1 ]; then |
| SIM_PORT_DATA=$((${SIM_PORT_DATA}-1)) |
| fi |
| SIM_PORT_CMD=$((${SIM_PORT_DATA}+1)) |
| echo "Starting simulator on port ${SIM_PORT_DATA}" |
| simulator_start tpm_server ${SIM_PORT_DATA} ${SIM_LOG_FILE} ${SIM_PID_FILE} ${SIM_TMP_DIR} |
| sleep 1 # give daemon time to bind to ports |
| if [ ! -s ${SIM_PID_FILE} ] ; then |
| echo "Simulator PID file is empty or missing. Giving up." |
| exit 1 |
| fi |
| PID=$(cat ${SIM_PID_FILE}) |
| echo "simulator PID: ${PID}"; |
| ${sock_tool} 2> /dev/null | grep "${PID}" | grep "${SIM_PORT_DATA}" |
| ret_data=$? |
| ${sock_tool} 2> /dev/null | grep "${PID}" | grep "${SIM_PORT_CMD}" |
| ret_cmd=$? |
| if [ \( $ret_data -eq 0 \) -a \( $ret_cmd -eq 0 \) ]; then |
| echo "Simulator with PID ${PID} bound to port ${SIM_PORT_DATA} and " \ |
| "${SIM_PORT_CMD} successfully."; |
| break |
| fi |
| echo "Port conflict? Cleaning up PID: ${PID}" |
| kill "${PID}" |
| BACKOFF=$((${BACKOFF}*${BACKOFF_FACTOR})) |
| echo "Failed to start simulator: port ${SIM_PORT_DATA} or " \ |
| "${SIM_PORT_CMD} probably in use. Retrying in ${BACKOFF}." |
| sleep ${BACKOFF} |
| if [ $i -eq 10 ]; then |
| echo "Failed to start simulator after $i tries. Giving up."; |
| exit 1 |
| fi |
| done |
| |
| while true; do |
| |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_startup |
| if [ $? -ne 0 ]; then |
| echo "TPM_StartUp failed" |
| ret=99 |
| break |
| fi |
| |
| EKPUB_FILE=${TEST_BIN}_ekpub.pem |
| EKCERT_FILE=${TEST_BIN}_ekcert.crt |
| EKCERT_PEM_FILE=${TEST_BIN}_ekcert.pem |
| |
| |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_getek>$EKPUB_FILE |
| if [ $? -ne 0 ]; then |
| echo "TPM_getek failed" |
| ret=99 |
| break |
| fi |
| |
| EKECCPUB_FILE=${TEST_BIN}_ekeccpub.pem |
| EKECCCERT_FILE=${TEST_BIN}_ekecccert.crt |
| EKECCCERT_PEM_FILE=${TEST_BIN}_ekecccert.pem |
| |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_getek_ecc>$EKECCPUB_FILE |
| if [ $? -ne 0 ]; then |
| echo "TPM_getek_ecc failed" |
| ret=99 |
| break |
| fi |
| |
| INTERMEDCA_FILE=${TEST_BIN}_intermedecc-ca |
| ROOTCA_FILE=${TEST_BIN}_root-ca |
| |
| if [ "$OS" == "Linux" ]; then |
| SCRIPTDIR="$(dirname $(realpath $0))/" |
| ${SCRIPTDIR}/ekca/create_ca.sh "${EKPUB_FILE}" "${EKECCPUB_FILE}" "${EKCERT_FILE}" \ |
| "${EKECCCERT_FILE}" "${INTERMEDCA_FILE}" "${ROOTCA_FILE}" >${TEST_BIN}_ca.log 2>&1 |
| if [ $? -ne 0 ]; then |
| echo "ek-cert ca failed" |
| ret=99 |
| break |
| fi |
| fi |
| |
| # Determine the fingerprint of the RSA EK public. |
| FINGERPRINT=$(openssl pkey -pubin -inform PEM -in $EKPUB_FILE -outform DER | sha256sum | cut -f 1 -d ' ') |
| export FAPI_TEST_FINGERPRINT=" { \"hashAlg\" : \"sha256\", \"digest\" : \"$FINGERPRINT\" }" |
| openssl x509 -inform DER -in $EKCERT_FILE -outform PEM -out $EKCERT_PEM_FILE |
| export FAPI_TEST_CERTIFICATE="file:${EKCERT_PEM_FILE}" |
| |
| # Determine the fingerprint of the RSA EK public. |
| FINGERPRINT_ECC=$(openssl pkey -pubin -inform PEM -in $EKECCPUB_FILE -outform DER | sha256sum | cut -f 1 -d ' ') |
| export FAPI_TEST_FINGERPRINT_ECC=" { \"hashAlg\" : \"sha256\", \"digest\" : \"$FINGERPRINT_ECC\" }" |
| openssl x509 -inform DER -in $EKECCCERT_FILE -outform PEM -out $EKECCCERT_PEM_FILE |
| export FAPI_TEST_CERTIFICATE_ECC="file:${EKECCCERT_PEM_FILE}" |
| |
| cat $EKCERT_FILE | \ |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_writeekcert 1C00002 |
| if [ $? -ne 0 ]; then |
| echo "TPM_writeekcert failed" |
| ret=99 |
| break |
| fi |
| |
| cat $EKECCCERT_FILE | \ |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_writeekcert 1C0000A |
| if [ $? -ne 0 ]; then |
| echo "TPM_writeekcert failed" |
| ret=99 |
| fi |
| |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_transientempty |
| if [ $? -ne 0 ]; then |
| echo "TPM transient area not empty => skipping" |
| ret=99 |
| break |
| fi |
| |
| TPMSTATE_FILE1=${TEST_BIN}_state1 |
| TPMSTATE_FILE2=${TEST_BIN}_state2 |
| |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_dumpstate>$TPMSTATE_FILE1 |
| if [ $? -ne 0 ]; then |
| echo "Error during dumpstate" |
| ret=99 |
| break |
| fi |
| |
| echo "Execute the test script" |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| FAPI_TEST_ROOT_CERT=${ROOTCA_FILE}.pem \ |
| G_MESSAGES_DEBUG=all $@ |
| ret=$? |
| echo "Script returned $ret" |
| |
| #We check the state before a reboot to see if transients and NV were chagned. |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_dumpstate>$TPMSTATE_FILE2 |
| if [ $? -ne 0 ]; then |
| echo "Error during dumpstate" |
| ret=99 |
| break |
| fi |
| |
| if [ "$(cat $TPMSTATE_FILE1)" != "$(cat $TPMSTATE_FILE2)" ]; then |
| echo "TPM changed state during test" |
| echo "State before ($TPMSTATE_FILE1):" |
| cat $TPMSTATE_FILE1 |
| echo "State after ($TPMSTATE_FILE2):" |
| cat $TPMSTATE_FILE2 |
| ret=1 |
| break |
| fi |
| |
| break |
| |
| #TODO: Add a tpm-restart/reboot here |
| |
| #We check the state again after a reboot to see if PCR allocations were chagned. |
| env TPM20TEST_TCTI_NAME="socket" \ |
| TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \ |
| TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \ |
| TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \ |
| G_MESSAGES_DEBUG=all ./test/helper/tpm_dumpstate>$TPMSTATE_FILE2 |
| if [ $? -ne 0 ]; then |
| echo "Error during dumpstate" |
| ret=99 |
| break |
| fi |
| |
| if [ "$(cat $TPMSTATE_FILE1)" != "$(cat $TPMSTATE_FILE2)" ]; then |
| echo "TPM changed state during test" |
| echo "State before ($TPMSTATE_FILE1):" |
| cat $TPMSTATE_FILE1 |
| echo "State after ($TPMSTATE_FILE2):" |
| cat $TPMSTATE_FILE2 |
| ret=1 |
| break |
| fi |
| |
| break |
| done |
| |
| # This sleep is sadly necessary: If we kill the tabrmd w/o sleeping for a |
| # second after the test finishes the simulator will die too. Bug in the |
| # simulator? |
| sleep 1 |
| # teardown |
| daemon_stop ${SIM_PID_FILE} |
| rm -rf ${SIM_TMP_DIR} ${SIM_PID_FILE} |
| |
| exit $ret |