#!/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 </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