1#!/usr/bin/env bash
2#;**********************************************************************;
3# Copyright (c) 2017 - 2018, Intel Corporation
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# 1. Redistributions of source code must retain the above copyright notice,
10# this list of conditions and the following disclaimer.
11#
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26# THE POSSIBILITY OF SUCH DAMAGE.
27#;**********************************************************************;
28set -u
29
30usage_error ()
31{
32    echo "$0: $*" >&2
33    print_usage >&2
34    exit 2
35}
36print_usage ()
37{
38    cat <<END
39Usage:
40    int-log-compiler.sh TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
41END
42}
43while test $# -gt 0; do
44    case $1 in
45    --help) print_usage; exit $?;;
46    --) shift; break;;
47    -*) usage_error "invalid option: '$1'";;
48     *) break;;
49    esac
50    shift
51done
52
53# Verify the running shell and OS environment is sufficient to run these tests.
54sanity_test ()
55{
56    # Check special file
57    if [ ! -e /dev/urandom ]; then
58        echo  "Missing file /dev/urandom; exiting"
59        exit 1
60    fi
61
62    # Check ps
63    PS_LINES=$(ps -e 2>/dev/null | wc -l)
64    if [ "$PS_LINES" -eq 0 ] ; then
65        echo "Command ps not listing processes; exiting"
66        exit 1
67    fi
68
69    if [ -z "$(which tpm_server)" ]; then
70        echo "tpm_server not on PATH; exiting"
71        exit 1
72    fi
73
74    if [ -z "$(which ss)" ]; then
75        echo "ss not on PATH; exiting"
76        exit 1
77    fi
78}
79
80# This function takes a PID as a parameter and determines whether or not the
81# process is currently running. If the daemon is running 0 is returned. Any
82# other value indicates that the daemon isn't running.
83daemon_status ()
84{
85    local pid=$1
86
87    if [ $(kill -0 "${pid}" 2> /dev/null) ]; then
88        echo "failed to detect running daemon with PID: ${pid}";
89        return 1
90    fi
91    return 0
92}
93
94# This is a generic function to start a daemon, setup the environment
95# variables, redirect output to a log file, store the PID of the daemon
96# in a file and disconnect the daemon from the parent shell.
97daemon_start ()
98{
99    local daemon_bin="$1"
100    local daemon_opts="$2"
101    local daemon_log_file="$3"
102    local daemon_pid_file="$4"
103    local daemon_env="$5"
104
105    env ${daemon_env} stdbuf -o0 -e0 ${daemon_bin} ${daemon_opts} > ${daemon_log_file} 2>&1 &
106    local ret=$?
107    local pid=$!
108    if [ ${ret} -ne 0 ]; then
109        echo "failed to start daemon: \"${daemon_bin}\" with env: \"${daemon_env}\""
110        exit ${ret}
111    fi
112    sleep 1
113    daemon_status "${pid}"
114    if [ $? -ne 0 ]; then
115        echo "daemon died after successfully starting in background, check " \
116             "log file: ${daemon_log_file}"
117        return 1
118    fi
119    echo ${pid} > ${daemon_pid_file}
120    disown ${pid}
121    echo "successfully started daemon: ${daemon_bin} with PID: ${pid}"
122    return 0
123}
124# function to start the simulator
125# This also that we have a private place to store the NVChip file. Since we
126# can't tell the simulator what to name this file we must generate a random
127# directory under /tmp, move to this directory, start the simulator, then
128# return to the old pwd.
129simulator_start ()
130{
131    local sim_bin="$1"
132    local sim_port="$2"
133    local sim_log_file="$3"
134    local sim_pid_file="$4"
135    local sim_tmp_dir="$5"
136    # simulator port is a random port between 1024 and 65535
137
138    cd ${sim_tmp_dir}
139    daemon_start "${sim_bin}" "-port ${sim_port}" "${sim_log_file}" \
140        "${sim_pid_file}" ""
141    local ret=$?
142    cd -
143    return $ret
144}
145# function to stop a running daemon
146# This function takes a single parameter: a file containing the PID of the
147# process to be killed. The PID is extracted and the daemon killed.
148daemon_stop ()
149{
150    local pid_file=$1
151    local pid=0
152    local ret=0
153
154    if [ ! -f ${pid_file} ]; then
155        echo "failed to stop daemon, no pid file: ${pid_file}"
156        return 1
157    fi
158    pid=$(cat ${pid_file})
159    daemon_status "${pid}"
160    ret=$?
161    if [ ${ret} -ne 0 ]; then
162        echo "failed to detect running daemon with PID: ${pid}";
163        return ${ret}
164    fi
165    kill ${pid}
166    ret=$?
167    if [ ${ret} -ne 0 ]; then
168        echo "failed to kill daemon process with PID: ${pid}"
169    fi
170    return ${ret}
171}
172
173OS=$(uname)
174
175if [ "$OS" == "Linux" ]; then
176    sanity_test
177fi
178
179# Once option processing is done, $@ should be the name of the test executable
180# followed by all of the options passed to the test executable.
181TEST_BIN=$(realpath "$1")
182TEST_DIR=$(dirname "$1")
183TEST_NAME=$(basename "${TEST_BIN}")
184
185# start an instance of the simulator for the test, have it use a random port
186SIM_LOG_FILE=${TEST_BIN}_simulator.log
187SIM_PID_FILE=${TEST_BIN}_simulator.pid
188SIM_TMP_DIR=$(mktemp -d /tmp/tpm_server_XXXXXX)
189PORT_MIN=1024
190PORT_MAX=65534
191BACKOFF_FACTOR=2
192BACKOFF_MAX=6
193BACKOFF=1
194
195sock_tool="unknown"
196
197if [ "$OS" == "Linux" ]; then
198    sock_tool="ss -lntp4"
199elif [ "$OS" == "FreeBSD" ]; then
200    sock_tool="sockstat -l4"
201fi
202
203for i in $(seq ${BACKOFF_MAX}); do
204    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}')
205    if [ $(expr ${SIM_PORT_DATA} % 2) -eq 1 ]; then
206        SIM_PORT_DATA=$((${SIM_PORT_DATA}-1))
207    fi
208    SIM_PORT_CMD=$((${SIM_PORT_DATA}+1))
209    echo "Starting simulator on port ${SIM_PORT_DATA}"
210    simulator_start tpm_server ${SIM_PORT_DATA} ${SIM_LOG_FILE} ${SIM_PID_FILE} ${SIM_TMP_DIR}
211    sleep 1 # give daemon time to bind to ports
212    if [ ! -s ${SIM_PID_FILE} ] ; then
213        echo "Simulator PID file is empty or missing. Giving up."
214        exit 1
215    fi
216    PID=$(cat ${SIM_PID_FILE})
217    echo "simulator PID: ${PID}";
218    ${sock_tool} 2> /dev/null | grep "${PID}" | grep "${SIM_PORT_DATA}"
219    ret_data=$?
220    ${sock_tool} 2> /dev/null | grep "${PID}" | grep "${SIM_PORT_CMD}"
221    ret_cmd=$?
222    if [ \( $ret_data -eq 0 \) -a \( $ret_cmd -eq 0 \) ]; then
223        echo "Simulator with PID ${PID} bound to port ${SIM_PORT_DATA} and " \
224             "${SIM_PORT_CMD} successfully.";
225        break
226    fi
227    echo "Port conflict? Cleaning up PID: ${PID}"
228    kill "${PID}"
229    BACKOFF=$((${BACKOFF}*${BACKOFF_FACTOR}))
230    echo "Failed to start simulator: port ${SIM_PORT_DATA} or " \
231         "${SIM_PORT_CMD} probably in use. Retrying in ${BACKOFF}."
232    sleep ${BACKOFF}
233    if [ $i -eq 10 ]; then
234        echo "Failed to start simulator after $i tries. Giving up.";
235        exit 1
236    fi
237done
238
239while true; do
240
241env TPM20TEST_TCTI_NAME="socket" \
242    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
243    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
244    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
245    G_MESSAGES_DEBUG=all ./test/helper/tpm_startup
246if [ $? -ne 0 ]; then
247    echo "TPM_StartUp failed"
248    ret=99
249    break
250fi
251
252EKPUB_FILE=${TEST_BIN}_ekpub.pem
253EKCERT_FILE=${TEST_BIN}_ekcert.crt
254EKCERT_PEM_FILE=${TEST_BIN}_ekcert.pem
255
256
257env TPM20TEST_TCTI_NAME="socket" \
258    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
259    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
260    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
261    G_MESSAGES_DEBUG=all ./test/helper/tpm_getek>$EKPUB_FILE
262if [ $? -ne 0 ]; then
263    echo "TPM_getek failed"
264    ret=99
265    break
266fi
267
268EKECCPUB_FILE=${TEST_BIN}_ekeccpub.pem
269EKECCCERT_FILE=${TEST_BIN}_ekecccert.crt
270EKECCCERT_PEM_FILE=${TEST_BIN}_ekecccert.pem
271
272env TPM20TEST_TCTI_NAME="socket" \
273    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
274    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
275    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
276    G_MESSAGES_DEBUG=all ./test/helper/tpm_getek_ecc>$EKECCPUB_FILE
277if [ $? -ne 0 ]; then
278    echo "TPM_getek_ecc failed"
279    ret=99
280    break
281fi
282
283INTERMEDCA_FILE=${TEST_BIN}_intermedecc-ca
284ROOTCA_FILE=${TEST_BIN}_root-ca
285
286if [ "$OS" == "Linux" ]; then
287    SCRIPTDIR="$(dirname $(realpath $0))/"
288    ${SCRIPTDIR}/ekca/create_ca.sh "${EKPUB_FILE}" "${EKECCPUB_FILE}" "${EKCERT_FILE}" \
289                               "${EKECCCERT_FILE}" "${INTERMEDCA_FILE}" "${ROOTCA_FILE}" >${TEST_BIN}_ca.log 2>&1
290    if [ $? -ne 0 ]; then
291        echo "ek-cert ca failed"
292        ret=99
293        break
294    fi
295fi
296
297# Determine the fingerprint of the RSA EK public.
298FINGERPRINT=$(openssl pkey -pubin -inform PEM -in $EKPUB_FILE -outform DER | sha256sum  | cut -f 1 -d ' ')
299export FAPI_TEST_FINGERPRINT="  { \"hashAlg\" : \"sha256\", \"digest\" : \"$FINGERPRINT\" }"
300openssl x509 -inform DER -in $EKCERT_FILE -outform PEM -out $EKCERT_PEM_FILE
301export FAPI_TEST_CERTIFICATE="file:${EKCERT_PEM_FILE}"
302
303# Determine the fingerprint of the RSA EK public.
304FINGERPRINT_ECC=$(openssl pkey -pubin -inform PEM -in $EKECCPUB_FILE -outform DER | sha256sum  | cut -f 1 -d ' ')
305export FAPI_TEST_FINGERPRINT_ECC="  { \"hashAlg\" : \"sha256\", \"digest\" : \"$FINGERPRINT_ECC\" }"
306openssl x509 -inform DER -in $EKECCCERT_FILE -outform PEM -out $EKECCCERT_PEM_FILE
307export FAPI_TEST_CERTIFICATE_ECC="file:${EKECCCERT_PEM_FILE}"
308
309cat $EKCERT_FILE | \
310env TPM20TEST_TCTI_NAME="socket" \
311    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
312    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
313    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
314    G_MESSAGES_DEBUG=all ./test/helper/tpm_writeekcert 1C00002
315if [ $? -ne 0 ]; then
316    echo "TPM_writeekcert failed"
317    ret=99
318    break
319fi
320
321cat $EKECCCERT_FILE | \
322env TPM20TEST_TCTI_NAME="socket" \
323    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
324    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
325    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
326    G_MESSAGES_DEBUG=all ./test/helper/tpm_writeekcert 1C0000A
327if [ $? -ne 0 ]; then
328    echo "TPM_writeekcert failed"
329    ret=99
330fi
331
332env TPM20TEST_TCTI_NAME="socket" \
333    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
334    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
335    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
336    G_MESSAGES_DEBUG=all ./test/helper/tpm_transientempty
337if [ $? -ne 0 ]; then
338    echo "TPM transient area not empty => skipping"
339    ret=99
340    break
341fi
342
343TPMSTATE_FILE1=${TEST_BIN}_state1
344TPMSTATE_FILE2=${TEST_BIN}_state2
345
346env TPM20TEST_TCTI_NAME="socket" \
347    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
348    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
349    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
350    G_MESSAGES_DEBUG=all ./test/helper/tpm_dumpstate>$TPMSTATE_FILE1
351if [ $? -ne 0 ]; then
352    echo "Error during dumpstate"
353    ret=99
354    break
355fi
356
357echo "Execute the test script"
358env TPM20TEST_TCTI_NAME="socket" \
359    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
360    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
361    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
362    FAPI_TEST_ROOT_CERT=${ROOTCA_FILE}.pem \
363    G_MESSAGES_DEBUG=all $@
364ret=$?
365echo "Script returned $ret"
366
367#We check the state before a reboot to see if transients and NV were chagned.
368env TPM20TEST_TCTI_NAME="socket" \
369    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
370    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
371    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
372    G_MESSAGES_DEBUG=all ./test/helper/tpm_dumpstate>$TPMSTATE_FILE2
373if [ $? -ne 0 ]; then
374    echo "Error during dumpstate"
375    ret=99
376    break
377fi
378
379if [ "$(cat $TPMSTATE_FILE1)" != "$(cat $TPMSTATE_FILE2)" ]; then
380    echo "TPM changed state during test"
381    echo "State before ($TPMSTATE_FILE1):"
382    cat $TPMSTATE_FILE1
383    echo "State after ($TPMSTATE_FILE2):"
384    cat $TPMSTATE_FILE2
385    ret=1
386    break
387fi
388
389break
390
391#TODO: Add a tpm-restart/reboot here
392
393#We check the state again after a reboot to see if PCR allocations were chagned.
394env TPM20TEST_TCTI_NAME="socket" \
395    TPM20TEST_SOCKET_ADDRESS="127.0.0.1" \
396    TPM20TEST_SOCKET_PORT="${SIM_PORT_DATA}" \
397    TPM20TEST_TCTI="mssim:host=127.0.0.1,port=${SIM_PORT_DATA}" \
398    G_MESSAGES_DEBUG=all ./test/helper/tpm_dumpstate>$TPMSTATE_FILE2
399if [ $? -ne 0 ]; then
400    echo "Error during dumpstate"
401    ret=99
402    break
403fi
404
405if [ "$(cat $TPMSTATE_FILE1)" != "$(cat $TPMSTATE_FILE2)" ]; then
406    echo "TPM changed state during test"
407    echo "State before ($TPMSTATE_FILE1):"
408    cat $TPMSTATE_FILE1
409    echo "State after ($TPMSTATE_FILE2):"
410    cat $TPMSTATE_FILE2
411    ret=1
412    break
413fi
414
415break
416done
417
418# This sleep is sadly necessary: If we kill the tabrmd w/o sleeping for a
419# second after the test finishes the simulator will die too. Bug in the
420# simulator?
421sleep 1
422# teardown
423daemon_stop ${SIM_PID_FILE}
424rm -rf ${SIM_TMP_DIR} ${SIM_PID_FILE}
425
426exit $ret
427