| #!/usr/bin/env bash |
| |
| # |
| # swtpm_setup.sh |
| # |
| # Authors: Stefan Berger <stefanb@us.ibm.com> |
| # |
| # (c) Copyright IBM Corporation 2011,2014,2015. |
| # |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # 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. |
| # |
| # Neither the names of the IBM Corporation nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # 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. |
| # |
| |
| # echo "UID=$UID EUID=$EUID" |
| |
| # Dependencies: |
| # |
| # - tpm_tools (tpm-tools package with NVRAM utilities) |
| # - tcsd (trousers package with tcsd with -c <configfile> option) |
| # - expect (expect package) |
| |
| SWTPM=`type -P swtpm` |
| if [ -n "$SWTPM" ]; then |
| SWTPM="$SWTPM socket" |
| fi |
| SWTPM_IOCTL=`type -P swtpm_ioctl` |
| |
| ECHO=`which echo` |
| if [ -z "$ECHO" ]; then |
| echo "Error: external echo program not found." |
| exit 1 |
| fi |
| UNAME_S="$(uname -s)" |
| |
| SETUP_CREATE_EK_F=1 |
| SETUP_TAKEOWN_F=2 |
| SETUP_EK_CERT_F=4 |
| SETUP_PLATFORM_CERT_F=8 |
| SETUP_LOCK_NVRAM_F=16 |
| SETUP_SRKPASS_ZEROS_F=32 |
| SETUP_OWNERPASS_ZEROS_F=64 |
| SETUP_STATE_OVERWRITE_F=128 |
| SETUP_STATE_NOT_OVERWRITE_F=256 |
| SETUP_TPM2_F=512 |
| SETUP_ALLOW_SIGNING_F=1024 |
| SETUP_TPM2_ECC_F=2048 |
| SETUP_CREATE_SPK_F=4096 |
| SETUP_DISPLAY_RESULTS_F=8192 |
| SETUP_DECRYPTION_F=16384 |
| |
| # default values for passwords |
| DEFAULT_OWNER_PASSWORD=ooo |
| DEFAULT_SRK_PASSWORD=sss |
| |
| # default configuration file |
| SWTPM_SETUP_CONF="swtpm_setup.conf" |
| |
| if [ -n "$XDG_CONFIG_HOME" ] && [ -r "$XDG_CONFIG_HOME/$SWTPM_SETUP_CONF" ] ; then |
| DEFAULT_CONFIG_FILE="$XDG_CONFIG_HOME/$SWTPM_SETUP_CONF" |
| elif [ -n "$HOME" ] && [ -r "$HOME/.config/$SWTPM_SETUP_CONF" ] ; then |
| DEFAULT_CONFIG_FILE="$HOME/.config/$SWTPM_SETUP_CONF" |
| else |
| DEFAULT_CONFIG_FILE="@SYSCONFDIR@/$SWTPM_SETUP_CONF" |
| fi |
| |
| #default PCR banks to activate for TPM 2 |
| DEFAULT_PCR_BANKS="sha1,sha256" |
| |
| # TPM constants |
| TPM_NV_INDEX_D_BIT=$((0x10000000)) |
| TPM_NV_INDEX_EKCert=$((0xF000)) |
| TPM_NV_INDEX_PlatformCert=$((0xF002)) |
| |
| TPM_NV_INDEX_LOCK=$((0xFFFFFFFF)) |
| |
| # TPM 2 constants |
| TPMA_NV_PLATFORMCREATE=$((0x40000000)) |
| TPMA_NV_AUTHWRITE=$((0x4)) |
| TPMA_NV_AUTHREAD=$((0x40000)) |
| TPMA_NV_NO_DA=$((0x2000000)) |
| TPMA_NV_PPWRITE=$((0x1)) |
| TPMA_NV_PPREAD=$((0x10000)) |
| TPMA_NV_OWNERREAD=$((0x20000)) |
| TPMA_NV_POLICY_DELETE=$((0x400)) |
| TPMA_NV_WRITEDEFINE=$((0x2000)) |
| |
| # Use standard EK Cert NVRAM, EK and SRK handles per IWG spec. |
| # "TCG TPM v2.0 Provisioning Guide"; Version 1.0, Rev 1.0, March 15, 2017 |
| # Table 2 |
| TPM2_NV_INDEX_RSA_EKCert=$((0x01c00002)) |
| TPM2_NV_INDEX_RSA_EKTemplate=$((0x01c00004)) |
| # For ECC follow "TCG EK Credential Profile For TPM Family 2.0; Level 0" |
| # Specification Version 2.1; Revision 12; 17 August 2018 (Draft) |
| TPM2_NV_INDEX_ECC_EKCert=$((0x01c0000a)) |
| TPM2_NV_INDEX_ECC_EKTemplate=$((0x01c0000c)) |
| TPM2_NV_INDEX_PlatformCert=$((0x01c08000)) |
| |
| TPM2_EK_HANDLE=$((0x81010001)) |
| TPM2_SPK_HANDLE=$((0x81000001)) |
| |
| # Default logging goes to stderr |
| LOGFILE="" |
| |
| TPMLIB_INFO_TPMSPECIFICATION=1 |
| TPMLIB_INFO_TPMATTRIBUTES=2 |
| |
| NB16='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
| NB32=${NB16}${NB16} |
| NB256=${NB32}${NB32}${NB32}${NB32}${NB32}${NB32}${NB32}${NB32} |
| # Nonce used for EK creation; 2 bytes length + nonce |
| NONCE_RSA='\x01\x00'${NB256} |
| NONCE_RSA_SIZE=256 |
| |
| NONCE_ECC='\x00\x20'${NB32} |
| NONCE_ECC_SIZE=32 |
| |
| trap "cleanup" SIGTERM EXIT |
| |
| logit() |
| { |
| if [ -z "$LOGFILE" ]; then |
| echo "$@" >&1 |
| else |
| echo "$@" >> $LOGFILE |
| fi |
| } |
| |
| logit_cmd() |
| { |
| if [ -z "$LOGFILE" ]; then |
| eval "$@" >&1 |
| else |
| eval "$@" >> $LOGFILE |
| fi |
| } |
| |
| logerr() |
| { |
| if [ -z "$LOGFILE" ]; then |
| echo "Error: $@" >&2 |
| else |
| echo "Error: $@" >> $LOGFILE |
| fi |
| } |
| |
| # Get the size of a file |
| # |
| # @param1: filename |
| get_filesize() |
| { |
| case "${UNAME_S}" in |
| OpenBSD|FreeBSD|NetBSD|Darwin|DragonFly) |
| stat -f%z $1 |
| ;; |
| *) |
| stat -c%s $1 |
| ;; |
| esac |
| } |
| |
| # Get a random number given a lower and upper bound |
| # |
| # @param1: lower bound |
| # @param2: upper bound |
| get_random() |
| { |
| local lower=$1 |
| local upper=$2 |
| |
| echo $(( (RANDOM % (upper - lower)) + lower)) |
| } |
| |
| # Get the TPM specification parameters from the TPM using swtpm_ioctl |
| get_tpm_parameters() |
| { |
| local json |
| local res part arr |
| |
| json="$($SWTPM_IOCTL \ |
| --info $((TPMLIB_INFO_TPMSPECIFICATION | |
| TPMLIB_INFO_TPMATTRIBUTES)) \ |
| --tcp :$((TPM_PORT+1)) 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Error: $SWTPM_IOCTL failed: $json" |
| return 1 |
| fi |
| |
| for params in \ |
| 's/.*"family":\s*"\([^"]*\)".*/\1/p --tpm-spec-family' \ |
| 's/.*"level":\s*\([0-9\.]*\).*/\1/p --tpm-spec-level' \ |
| 's/.*"revision":\s*\([0-9]*\).*/\1/p --tpm-spec-revision' \ |
| 's/.*"manufacturer":\s*"\([^"]*\)".*/\1/p --tpm-manufacturer' \ |
| 's/.*"model":\s*"\([^"]*\)".*/\1/p --tpm-model' \ |
| 's/.*"version":\s*"\([^"]*\)".*/\1/p --tpm-version'; |
| do |
| arr=($params) |
| part=$(echo "$json" | sed -n "${arr[0]}") |
| if [ -z "$part" ]; then |
| logerr "Error: Could not parse JSON output" |
| logerr " No result from \"echo '$json' | sed -n '${arr[0]}'\"" |
| return 1 |
| fi |
| res+="${arr[1]} ${part} " |
| done |
| |
| echo "${res}" |
| |
| return 0 |
| } |
| |
| # Call external program to create certificates |
| # |
| # @param1: flags |
| # @param2: the configuration file to get the external program from |
| # @parma3: the directory where to write the certificates to |
| # @param4: the EK as a sequence of hex nunbers |
| # @param5: the ID of the VM |
| call_create_certs() |
| { |
| local ret=0 |
| |
| local flags="$1" |
| local configfile="$2" |
| local certdir="$3" |
| local ek="$4" |
| local vmid="$5" |
| |
| local logparam tmp |
| local params="" cmd |
| |
| if [ -n "$vmid" ]; then |
| params="$params --vmid \"$vmid\"" |
| fi |
| |
| if [ -n "$LOGFILE" ]; then |
| logparam="--logfile $LOGFILE" |
| fi |
| |
| params="${params} $(get_tpm_parameters)" |
| [ $? -ne 0 ] && return 1 |
| |
| if [ $((flags & SETUP_EK_CERT_F)) -ne 0 ] || \ |
| [ $((flags & SETUP_PLATFORM_CERT_F)) -ne 0 ]; then |
| if [ -r "$configfile" ]; then |
| # The config file contains lines in the format: |
| # key = value |
| # or with a comment at the end started by #: |
| # key = value # comment |
| create_certs_tool="$(sed -n 's/\s*create_certs_tool\s*=\s*\([^#]*\).*/\1/p' \ |
| "$configfile")" |
| |
| create_certs_tool_config="$(sed -n 's/\s*create_certs_tool_config\s*=\s*\([^#]*\).*/\1/p' \ |
| "$configfile")" |
| if [ -n "$create_certs_tool_config" ]; then |
| params="$params --configfile \"$create_certs_tool_config\"" |
| fi |
| |
| create_certs_tool_options="$(sed -n 's/\s*create_certs_tool_options\s*=\s*\([^#]*\).*/\1/p' \ |
| "$configfile")" |
| if [ -n "$create_certs_tool_options" ]; then |
| params="$params --optsfile \"$create_certs_tool_options\"" |
| fi |
| else |
| logerr "Could not access config file" \ |
| "'$configfile' to get" \ |
| "name of certificate tool to invoke." |
| return 1 |
| fi |
| fi |
| |
| if [ $((flags & SETUP_TPM2_F)) -ne 0 ]; then |
| params="$params --tpm2" |
| fi |
| |
| if [ -n "$create_certs_tool" ]; then |
| local fn=$(basename "${create_certs_tool}") |
| |
| if [ $((flags & SETUP_EK_CERT_F)) -ne 0 ]; then |
| cmd="$create_certs_tool \ |
| --type ek \ |
| --ek "$ek" \ |
| --dir "$certdir" \ |
| ${logparam} ${params}" |
| logit " Invoking: $(echo $cmd | tr -s " ")" |
| tmp="$(eval $cmd 2>&1)" |
| ret=$? |
| logit "$(echo "${tmp}" | sed -e "s/^/$fn: /")" |
| if [ $ret -ne 0 ]; then |
| logerr "Error running '$cmd'." |
| return $ret |
| fi |
| fi |
| if [ $((flags & SETUP_PLATFORM_CERT_F)) -ne 0 ]; then |
| cmd="$create_certs_tool \ |
| --type platform \ |
| --ek "$ek" \ |
| --dir "$certdir" \ |
| ${logparam} ${params}" |
| logit " Invoking: $(echo $cmd | tr -s " ")" |
| tmp="$(eval $cmd 2>&1)" |
| ret=$? |
| logit "$(echo "${tmp}" | sed -e "s/^/$fn: /")" |
| if [ $ret -ne 0 ]; then |
| logerr "Error running '$cmd'." |
| return $ret |
| fi |
| fi |
| fi |
| |
| return $ret |
| } |
| |
| # Start the TPM on a random open port |
| # |
| # @param1: full path to the TPM executable to use |
| # @param2: the directory where the TPM is supposed to write its state to |
| start_tpm() |
| { |
| local swtpm="$1" |
| local swtpm_state="$2" |
| |
| local ctr=0 ctr2 ctr3 |
| local pidfile="${swtpm_state}/.swtpm_setup.pidfile" |
| |
| while [ $ctr -lt 100 ]; do |
| TPM_PORT=$(get_random 30000 65535) |
| |
| # kill swtpm if still alive |
| stop_tpm 1 |
| |
| rm -f $pidfile &>/dev/null |
| $swtpm \ |
| --flags not-need-init \ |
| -p $TPM_PORT \ |
| --tpmstate dir=$swtpm_state \ |
| --ctrl type=tcp,port=$((TPM_PORT+1)) \ |
| --pid file=$pidfile \ |
| 2>&1 1>/dev/null & |
| SWTPM_PID=$! |
| |
| # poll for open port (good) or the process to have |
| # disappeared (bad); whatever happens first |
| ctr3=0 |
| while :; do |
| kill -0 $SWTPM_PID 2>/dev/null |
| if [ $? -ne 0 ]; then |
| # process dead; try next socket |
| break |
| fi |
| |
| # pidfile needs to be there for us to know we are |
| # testing swtpm's port rather than some other |
| # process's |
| ctr2=0 |
| while [ -f ${pidfile} ]; do |
| # test the connection to swtpm |
| (exec 100<>/dev/tcp/localhost/$TPM_PORT) 2>/dev/null |
| if [ $? -ne 0 ]; then |
| if [ $ctr2 -eq 40 ]; then |
| stop_tpm 0 |
| break |
| fi |
| sleep 0.05 |
| let ctr2=ctr2+1 |
| continue |
| fi |
| exec 100>&- |
| echo "TPM is listening on TCP port $TPM_PORT." |
| return 0 |
| done |
| sleep 0.05 |
| |
| let ctr3=ctr3+1 |
| # at some point the pid file must appear... |
| if [ $ctr3 -eq 40 ] && [ ! -f ${pidfile} ]; then |
| stop_tpm 0 |
| break |
| fi |
| done |
| |
| let ctr=$ctr+1 |
| done |
| |
| return 1 |
| } |
| |
| # Terminate a process gracefully, and if wanted wait for some time |
| # and kill it hard if it didn't terminate. |
| # |
| # @param1: pid |
| # @param2: whether to wait (1) for it to be gone or not (0) |
| # @param3: name of process for error message in case of hard kill |
| terminate_proc() |
| { |
| local pid="$1" |
| local sync="$2" |
| local name="$3" |
| |
| local c |
| |
| kill -0 $pid &>/dev/null |
| [ $? -ne 0 ] && return |
| |
| kill -SIGTERM $pid &>/dev/null |
| [ $sync -eq 0 ] && return |
| |
| # kill hard if necessary |
| for ((c=0; c<50; c++)); do |
| kill -0 $pid &>/dev/null |
| [ $? -ne 0 ] && return |
| sleep 0.02 |
| done |
| kill -SIGKILL $pid |
| logerr "Had to use SIGKILL on $name" |
| } |
| |
| # Stop the TPM by sigalling it with a SIGTERM and if @param1 is '1', |
| # make sure it's gone. |
| # |
| # @param1: whether to wait (1) until it is gone, or not |
| stop_tpm() |
| { |
| local sync="$1" |
| |
| [ "$SWTPM_PID" != "" ] && { |
| terminate_proc $SWTPM_PID "$sync" "swtpm" |
| [ $sync -eq 0 ] && return |
| SWTPM_PID= |
| } |
| } |
| |
| # Start the TSS for TPM 1.2 |
| start_tcsd() |
| { |
| local TCSD=$1 |
| local user=$(id -u -n) |
| local group=$(id -g -n) |
| local ctr=0 ctr2 |
| |
| export TSS_TCSD_PORT |
| |
| TCSD_CONFIG="$(mktemp)" |
| TCSD_DATA_DIR="$(mktemp -d)" |
| TCSD_DATA_FILE="$(TMPDIR=${TCSD_DATA_DIR} mktemp)" |
| |
| if [ -z "$TCSD_CONFIG" ] || [ -z "$TCSD_DATA_DIR" ] || \ |
| [ -z "$TCSD_DATA_FILE" ]; then |
| logerr "Could not create temporary file; TMPDIR=$TMPDIR" |
| return 1 |
| fi |
| |
| while [ $ctr -lt 100 ]; do |
| TSS_TCSD_PORT=$(get_random 30000 65535) |
| |
| cat << EOF >$TCSD_CONFIG |
| port = $TSS_TCSD_PORT |
| system_ps_file = $TCSD_DATA_FILE |
| EOF |
| # tcsd requires @TSS_USER@:@TSS_GROUP@ and 0600 on TCSD_CONFIG |
| # -> only root can start |
| chmod 600 $TCSD_CONFIG |
| if [ $(id -u) -eq 0 ]; then |
| chown @TSS_USER@:@TSS_GROUP@ $TCSD_CONFIG 2>/dev/null |
| chown @TSS_USER@:@TSS_GROUP@ $TCSD_DATA_DIR 2>/dev/null |
| chown @TSS_USER@:@TSS_GROUP@ $TCSD_DATA_FILE 2>/dev/null |
| fi |
| if [ $? -ne 0 ]; then |
| logerr "Could not change ownership on $TCSD_CONFIG to ${user}:${group}." |
| ls -l $TCSD_CONFIG |
| return 1 |
| fi |
| |
| # make sure tcsd is gone |
| stop_tcsd 1 |
| case "$(id -u)" in |
| 0) |
| $TCSD -c $TCSD_CONFIG -e -f 2>&1 1>/dev/null & |
| TCSD_PID=$! |
| ;; |
| *) |
| # for tss user, use the wrapper |
| $TCSD -c $TCSD_CONFIG -e -f 2>&1 1>/dev/null & |
| #if [ $? -ne 0]; then |
| # swtpm_tcsd_launcher -c $TCSD_CONFIG -e -f 2>&1 1>/dev/null & |
| #fi |
| TCSD_PID=$! |
| ;; |
| esac |
| |
| # poll for open port (good) or the process to have |
| # disappeared (bad); whatever happens first |
| ctr2=0 |
| while :; do |
| (exec 100<>/dev/tcp/localhost/$TSS_TCSD_PORT) 2>/dev/null |
| if [ $? -ne 0 ]; then |
| if [ $ctr2 -eq 40 ]; then |
| stop_tcsd 0 |
| break |
| fi |
| # check TCSD is still alive and we haven't |
| # successfully test some other process's port |
| kill -0 $TCSD_PID 2>/dev/null |
| if [ $? -ne 0 ]; then |
| break |
| fi |
| # process still alive |
| let ctr2=ctr2+1 |
| sleep 0.05 |
| continue |
| fi |
| exec 100>&- |
| echo "TSS is listening on TCP port $TSS_TCSD_PORT." |
| return 0 |
| done |
| |
| let ctr=$ctr+1 |
| done |
| |
| return 1 |
| } |
| |
| # Stop the TSS by sigalling it with a SIGTERM and if @param1 is '1', |
| # make sure it's gone. |
| # |
| # @param1: whether to wait (1) until it is gone, or not |
| stop_tcsd() |
| { |
| local sync="$1" |
| |
| [ "$TCSD_PID" != "" ] && { |
| terminate_proc $TCSD_PID "$sync" "tcsd" |
| [ $sync -eq 0 ] && return |
| TCSD_PID= |
| } |
| } |
| |
| # Cleanup everything including TPM, TSS, and files we may have created |
| cleanup() |
| { |
| # Send SIGTERMs first |
| stop_tpm 0 |
| stop_tcsd 0 |
| # Make sure they are gone for good |
| stop_tpm 1 |
| stop_tcsd 1 |
| for f in "$TCSD_CONFIG" "$TCSD_DATA_FILE" "$TCSD_DATA_DIR"; do |
| [ -z "${f}" ] && continue |
| rm -rf "${f}" |
| done |
| } |
| |
| # Read hex data passed to this function via a pipe and convert them to a |
| # string using od -t x1. The specifics require the use oif a different |
| # implementation on OpenBSD than on Linux/Cygwin. On the latter systems it |
| # can be executed more efficiently using only 'od'. |
| read_hex_data() |
| { |
| case "${UNAME_S}" in |
| OpenBSD|FreeBSD|NetBSD|Darwin|DragonFly) |
| od -t x1 -A n | tr -s ' ' | tr -d '\n' | sed 's/ $//' |
| ;; |
| *) |
| od -t x1 -A n -w2048 |
| ;; |
| esac |
| } |
| |
| # Send hex data written in a string with each hex number |
| # written in the form \x<2 hex digits> |
| # We have to use bash's echo on OpenBSD 6.2. |
| # We have to use | cat since some versions of echo otherwise |
| # stop writing data into a socket after the first \x0a. |
| # |
| # @param1: The string with hex numbers |
| send_hex_data() |
| { |
| case "${UNAME_S}" in |
| OpenBSD|FreeBSD|NetBSD|Darwin|DragonFly|CYGWIN*) |
| echo -en "$1" | cat |
| ;; |
| *) |
| # Some Ubuntu Xenial version still needs this |
| $ECHO -en "$1" |
| ;; |
| esac |
| } |
| |
| # Transfer a request to the TPM and receive the response |
| # |
| # @param1: The request to send |
| tpm_transfer() |
| { |
| exec 100<>/dev/tcp/127.0.0.1/${TPM_PORT} |
| send_hex_data "$1" >&100 |
| |
| read_hex_data <&100 |
| exec 100>&- |
| } |
| |
| # Create an endorsement key |
| tpm_createendorsementkeypair() |
| { |
| local req rsp exp |
| |
| req='\x00\xc1\x00\x00\x00\x36\x00\x00\x00\x78\x38\xf0\x30\x81\x07\x2b' |
| req+='\x0c\xa9\x10\x98\x08\xc0\x4B\x05\x11\xc9\x50\x23\x52\xc4\x00\x00' |
| req+='\x00\x01\x00\x03\x00\x02\x00\x00\x00\x0c\x00\x00\x08\x00\x00\x00' |
| req+='\x00\x02\x00\x00\x00\x00' |
| |
| rsp="$(tpm_transfer "${req}")" |
| |
| exp=' 00 c4 00 00 01 3a 00 00 00 00' |
| if [ "${rsp:0:30}" != "$exp" ]; then |
| logerr "TPM_CreateEndorsementKeyPair() failed" |
| logerr " expected: $exp" |
| logerr " received: ${rsp:0:30}" |
| return 1 |
| fi |
| |
| echo "${rsp:114:768}" | tr -d " " |
| |
| return 0 |
| } |
| |
| # Initialize the TPM |
| # |
| # @param1: the flags |
| # @param2: the configuration file to get the external program from |
| # @parma3: the directory where the TPM is supposed to write it state to |
| # @param4: the TPM owner password to use |
| # @param5: The SRK password to use |
| # @param6: The ID of the VM |
| # @param7: The filename to copy the TCSD system_ps_file to; optional |
| init_tpm() |
| { |
| local flags="$1" |
| local config_file="$2" |
| local tpm_state_path="$3" |
| local ownerpass="$4" |
| local srkpass="$5" |
| local vmid="$6" |
| local tcsd_system_ps_file="$7" |
| |
| # where external app writes certs into |
| local certsdir="$tpm_state_path" |
| local ek tmp output |
| |
| local PLATFORM_CERT_FILE="$certsdir/platform.cert" |
| local EK_CERT_FILE="$certsdir/ek.cert" |
| local nvramauth="OWNERREAD|OWNERWRITE" |
| |
| start_tpm "$SWTPM" "$tpm_state_path" |
| if [ $? -ne 0 ]; then |
| logerr "Could not start the TPM." |
| return 1 |
| fi |
| |
| export TCSD_USE_TCP_DEVICE=1 |
| export TCSD_TCP_DEVICE_PORT=$TPM_PORT |
| |
| output="$(swtpm_bios 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "swtpm_bios failed: $output" |
| return 1 |
| fi |
| |
| # Creating EK is simple enough to do without the tcsd |
| if [ $((flags & $SETUP_CREATE_EK_F)) -ne 0 ]; then |
| ek="$(tpm_createendorsementkeypair)" |
| if [ $? -ne 0 ]; then |
| logerr "tpm_createendorsementkeypair failed." |
| return 1 |
| fi |
| logit "Successfully created EK." |
| |
| if [ $((flags & ~$SETUP_CREATE_EK_F)) -eq 0 ]; then |
| return 0 |
| fi |
| fi |
| |
| # TPM is enabled and activated upon first start |
| |
| start_tcsd $TCSD |
| if [ $? -ne 0 ]; then |
| return 1 |
| fi |
| |
| # temporarily take ownership if an EK was created |
| if [ $((flags & $SETUP_CREATE_EK_F)) -ne 0 ] ; then |
| local parm_z="" |
| local parm_y="" |
| if [ $((flags & $SETUP_SRKPASS_ZEROS_F)) -ne 0 ]; then |
| parm_z="-z" |
| fi |
| if [ $((flags & $SETUP_OWNERPASS_ZEROS_F)) -ne 0 ]; then |
| parm_y="-y" |
| fi |
| if [ -n "${parm_y}" ] && [ -n "${parm_z}" ]; then |
| tpm_takeownership $parm_z $parm_y &>/dev/null |
| else |
| if [ -z "$(type -p expect)" ]; then |
| logerr "Missing 'expect' tool to take" \ |
| "ownership with non-standard password." |
| return 1 |
| fi |
| a=$(expect -c " |
| set parm_z \"$parm_z\" |
| set parm_y \"$parm_y\" |
| spawn tpm_takeownership \$parm_z \$parm_y |
| if { \$parm_y == \"\" } { |
| expect { |
| \"Enter owner password:\" |
| { send \"$ownerpass\n\" } |
| } |
| expect { |
| \"Confirm password:\" |
| { send \"$ownerpass\n\" } |
| } |
| } |
| if { \$parm_z == \"\" } { |
| expect { |
| \"Enter SRK password:\" |
| { send \"$srkpass\n\" } |
| } |
| expect { |
| \"Confirm password:\" |
| { send \"$srkpass\n\" } |
| } |
| } |
| expect eof |
| catch wait result |
| exit [lindex \$result 3] |
| ") |
| fi |
| if [ $? -ne 0 ]; then |
| logerr "Could not take ownership of TPM." |
| return 1 |
| fi |
| logit "Successfully took ownership of the TPM." |
| fi |
| |
| # have external program create the certificates now |
| call_create_certs "$flags" "$config_file" "$certsdir" "$ek" "$vmid" |
| if [ $? -ne 0 ]; then |
| return 1 |
| fi |
| |
| # Define NVRAM are for Physical Presence Interface; unfortunately |
| # there are no useful write permissions... |
| #tpm_nvdefine \ |
| # -i $((0x50010000)) \ |
| # -p "PPREAD|PPWRITE|WRITEDEFINE" \ |
| # -s 6 2>&1 > /dev/null |
| |
| if [ $((flags & SETUP_EK_CERT_F)) -ne 0 ] && \ |
| [ -r "${EK_CERT_FILE}" ]; then |
| output="$(tpm_nvdefine \ |
| -i $((TPM_NV_INDEX_EKCert|TPM_NV_INDEX_D_BIT)) \ |
| -p "${nvramauth}" \ |
| -s $(get_filesize "${EK_CERT_FILE}") 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area for EK certificate." |
| return 1 |
| fi |
| output="$(tpm_nvwrite -i $((TPM_NV_INDEX_EKCert|TPM_NV_INDEX_D_BIT)) \ |
| -f "${EK_CERT_FILE}" 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Could not write EK cert to NVRAM: $output" |
| return 1 |
| fi |
| logit "Successfully created NVRAM area for EK certificate." |
| rm -f ${EK_CERT_FILE} |
| fi |
| |
| if [ $((flags & SETUP_PLATFORM_CERT_F)) -ne 0 ] && \ |
| [ -r "${PLATFORM_CERT_FILE}" ] ; then |
| output="$(tpm_nvdefine \ |
| -i $((TPM_NV_INDEX_PlatformCert|TPM_NV_INDEX_D_BIT)) \ |
| -p "${nvramauth}" \ |
| -s $(get_filesize "${PLATFORM_CERT_FILE}") 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area for platform" \ |
| "certificate." |
| return 1 |
| fi |
| output="$(tpm_nvwrite \ |
| -i $((TPM_NV_INDEX_PlatformCert|TPM_NV_INDEX_D_BIT)) \ |
| -f "$PLATFORM_CERT_FILE" 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Could not write EK cert to NVRAM: $output" |
| return 1 |
| fi |
| logit "Successfully created NVRAM area for platform" \ |
| "certificate." |
| rm -f ${PLATFORM_CERT_FILE} |
| fi |
| |
| if [ $((flags & SETUP_DISPLAY_RESULTS_F)) -ne 0 ]; then |
| local nvidxs=`tpm_nvinfo -n | grep 0x | gawk '{print $1}'` |
| local passparam |
| |
| if [ $((flags & $SETUP_OWNERPASS_ZEROS_F)) -ne 0 ]; then |
| passparam="-z" |
| else |
| passparam="--password=$ownerpass" |
| fi |
| |
| for i in $nvidxs; do |
| logit "Content of NVRAM area $i:" |
| tmp="tpm_nvread -i $i $passparam" |
| logit_cmd "$cmd" |
| done |
| fi |
| |
| # Last thing is to lock the NVRAM area |
| if [ $((flags & SETUP_LOCK_NVRAM_F)) -ne 0 ]; then |
| output="$(tpm_nvdefine -i $TPM_NV_INDEX_LOCK 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Could not lock NVRAM access: $output" |
| return 1 |
| fi |
| logit "Successfully locked NVRAM access." |
| fi |
| |
| # give up ownership if not wanted |
| if [ $((flags & SETUP_TAKEOWN_F)) -eq 0 -a \ |
| $((flags & SETUP_CREATE_EK_F)) -ne 0 ] ; then |
| if [ $((flags & $SETUP_OWNERPASS_ZEROS_F)) -ne 0 ]; then |
| tpm_clear -z &>/dev/null |
| else |
| if [ -z "$(type -p expect)" ]; then |
| logerr "Missing 'expect' tool to take" \ |
| "ownership with non-standard password." |
| return 1 |
| fi |
| a=$(expect -c " |
| spawn tpm_clear |
| expect { |
| \"Enter owner password:\" { send \"$ownerpass\n\" } |
| } |
| expect eof |
| catch wait result |
| exit [lindex \$result 3] |
| ") |
| fi |
| if [ $? -ne 0 ]; then |
| logerr "Could not give up ownership of TPM." |
| return 1 |
| fi |
| logit "Successfully gave up ownership of the TPM." |
| |
| # TPM is now disabled and deactivated; enable and activate it |
| output="$($SWTPM_IOCTL --tcp :$((TPM_PORT+1)) -i 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "Could not re-init TPM." |
| return 1 |
| fi |
| |
| TCSD_TCP_DEVICE_PORT=$TPM_PORT |
| output="$(swtpm_bios -c 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "swtpm_bios -c failed: $output" |
| return 1 |
| fi |
| logit "Successfully enabled and activated the TPM" |
| fi |
| |
| logit "${tcsd_system_ps_file}" |
| if [ -n "${tcsd_system_ps_file}" ]; then |
| cp "${TCSD_DATA_FILE}" "${tcsd_system_ps_file}" |
| fi |
| |
| return 0 |
| } |
| |
| ################################# TPM 2 ################################## |
| |
| # Create the primary key (EK equivalent) |
| # |
| # @param1: flags |
| # @param2: filename for template |
| tpm2_createprimary_ek_rsa() |
| { |
| local flags="$1" |
| local templatefile="$2" |
| |
| local symkeydata keyflags totlen publen off min_exp authpolicy |
| |
| if [ $((flags & SETUP_ALLOW_SIGNING_F)) -ne 0 ] && \ |
| [ $((flags & SETUP_DECRYPTION_F)) -ne 0 ]; then |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, sign, decrypt |
| keyflags=$((0x000600b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x10' |
| publen=$((0x36 + NONCE_RSA_SIZE)) |
| totlen=$((0x5f + NONCE_RSA_SIZE)) |
| min_exp=1506 |
| # offset of length indicator for key |
| off=216 |
| elif [ $((flags & SETUP_ALLOW_SIGNING_F)) -ne 0 ]; then |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, sign |
| keyflags=$((0x000400b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x10' |
| publen=$((0x36 + NONCE_RSA_SIZE)) |
| totlen=$((0x5f + NONCE_RSA_SIZE)) |
| min_exp=1506 |
| # offset of length indicator for key |
| off=216 |
| else |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, restricted, decrypt |
| keyflags=$((0x000300b2)) |
| # symmetric: TPM_ALG_AES, 128bit, TPM_ALG_CFB |
| symkeydata='\\x00\\x06\\x00\\x80\\x00\\x43' |
| publen=$((0x3a + NONCE_RSA_SIZE)) |
| totlen=$((0x63 + NONCE_RSA_SIZE)) |
| min_exp=1518 |
| # offset of length indicator for key |
| off=228 |
| fi |
| |
| authpolicy='\\x83\\x71\\x97\\x67\\x44\\x84\\xb3\\xf8\\x1a\\x90\\xcc\\x8d' |
| authpolicy+='\\x46\\xa5\\xd7\\x24\\xfd\\x52\\xd7\\x6e\\x06\\x52\\x0b\\x64' |
| authpolicy+='\\xf2\\xa1\\xda\\x1b\\x33\\x14\\x69\\xaa' |
| |
| # Check the TCG EK Credential Profile doc for TPM 2 for |
| # parameters used here |
| |
| # TPM_RH_ENDORSEMENT |
| tpm2_createprimary_rsa_params '\\x40\\x00\\x00\\x0b' "${keyflags}" \ |
| "${symkeydata}" "${publen}" "${totlen}" "${min_exp}" "${off}" \ |
| "${authpolicy}" "${templatefile}" |
| return $? |
| } |
| |
| # Create a storage primary key |
| # |
| # @param1: flags |
| tpm2_createprimary_spk_rsa() |
| { |
| local flags="$1" |
| |
| local symkeydata keyflags totlen publen off min_exp |
| |
| # keyflags: fixedTPM, fixedParent, sensitiveDataOrigin, |
| # userWithAuth, noDA, restricted, decrypt |
| keyflags=$((0x00030472)) |
| # keyflags=$((0x000300b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x06\\x00\\x80\\x00\\x43' |
| publen=$((0x1a + NONCE_RSA_SIZE)) |
| totlen=$((0x43 + NONCE_RSA_SIZE)) |
| min_exp=1470 |
| # offset of length indicator for key |
| off=132 |
| |
| # TPM_RH_OWNER |
| tpm2_createprimary_rsa_params '\\x40\\x00\\x00\\x01' "${keyflags}" \ |
| "${symkeydata}" "${publen}" "${totlen}" "${min_exp}" "${off}" "" \ |
| "" |
| return $? |
| } |
| |
| function tpm2_createprimary_rsa_params() |
| { |
| local primaryhandle="$1" |
| local keyflags="$2" |
| local symkeydata="$3" |
| local publen="$4" |
| local totlen="$5" |
| local min_exp="$6" |
| local off="$7" |
| local authpolicy="$8" |
| local templatefile="$9" |
| |
| local req rsp res temp |
| local authpolicylen=$((${#authpolicy} / 5)) |
| |
| req='\x80\x02@TOTLEN-4@\x00\x00\x01\x31' |
| req+='@KEYHANDLE-4@' |
| # size of buffer |
| req+='\x00\x00\x00\x09' |
| # TPM_RS_PW |
| req+='\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| req+='\x00\x04\x00\x00\x00\x00' |
| # Size of TPM2B_PUBLIC |
| req+='@PUBLEN-2@' |
| # TPM_ALG_RSA, TPM_ALG_SHA256 |
| temp='\x00\x01\x00\x0b' |
| # fixedTPM, fixedParent, sensitiveDatOrigin, adminWithPolicy |
| # restricted, decrypt |
| temp+='@KEYFLAGS-4@' |
| # authPolicy;32 bytes |
| temp+='@AUTHPOLICYLEN-2@' |
| temp+='@AUTHPOLICY@' |
| temp+='@SYMKEYDATA@' |
| # scheme: TPM_ALG_NULL, keyBits: 2048bits |
| temp+='\x00\x10\x08\x00' |
| # exponent |
| temp+='\x00\x00\x00\x00' |
| # TPM2B_DATA |
| temp+=${NONCE_RSA} |
| |
| temp=$(echo $temp | \ |
| sed -e "s/@KEYFLAGS-4@/$(_format "$keyflags" 4)/" \ |
| -e "s/@SYMKEYDATA@/$symkeydata/" \ |
| -e "s/@AUTHPOLICY@/$authpolicy/" \ |
| -e "s/@AUTHPOLICYLEN-2@/$(_format "$authpolicylen" 2)/") |
| |
| req+=${temp} |
| # TPML_PCR_SELECTION |
| req+='\x00\x00\x00\x00\x00\x00' |
| |
| req=$(echo $req | \ |
| sed -e "s/@PUBLEN-2@/$(_format "$publen" 2)/" \ |
| -e "s/@TOTLEN-4@/$(_format "$totlen" 4)/" \ |
| -e "s/@KEYHANDLE-4@/$primaryhandle/") |
| |
| rsp="$(tpm_transfer "${req}")" |
| |
| if [ ${#rsp} -lt $min_exp ]; then |
| logerr "TPM2_CreatePrimary(RSA) failed" |
| logerr " expected at least $min_exp bytes, got ${#rsp}." |
| logerr " response: $rsp" |
| return 1 |
| fi |
| |
| # Check the RSA modulus length indicator |
| if [ "${rsp:$off:6}" != " 01 00" ]; then |
| logerr "Getting modulus from wrong offset." |
| return 1 |
| fi |
| |
| let off=off+6 |
| |
| # output: handle,ek |
| res="$(echo "0x${rsp:30:12}" | sed -n 's/ //pg')," |
| res+="$(echo "${rsp:$off:768}" | sed -n 's/ //pg')" |
| echo $res |
| |
| if [ -n "${templatefile}" ]; then |
| $ECHO -en ${temp} > ${templatefile} |
| fi |
| |
| return 0 |
| } |
| |
| # Create the primary key as an ECC key (EK equivalent) |
| # |
| # @param1: flags |
| # @param2: filename for template |
| tpm2_createprimary_ek_ecc() |
| { |
| local flags="$1" |
| local templatefile="$2" |
| |
| local min_exp symkeydata keyflags totlen publen off1 off2 authpolicy |
| |
| if [ $((flags & SETUP_ALLOW_SIGNING_F)) -ne 0 ] && \ |
| [ $((flags & SETUP_DECRYPTION_F)) -ne 0 ]; then |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, sign, decrypt |
| keyflags=$((0x000600b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x10' |
| publen=$((0x36 + 2 * NONCE_ECC_SIZE)) |
| totlen=$((0x5f + 2 * NONCE_ECC_SIZE)) |
| min_exp=930 |
| # offset of length indicator for key |
| off1=210 |
| off2=312 |
| elif [ $((flags & SETUP_ALLOW_SIGNING_F)) -ne 0 ]; then |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, sign |
| keyflags=$((0x000400b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x10' |
| publen=$((0x36 + 2 * NONCE_ECC_SIZE)) |
| totlen=$((0x5f + 2 * NONCE_ECC_SIZE)) |
| min_exp=930 |
| # offset of length indicator for key |
| off1=210 |
| off2=312 |
| else |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, restricted, decrypt |
| keyflags=$((0x000300b2)) |
| # symmetric: TPM_ALG_AES, 128bit, TPM_ALG_CFB |
| symkeydata='\\x00\\x06\\x00\\x80\\x00\\x43' |
| publen=$((0x3a + 2 * NONCE_ECC_SIZE)) |
| totlen=$((0x63 + 2 * NONCE_ECC_SIZE)) |
| # some version of TPM2 returns 942, another 990 |
| min_exp=942 |
| # offset of length indicator for key |
| off1=222 |
| off2=324 |
| fi |
| |
| authpolicy='\\x83\\x71\\x97\\x67\\x44\\x84\\xb3\\xf8\\x1a\\x90\\xcc\\x8d' |
| authpolicy+='\\x46\\xa5\\xd7\\x24\\xfd\\x52\\xd7\\x6e\\x06\\x52\\x0b\\x64' |
| authpolicy+='\\xf2\\xa1\\xda\\x1b\\x33\\x14\\x69\\xaa' |
| |
| tpm2_createprimary_ecc_params '\\x40\\x00\\x00\\x0b' "${keyflags}" \ |
| "${symkeydata}" "${publen}" "${totlen}" "${min_exp}" "${off1}" \ |
| "${off2}" "${authpolicy}" "${templatefile}" |
| return $? |
| } |
| |
| # Create primary storage key as an ECC key |
| # |
| # @param1: flags |
| tpm2_createprimary_spk_ecc() |
| { |
| local flags="$1" |
| |
| local min_exp symkeydata keyflags totlen publen off1 off2 |
| |
| # keyflags: fixedTPM, fixedParent, sensitiveDataOrigin, |
| # userWithAuth, noDA, restricted, decrypt |
| keyflags=$((0x00030472)) |
| # symmetric: TPM_ALG_AES, 128bit, TPM_ALG_CFB |
| symkeydata='\\x00\\x06\\x00\\x80\\x00\\x43' |
| publen=$((0x1a + 2 * NONCE_ECC_SIZE)) |
| totlen=$((0x43 + 2 * NONCE_ECC_SIZE)) |
| # some version of TPM2 returns 942, another 990 |
| min_exp=894 |
| # offset of length indicator for key |
| off1=126 |
| off2=228 |
| |
| # TPM_RH_OWNER |
| tpm2_createprimary_ecc_params '\\x40\\x00\\x00\\x01' "${keyflags}" \ |
| "${symkeydata}" "${publen}" "${totlen}" "${min_exp}" "${off1}" \ |
| "${off2}" "" "" |
| return $? |
| } |
| |
| tpm2_createprimary_ecc_params() |
| { |
| local primaryhandle="$1" |
| local keyflags="$2" |
| local symkeydata="$3" |
| local publen="$4" |
| local totlen="$5" |
| local min_exp="$6" |
| local off1="$7" |
| local off2="$8" |
| local authpolicy="$9" |
| local templatefile="${10}" |
| |
| local req rsp res temp |
| local authpolicylen=$((${#authpolicy} / 5)) |
| |
| # Check the TCG EK Credential Profile doc for TPM 2 for |
| # parameters used here |
| |
| req='\x80\x02@TOTLEN-4@\x00\x00\x01\x31' |
| # TPM_RH_ENDORSEMENT |
| req+='@KEYHANDLE-4@' |
| # size of buffer |
| req+='\x00\x00\x00\x09' |
| # TPM_RS_PW |
| req+='\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| # TPM2B_SENSITIVE_CREATE |
| req+='\x00\x04\x00\x00\x00\x00' |
| # Size of TPM2B_PUBLIC |
| req+='@PUBLEN-2@' |
| # TPM_ALG_ECC, TPM_ALG_SHA256 |
| temp='\x00\x23\x00\x0b' |
| # flags: fixedTPM, fixedParent, sensitiveDatOrigin, adminWithPolicy |
| # restricted, decrypt |
| temp+='@KEYFLAGS-4@' |
| # authPolicy: size = 32 bytes |
| # authPolicy;32 bytes |
| temp+='@AUTHPOLICYLEN-2@' |
| temp+='@AUTHPOLICY@' |
| temp+='@SYMKEYDATA@' |
| # scheme: TPM_ALG_NULL, curveID: TPM_ECC_NIST_P256 |
| temp+='\x00\x10\x00\x03' |
| # kdf->scheme: TPM_ALG_NULL |
| temp+='\x00\x10' |
| # TPM2B_DATA for x and y |
| temp+=${NONCE_ECC} |
| temp+=${NONCE_ECC} |
| |
| temp=$(echo $temp | \ |
| sed -e "s/@KEYFLAGS-4@/$(_format "$keyflags" 4)/" \ |
| -e "s/@SYMKEYDATA@/$symkeydata/" \ |
| -e "s/@AUTHPOLICY@/$authpolicy/" \ |
| -e "s/@AUTHPOLICYLEN-2@/$(_format "$authpolicylen" 2)/") |
| |
| req+=${temp} |
| # TPML_PCR_SELECTION |
| req+='\x00\x00\x00\x00\x00\x00' |
| |
| req=$(echo $req | \ |
| sed -e "s/@PUBLEN-2@/$(_format "$publen" 2)/" \ |
| -e "s/@TOTLEN-4@/$(_format "$totlen" 4)/" \ |
| -e "s/@KEYHANDLE-4@/$primaryhandle/") |
| |
| rsp="$(tpm_transfer "${req}")" |
| if [ ${#rsp} -lt $min_exp ]; then |
| logerr "TPM2_CreatePrimary(ECC) failed" |
| logerr " expected at least $min_exp bytes, got ${#rsp}." |
| logerr " response: $rsp" |
| return 1 |
| fi |
| |
| # Check the x and y length indicators |
| if [ "${rsp:$off1:6}" != " 00 20" ] || \ |
| [ "${rsp:$off2:6}" != " 00 20" ]; then |
| logerr "Getting ECC x and y parameter from wrong offset." |
| return 1 |
| fi |
| |
| let off1=off1+6 |
| let off2=off2+6 |
| |
| # output: handle,ek |
| res="$(echo "0x${rsp:30:12}" | sed -n 's/ //pg')," |
| res+="$(echo x=${rsp:$off1:96},y=${rsp:$off2:96} | sed -n 's/ //pg')" |
| echo $res |
| |
| if [ -n "${templatefile}" ]; then |
| $ECHO -en ${temp} > ${templatefile} |
| fi |
| |
| return 0 |
| } |
| |
| # Make a object permanent |
| # |
| # @param1: the current object handle |
| # @param2: the permanent object handle |
| tpm2_evictcontrol() |
| { |
| local trhandle="$1" |
| local pmhandle="$2" |
| |
| local req rsp exp |
| |
| req='\x80\x02\x00\x00\x00\x23\x00\x00\x01\x20' |
| req+='\x40\x00\x00\x01' |
| req+='@TRHANDLE-4@' |
| req+='\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| req+='@PMHANDLE-4@' |
| |
| req=$(echo $req | \ |
| sed -e "s/@TRHANDLE-4@/$(_format "$trhandle" 4)/" \ |
| -e "s/@PMHANDLE-4@/$(_format "$pmhandle" 4)/") |
| |
| rsp="$(tpm_transfer "$req")" |
| |
| exp=' 80 02 00 00 00 13 00 00 00 00 00 00 00 00 00 00 01 00 00' |
| if [ "${rsp}" != "$exp" ]; then |
| logerr "TPM2_EvictControl() failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # Create the EK, either RSA or ECC |
| # |
| # @param1: flags |
| # @param2: non-evict handle, if any |
| # @param3: filename for EK template |
| tpm2_create_ek() |
| { |
| local flags="$1" |
| local nehandle="$2" |
| local ektemplatefile="$3" |
| |
| local res handle |
| |
| if [ $((flags & SETUP_TPM2_ECC_F)) -ne 0 ]; then |
| res=$(tpm2_createprimary_ek_ecc "$flags" "${ektemplatefile}") |
| else |
| res=$(tpm2_createprimary_ek_rsa "$flags" "${ektemplatefile}") |
| fi |
| [ $? -ne 0 ] && return 1 |
| |
| handle=$(echo $res | cut -d "," -f1) |
| |
| # make key permanent |
| if [ -n "$nehandle" ]; then |
| tpm2_evictcontrol "$handle" "$nehandle" |
| [ $? -ne 0 ] && return 1 |
| fi |
| |
| # ek |
| echo $res | cut -d "," -f2- |
| |
| return 0 |
| } |
| |
| # Create the platform key, either RSA or ECC |
| # |
| # @param1: flags |
| # @param2: non-evict handle, if any |
| tpm2_create_spk() |
| { |
| local flags="$1" |
| local nehandle="$2" |
| |
| local res handle |
| |
| if [ $((flags & SETUP_TPM2_ECC_F)) -ne 0 ]; then |
| res=$(tpm2_createprimary_spk_ecc "$flags") |
| else |
| res=$(tpm2_createprimary_spk_rsa "$flags") |
| fi |
| |
| [ $? -ne 0 ] && return 1 |
| |
| handle=$(echo $res | cut -d "," -f1) |
| |
| # make key permanent |
| if [ -n "$nehandle" ]; then |
| tpm2_evictcontrol "$handle" "$nehandle" |
| [ $? -ne 0 ] && return 1 |
| fi |
| |
| # ek |
| echo $res | cut -d "," -f2- |
| |
| return 0 |
| } |
| |
| # Format an integer to a represenation of '\xaa\xbb...' |
| # |
| # @param1: the number to format |
| # @param2: the size of the integer in bytes |
| _format() |
| { |
| local num="$1" |
| local bytes="$2" |
| |
| local f r res i |
| |
| case $bytes in |
| 1) f="%02x"; num=$((num & 0xff));; |
| 2) f="%04x"; num=$((num & 0xffff));; |
| 4) f="%08x"; num=$((num & 0xffffffff));; |
| esac |
| |
| r="$(printf $f $num)" |
| for ((i = 0; i < ${#r}; i+=2 )); do |
| # prepare for usage with sed -> extra backslashes |
| res+="\\\x${r:$i:2}" |
| done |
| |
| echo $res |
| } |
| |
| # Define an NVRAM space |
| # |
| # @param1: index |
| # @param2: access attributes/flags for the NVRAM space |
| # @param3: the size of the NVRAM area |
| tpm2_nv_define() |
| { |
| local index="$1" |
| local flags="$2" |
| local size="$3" |
| |
| local req rsp exp |
| |
| req='\x80\x02\x00\x00\x00\x2d\x00\x00\x01\x2a\x40\x00\x00\x0c\x00\x00' |
| req+='\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x0e' |
| req+='@INDEX-4@\x00\x0b@FLAGS-4@\x00\x00@SIZE-2@' |
| |
| req=$(echo $req | \ |
| sed -e "s/@INDEX-4@/$(_format "$index" 4)/" \ |
| -e "s/@FLAGS-4@/$(_format "$flags" 4)/" \ |
| -e "s/@SIZE-2@/$(_format "$size" 2)/") |
| |
| rsp="$(tpm_transfer "$req")" |
| |
| exp=' 80 02 00 00 00 13 00 00 00 00 00 00 00 00 00 00 01 00 00' |
| if [ "$rsp" != "$exp" ]; then |
| logerr "TPM2_NV_DefineSpace() failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # Write the contents of a file into a NVRAM area |
| # |
| # @param1: the NVRAM index |
| # @param2: The name of the file |
| tpm2_nv_write() |
| { |
| local index="$1" |
| local fil="$2" |
| |
| local reqhdr reqbdy req rsp datalen data index_f totlen exp |
| local rc=0 offset=0 step=1024 |
| |
| index_f=$(_format "$index" 4) |
| |
| reqhdr='\x80\x02@TOTLEN-4@\x00\x00\x01\x37' |
| |
| reqbdy='\x40\x00\x00\x0c@INDEX-4@' |
| reqbdy+='\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| reqbdy+='@DATALEN-2@@DATA@@OFFSET-2@' |
| |
| exp=' 80 02 00 00 00 13 00 00 00 00 ' |
| exp+='00 00 00 00 00 00 01 00 00' |
| |
| while :; do |
| # read data from file |
| data=$(dd bs=1 skip=${offset} count=${step} if=$fil \ |
| 2>/dev/null| \ |
| read_hex_data | \ |
| sed -n 's/ /\\\\x/pg') |
| if [ ${#data} -eq 0 ]; then |
| # no data -> done |
| break |
| fi |
| # data are prepared for usage with sed |
| datalen=$((${#data} / 5)) |
| |
| req=$(echo ${reqbdy} | \ |
| sed -e "s/@INDEX-4@/${index_f}/g" \ |
| -e "s/@DATALEN-2@/$(_format $datalen 2)/" \ |
| -e "s/@DATA@/${data}/" \ |
| -e "s/@OFFSET-2@/$(_format $offset 2)/") |
| totlen=$(( (${#req} / 4) + 10)) |
| req=$(echo ${reqhdr}${req} | \ |
| sed -e "s/@TOTLEN-4@/$(_format $totlen 4)/") |
| |
| rsp="$(tpm_transfer "$req")" |
| |
| if [ "$res" == "$exp" ]; then |
| logerr "TPM2_NV_Write() failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| rc=1 |
| break |
| fi |
| |
| let offset=$offset+step |
| done |
| |
| return $rc |
| } |
| |
| # Lockan NVRAM location |
| # |
| # @param1: the NVRAM index |
| tpm2_nv_writelock() |
| { |
| local index="$1" |
| |
| local req rsp exp |
| |
| req='\x80\x02\x00\x00\x00\x1f\x00\x00\x01\x38' |
| |
| req+='\x40\x00\x00\x0c@INDEX-4@' |
| req+='\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| |
| req=$(echo $req | \ |
| sed -e "s/@INDEX-4@/$(_format "$index" 4)/") |
| |
| rsp="$(tpm_transfer "$req")" |
| |
| exp=' 80 02 00 00 00 13 00 00 00 00 00 00 00 00 00 00 01 00 00' |
| if [ "$rsp" != "$exp" ]; then |
| logerr "TPM2_NV_WriteLock() failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # Get the list of all PCR banks |
| function tpm2_get_all_pcr_banks() |
| { |
| local all_pcr_banks="" |
| local req rsp exp o l count c bank banks |
| |
| req='\x80\x01\x00\x00\x00\x16\x00\x00\x01\x7a' |
| req+='\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x40' |
| |
| rsp="$(tpm_transfer "$req")" |
| exp=' 80 01 00 00 00 .. 00 00 00 00' |
| if ! [[ "${rsp:0:30}" =~ $exp ]]; then |
| logerr "TPM2_Get_Capability() failed for getting PCR bank info" |
| logerr " expected: $exp [pattern]" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| # read the count byte's lower nibble |
| count=${rsp:56:1} |
| o=57 |
| for ((c=0; c<count;c++)); do |
| bank=${rsp:o:6} |
| case "$bank" in |
| " 00 04") banks="$banks,sha1";; |
| " 00 0b") banks="$banks,sha256";; |
| " 00 0c") banks="$banks,sha384";; |
| " 00 0d") banks="$banks,sha512";; |
| " 00 12") banks="$banks,sm3-256";; |
| *) |
| logerr "Unsupported hash algorithm id ${bank}" |
| return 1 |
| esac |
| o=$((o + 6)) |
| l=$((0x${rsp:o+1:2})) |
| o=$((o + 3 + l * 3)) |
| done |
| |
| echo "$banks" | sed 's/^,//' |
| return 0 |
| } |
| |
| # Set the intial active set of PCR banks of a TPM 2 |
| # |
| # @param1: Comma-separated list of PCR banks to activate |
| # @param2: List of all PCR banks supported by the TPM 2 |
| function tpm2_set_active_pcr_banks() |
| { |
| local pcr_banks="$1" |
| local all_pcr_banks="$2" |
| |
| local req rsp exp pcr_bank totlen count |
| local OIFS="$IFS" active="" |
| |
| req='\x80\x02@TOTLEN-4@\x00\x00\x01\x2b' |
| req+='\x40\x00\00\x0c' |
| req+='\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| req+='@COUNT-4@' |
| |
| totlen=31 |
| count=0 |
| |
| # enable the ones the user wants |
| for pcr_bank in $(echo ${pcr_banks} | tr "," "\n"); do |
| # skip if not even available |
| ! [[ ",${all_pcr_banks}," =~ ",${pcr_bank}," ]] && continue |
| case "${pcr_bank}" in |
| sha1) active="sha1,$active"; req+='\x00\x04\x03\xff\xff\xff';; |
| sha256) active="sha256,$active"; req+='\x00\x0b\x03\xff\xff\xff';; |
| sha384) active="sha384,$active"; req+='\x00\x0c\x03\xff\xff\xff';; |
| sha512) active="sha512,$active"; req+='\x00\x0d\x03\xff\xff\xff';; |
| sm3-256) active="sm3-256,$active"; req+='\x00\x12\x03\xff\xff\xff';; |
| *) |
| logerr "Unsupported PCR bank ${pcr_bank}." |
| return 1 |
| esac |
| count=$((count + 1)) |
| done |
| |
| if [ -z "$active" ]; then |
| logerr "No PCR banks could be allocated." \ |
| "None of the selected algorithms are supported." |
| return 1 |
| fi |
| |
| # disable the rest |
| for pcr_bank in $(echo ${all_pcr_banks} | tr "," "\n"); do |
| # skip over those to activate |
| [[ ",${pcr_banks}," =~ ",${pcr_bank}," ]] && continue |
| |
| case "$pcr_bank" in |
| sha1) req+='\x00\x04\x03\x00\x00\x00';; |
| sha256) req+='\x00\x0b\x03\x00\x00\x00';; |
| sha384) req+='\x00\x0c\x03\x00\x00\x00';; |
| sha512) req+='\x00\x0d\x03\x00\x00\x00';; |
| sm3-256) req+='\x00\x12\x03\x00\x00\x00';; |
| *) |
| logerr "Unsupported PCR bank ${pcr_bank}." |
| return 1 |
| esac |
| count=$((count + 1)) |
| done |
| |
| totlen=$((totlen + count * 6)) |
| |
| req=$(echo $req | \ |
| sed -e "s/@TOTLEN-4@/$(_format "$totlen" 4)/" \ |
| -e "s/@COUNT-4@/$(_format "$count" 4)/") |
| |
| rsp="$(tpm_transfer "$req")" |
| |
| exp=' 80 02 00 00 00 20 00 00 00 00 00 00 00 0d 01' |
| if [ "${rsp:0:45}" != "$exp" ]; then |
| logerr "TPM2_PCR_Allocate() failed" |
| logerr " expected: $exp [first few bytes]" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| echo "$active" | sed 's/,$//' |
| |
| return 0 |
| } |
| |
| # Shut down the TPM 2 with SU_CLEAR |
| function tpm2_shutdown |
| { |
| local req rsp exp |
| |
| req='\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x45\x00\x00' |
| |
| rsp="$(tpm_transfer "$req")" |
| |
| exp=' 80 01 00 00 00 0a 00 00 00 00' |
| if [ "$rsp" != "$exp" ]; then |
| logerr "TPM2_Shutdown(SU_CLEAR) failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # Initialize the TPM 2 |
| # |
| # @param1: the flags |
| # @param2: the configuration file to get the external program from |
| # @parma3: the directory where the TPM is supposed to write it state to |
| # @param4: the TPM owner password to use |
| # @param5: The SRK password to use |
| # @param6: The ID of the VM |
| # @param7: The set of PCR banks to activate |
| init_tpm2() |
| { |
| local flags="$1" |
| local config_file="$2" |
| local tpm2_state_path="$3" |
| local ownerpass="$4" |
| local srkpass="$5" |
| local vmid="$6" |
| local pcr_banks="$7" |
| |
| # where external app writes certs into |
| local certsdir="$tpm2_state_path" |
| local ek tmp output nvindex nxindex_str |
| local all_pcr_banks active_pcr_banks |
| |
| local PLATFORM_CERT_FILE="$certsdir/platform.cert" |
| local EK_CERT_FILE="$certsdir/ek.cert" |
| local EK_TEMP_FILE="$certsdir/ektemplate" |
| |
| start_tpm "$SWTPM" "$tpm2_state_path" |
| if [ $? -ne 0 ]; then |
| logerr "Could not start the TPM 2." |
| return 1 |
| fi |
| |
| export TCSD_USE_TCP_DEVICE=1 |
| export TCSD_TCP_DEVICE_PORT=$TPM_PORT |
| |
| output="$(swtpm_bios --tpm2 -c -o 2>&1)" |
| if [ $? -ne 0 ]; then |
| logerr "swtpm_bios failed: $output" |
| return 1 |
| fi |
| |
| if [ $((flags & $SETUP_CREATE_SPK_F)) -ne 0 ]; then |
| pk=$(tpm2_create_spk "$flags" "${TPM2_SPK_HANDLE}") |
| if [ $? -ne 0 ]; then |
| logerr "tpm2_create_spk failed" |
| return 1 |
| fi |
| logit "Successfully created storage primary key with " \ |
| "handle $(printf "0x%08x" ${TPM2_SPK_HANDLE})." |
| fi |
| |
| if [ $((flags & $SETUP_CREATE_EK_F)) -ne 0 ]; then |
| ek=$(tpm2_create_ek "$flags" "${TPM2_EK_HANDLE}" \ |
| "${EK_TEMP_FILE}") |
| if [ $? -ne 0 ]; then |
| logerr "tpm2_create_ek failed" |
| return 1 |
| fi |
| logit "Successfully created EK with handle" \ |
| "$(printf "0x%08x" ${TPM2_EK_HANDLE})." |
| |
| if [ $((flags & SETUP_TPM2_ECC_F)) -eq 0 ]; then |
| nvindex=${TPM2_NV_INDEX_RSA_EKTemplate} |
| else |
| nvindex=${TPM2_NV_INDEX_ECC_EKTemplate} |
| fi |
| nvindex_str="$(printf "0x%08x" ${nvindex})" |
| |
| if [ $((flags & $SETUP_ALLOW_SIGNING_F )) -ne 0 ]; then |
| tpm2_nv_define \ |
| ${nvindex} \ |
| $((TPMA_NV_PLATFORMCREATE | \ |
| TPMA_NV_AUTHREAD | \ |
| TPMA_NV_OWNERREAD | \ |
| TPMA_NV_PPREAD | \ |
| TPMA_NV_PPWRITE | \ |
| TPMA_NV_NO_DA | \ |
| TPMA_NV_WRITEDEFINE)) \ |
| $(get_filesize "${EK_TEMP_FILE}") |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area ${nvindex_str}" \ |
| "for EK template." |
| return 1 |
| fi |
| tpm2_nv_write \ |
| ${nvindex} \ |
| "${EK_TEMP_FILE}" |
| if [ $? -ne 0 ]; then |
| logerr "Could not write EK template into" \ |
| "NVRAM area ${nvindex_str}." |
| return 1 |
| fi |
| if [ $((flags & SETUP_LOCK_NVRAM_F)) -ne 0 ]; then |
| tpm2_nv_writelock \ |
| ${nvindex} |
| if [ $? -ne 0 ]; then |
| logerr "Could not lock EK template NVRAM" \ |
| "area ${nvindex_str}." |
| return 1 |
| fi |
| fi |
| logit "Successfully created NVRAM area ${nvindex_str} for EK template." |
| fi |
| rm -f ${EK_TEMP_FILE} |
| fi |
| |
| # have external program create the certificates now |
| call_create_certs "$flags" "$config_file" "$certsdir" "$ek" "$vmid" |
| if [ $? -ne 0 ]; then |
| return 1 |
| fi |
| |
| if [ $((flags & SETUP_EK_CERT_F)) -ne 0 ] && \ |
| [ -r "${EK_CERT_FILE}" ]; then |
| |
| if [ $((flags & SETUP_TPM2_ECC_F)) -eq 0 ]; then |
| nvindex=${TPM2_NV_INDEX_RSA_EKCert} |
| else |
| nvindex=${TPM2_NV_INDEX_ECC_EKCert} |
| fi |
| nvindex_str="$(printf "0x%08x" ${nvindex})" |
| |
| tpm2_nv_define \ |
| ${nvindex} \ |
| $((TPMA_NV_PLATFORMCREATE | \ |
| TPMA_NV_AUTHREAD | \ |
| TPMA_NV_OWNERREAD | \ |
| TPMA_NV_PPREAD | \ |
| TPMA_NV_PPWRITE | \ |
| TPMA_NV_NO_DA | \ |
| TPMA_NV_WRITEDEFINE)) \ |
| $(get_filesize "${EK_CERT_FILE}") |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area ${nvindex_str}" \ |
| "for EK certificate." |
| return 1 |
| fi |
| tpm2_nv_write \ |
| ${nvindex} \ |
| "${EK_CERT_FILE}" |
| if [ $? -ne 0 ]; then |
| logerr "Could not write EK certificate into" \ |
| "NVRAM area ${nvindex_str}." |
| return 1 |
| fi |
| if [ $((flags & SETUP_LOCK_NVRAM_F)) -ne 0 ]; then |
| tpm2_nv_writelock \ |
| ${nvindex} |
| if [ $? -ne 0 ]; then |
| logerr "Could not lock EK certificate NVRAM" \ |
| "area ${nvindex_str}." |
| return 1 |
| fi |
| fi |
| logit "Successfully created NVRAM area ${nvindex_str} for EK certificate." |
| rm -f ${EK_CERT_FILE} |
| fi |
| |
| if [ $((flags & SETUP_PLATFORM_CERT_F)) -ne 0 ] && \ |
| [ -r "${PLATFORM_CERT_FILE}" ] ; then |
| |
| nvindex=${TPM2_NV_INDEX_PlatformCert} |
| nvindex_str="$(printf "0x%08x" ${nvindex})" |
| |
| tpm2_nv_define \ |
| ${nvindex} \ |
| $((TPMA_NV_PLATFORMCREATE | \ |
| TPMA_NV_AUTHREAD | \ |
| TPMA_NV_OWNERREAD | \ |
| TPMA_NV_PPREAD | \ |
| TPMA_NV_PPWRITE | \ |
| TPMA_NV_NO_DA | \ |
| TPMA_NV_WRITEDEFINE)) \ |
| $(get_filesize "${PLATFORM_CERT_FILE}") |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area ${nvindex_str}" \ |
| "for platform certificate." |
| return 1 |
| fi |
| tpm2_nv_write \ |
| ${nvindex} \ |
| "${PLATFORM_CERT_FILE}" |
| if [ $? -ne 0 ]; then |
| logerr "Could not write platform certificate into" \ |
| "NVRAM area ${nvindex_str}." |
| return 1 |
| fi |
| if [ $((flags & SETUP_LOCK_NVRAM_F)) -ne 0 ]; then |
| tpm2_nv_writelock \ |
| ${nvindex} |
| if [ $? -ne 0 ]; then |
| logerr "Could not lock platform certificate" \ |
| "NVRAM area ${nvindex_str}." |
| return 1 |
| fi |
| fi |
| logit "Successfully created NVRAM area ${nvindex_str}" \ |
| "for platform certificate." |
| rm -f ${PLATFORM_CERT_FILE} |
| fi |
| |
| if [ "$pcr_banks" != "-" ]; then |
| all_pcr_banks="$(tpm2_get_all_pcr_banks)" |
| [ $? -ne 0 ] && return 1 |
| active_pcr_banks="$(tpm2_set_active_pcr_banks "$pcr_banks" "$all_pcr_banks")" |
| [ $? -ne 0 ] && return 1 |
| logit "Successfully activated PCR banks $active_pcr_banks among $all_pcr_banks." |
| fi |
| |
| # FIXME: From here on missing functions... |
| |
| if [ $((flags & SETUP_DISPLAY_RESULTS_F)) -ne 0 ]; then |
| echo "Display of results not supported yet." |
| fi |
| |
| tpm2_shutdown |
| [ $? -ne 0 ] && return 1 |
| |
| return 0 |
| } |
| |
| ################################################################################# |
| |
| # Check whether a TPM state file already exists and whether we are |
| # allowed to overwrite it or should leave it as is. |
| # |
| # @param1: flags |
| # @param2: the TPM state path (directory) |
| # |
| # Return 0 if we can continue, 2 if we should end without an error (state file |
| # exists and we are not supposed to overwrite it), or 1 if we need to terminate |
| # with an error |
| check_state_overwrite() |
| { |
| local flags="$1" |
| local tpm_state_path="$2" |
| |
| local statefile |
| |
| if [ $((flags & SETUP_TPM2_F)) -ne 0 ]; then |
| statefile="tpm2-00.permall" |
| else |
| statefile="tpm-00.permall" |
| fi |
| |
| if [ -f "${tpm_state_path}/${statefile}" ]; then |
| if [ $((flags & SETUP_STATE_NOT_OVERWRITE_F)) -ne 0 ]; then |
| logit "Not overwriting existing state file." |
| return 2 |
| fi |
| if [ $((flags & SETUP_STATE_OVERWRITE_F)) -ne 0 ]; then |
| return 0 |
| fi |
| logerr "Found existing TPM state file ${statefile}." |
| return 1 |
| fi |
| return 0 |
| } |
| |
| versioninfo() |
| { |
| cat <<EOF |
| TPM emulator setup tool version @SWTPM_VER_MAJOR@.@SWTPM_VER_MINOR@.@SWTPM_VER_MICRO@ |
| EOF |
| } |
| |
| print_capabilities() |
| { |
| echo '{ "type": "swtpm_setup",' \ |
| '"features": [ "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd" ]'\ |
| '}' |
| } |
| |
| usage() |
| { |
| versioninfo |
| cat <<EOF |
| |
| Usage: $1 [options] |
| |
| The following options are supported: |
| |
| --runas <user> : Use the given user id to switch to and run this program; |
| this parameter is interpreted by swtpm_setup that switches |
| to this user and invokes swtpm_setup.sh; defaults to 'tss' |
| |
| --tpm-state <dir>: Path to a directory where the TPM's state will be written |
| into; this is a mandatory argument |
| |
| --tpmstate <dir> : This is an alias for --tpm-state <dir>. |
| |
| --tpm <executable> |
| : Path to the TPM executable; this is an optional argument and |
| by default $SWTPM is used. |
| |
| --swtpm_ioctl <executable> |
| : Path to the swtpm_ioctl executable; this is an optional |
| argument and by default $SWTPM_IOCTL is used. |
| |
| --tpm2 : Setup a TPM 2; by default a TPM 1.2 is setup. |
| |
| --createek : Create the EK |
| |
| --allow-signing : Create an EK that can be used for signing; |
| this option requires --tpm2. |
| |
| --decryption : Create an EK that can be used for key encipherment; |
| this is the default unless --allow-signing is given; |
| this option requires --tpm2. |
| |
| --ecc : Create ECC keys rather than RSA keys; this requires --tpm2 |
| |
| --take-ownership : Take ownership; this option implies --createek |
| --ownerpass <password> |
| : Provide custom owner password; default is $DEFAULT_OWNER_PASSWORD |
| --owner-well-known: |
| : Use an owner password of 20 zero bytes |
| --srkpass <password> |
| : Provide custom SRK password; default is $DEFAULT_SRK_PASSWORD |
| --srk-well-known: |
| : Use an SRK password of 20 zero bytes |
| --create-ek-cert : Create an EK certificate; this implies --createek |
| |
| --create-platform-cert |
| : Create a platform certificate; this implies --create-ek-cert |
| |
| --create-spk : Create storage primary key; this requires --tpm2 |
| |
| --lock-nvram : Lock NVRAM access |
| |
| --display : At the end display as much info as possible about the |
| configuration of the TPM |
| |
| --config <config file> |
| : Path to configuration file; default is $DEFAULT_CONFIG_FILE |
| |
| --logfile <logfile> |
| : Path to log file; default is logging to stderr |
| |
| --keyfile <keyfile> |
| : Path to a key file containing the encryption key for the |
| TPM to encrypt its persistent state with. The content |
| must be a 32 hex digit number representing a 128bit AES key. |
| This parameter will be passed to the TPM using |
| '--key file=<file>'. |
| |
| --keyfile-fd <fd>: Like --keyfile but file descriptor is given to read |
| encryption key from. |
| |
| --pwdfile <pwdfile> |
| : Path to a file containing a passphrase from which the |
| TPM will derive the 128bit AES key. The passphrase can be |
| 32 bytes long. |
| This parameter will be passed to the TPM using |
| '--key pwdfile=<file>'. |
| |
| --pwdfile-fd <fd>: Like --pwdfile but file descriptor to read passphrase |
| from is given. |
| |
| --cipher <cipher>: The cipher to use; either aes-128-cbc or aes-256-cbc; |
| the default is aes-128-cbc; the same cipher must be |
| used on the swtpm command line |
| |
| --overwrite : Overwrite existing TPM state be re-initializing it; if this |
| option is not given, this program will return an error if |
| existing state is detected |
| |
| --not-overwrite : Do not overwrite existing TPM state but silently end |
| |
| --pcr-banks <banks> |
| : Set of PCR banks to activate. Provide a comma separated list |
| like 'sha1,sha256'. '-' to skip and leave all banks active. |
| Default: $DEFAULT_PCR_BANKS |
| |
| --tcsd-system-ps-file <file> |
| : File to copy TCSD's system_ps_file to; only for TPM 1.2 |
| |
| --version : Display version and exit |
| |
| --help,-h,-? : Display this help screen |
| EOF |
| } |
| |
| main() |
| { |
| local flags=0 |
| local tpm_state_path="" |
| local config_file="$DEFAULT_CONFIG_FILE" |
| local vmid="" |
| local ret |
| local keyfile pwdfile cipher="aes-128-cbc" |
| local keyfile_fd pwdfile_fd |
| local got_ownerpass=0 got_srkpass=0 |
| local pcr_banks="" |
| local tcsd_system_ps_file="" |
| |
| while [ $# -ne 0 ]; do |
| case "$1" in |
| --tpm-state|--tpmstate) shift; tpm_state_path="$1";; |
| --tpm) shift; SWTPM="$1";; |
| --swtpm_ioctl) shift; SWTPM_IOCTL="$1";; |
| --tpm2) flags=$((flags | SETUP_TPM2_F));; |
| --ecc) flags=$((flags | SETUP_TPM2_ECC_F));; |
| --createek) flags=$((flags | SETUP_CREATE_EK_F));; |
| --create-spk) flags=$((flags | SETUP_CREATE_SPK_F));; |
| --take-ownership) flags=$((flags | |
| SETUP_CREATE_EK_F|SETUP_TAKEOWN_F));; |
| --ownerpass) shift; ownerpass="$1"; got_ownerpass=1;; |
| --owner-well-known) flags=$((flags | SETUP_OWNERPASS_ZEROS_F));; |
| --srkpass) shift; srkpass="$1"; got_srkpass=1;; |
| --srk-well-known) flags=$((flags | SETUP_SRKPASS_ZEROS_F));; |
| --create-ek-cert) flags=$((flags | |
| SETUP_CREATE_EK_F|SETUP_EK_CERT_F));; |
| --create-platform-cert) flags=$((flags | |
| SETUP_CREATE_EK_F|SETUP_PLATFORM_CERT_F));; |
| --lock-nvram) flags=$((flags | SETUP_LOCK_NVRAM_F));; |
| --display) flags=$((flags | SETUP_DISPLAY_RESULTS_F));; |
| --config) shift; config_file="$1";; |
| --vmid) shift; vmid="$1";; |
| --keyfile) shift; keyfile="$1";; |
| --keyfile-fd) shift; keyfile_fd="$1";; |
| --pwdfile) shift; pwdfile="$1";; |
| --pwdfile-fd) shift; pwdfile_fd="$1";; |
| --cipher) shift; cipher="$1";; |
| --runas) shift;; # ignore here |
| --logfile) shift; LOGFILE="$1";; |
| --overwrite) flags=$((flags | SETUP_STATE_OVERWRITE_F));; |
| --not-overwrite) flags=$((flags | SETUP_STATE_NOT_OVERWRITE_F));; |
| --allow-signing) flags=$((flags | SETUP_ALLOW_SIGNING_F));; |
| --decryption) flags=$((flags | SETUP_DECRYPTION_F));; |
| --pcr-banks) shift; pcr_banks="${pcr_banks},$1";; |
| --tcsd-system-ps-file) shift; tcsd_system_ps_file="$1";; |
| --version) versioninfo $0; exit 0;; |
| --print-capabilities) print_capabilities; exit 0;; |
| --help|-h|-?) usage $0; exit 0;; |
| *) logerr "Unknown option $1"; usage $0; exit 1;; |
| esac |
| shift |
| done |
| |
| [ $got_ownerpass -eq 0 ] && flags=$((flags | SETUP_OWNERPASS_ZEROS_F)) |
| [ $got_srkpass -eq 0 ] && flags=$((flags | SETUP_SRKPASS_ZEROS_F)) |
| |
| pcr_banks="$(echo $pcr_banks | |
| tr -s ',' | |
| sed -e 's/^,//' -e 's/,$//' | |
| tr '[:upper:]' '[:lower:]')" |
| [ -z "$pcr_banks" ] && pcr_banks="$DEFAULT_PCR_BANKS" |
| |
| # set owner password to default if user didn't provide any password wish |
| # and wants to take ownership |
| if [ $((flags & SETUP_TAKEOWN_F)) -ne 0 ] && \ |
| [ $((flags & SETUP_OWNERPASS_ZEROS_F)) -ne 0 ] && \ |
| [ $got_ownerpass -eq 0 ]; then |
| ownerpass=$DEFAULT_OWNER_PASSWORD |
| fi |
| |
| # set SRK password to default if user didn't provide any password wish |
| # and wants to take ownership |
| if [ $((flags & SETUP_TAKEOWN_F)) -ne 0 ] && \ |
| [ $((flags & SETUP_SRKPASS_ZEROS_F)) -ne 0 ] && \ |
| [ $got_srkpass -eq 0 ]; then |
| srkpass=$DEFAULT_SRK_PASSWORD |
| fi |
| |
| if [ -n "$LOGFILE" ]; then |
| touch $LOGFILE &>/dev/null |
| if [ ! -w "$LOGFILE" ]; then |
| echo "Cannot write to logfile ${LOGFILE}." >&2 |
| exit 1 |
| fi |
| fi |
| if [ "$tpm_state_path" == "" ]; then |
| logerr "--tpm-state must be provided" |
| exit 1 |
| fi |
| if [ ! -d "$tpm_state_path" ]; then |
| logerr "$tpm_state_path is not a directory that user $(whoami) could access." |
| exit 1 |
| fi |
| |
| if [ ! -r "$tpm_state_path" ]; then |
| logerr "Need read rights on directory $tpm_state_path for user $(whoami)." |
| exit 1 |
| fi |
| |
| if [ ! -w "$tpm_state_path" ]; then |
| logerr "Need write rights on directory $tpm_state_path for user $(whoami)." |
| exit 1 |
| fi |
| |
| if [ $((flags & SETUP_TPM2_F)) -ne 0 ]; then |
| if [ $((flags & SETUP_TAKEOWN_F)) -ne 0 ]; then |
| logerr "Taking ownership is not supported for TPM 2." |
| exit 1 |
| fi |
| else |
| if [ $((flags & SETUP_TPM2_ECC_F)) -ne 0 ]; then |
| logerr "--ecc requires --tpm2." |
| exit 1 |
| fi |
| if [ $((flags & SETUP_CREATE_SPK_F)) -ne 0 ]; then |
| logerr "--create-spk requires --tpm2." |
| exit 1 |
| fi |
| if [ -n "${tcsd_system_ps_file}" ]; then |
| touch ${tcsd_system_ps_file} &>/dev/null |
| if [ $? -ne 0 ]; then |
| logerr "Cannot write to ${tcsd_system_ps_file}" |
| exit 1 |
| fi |
| rm -f ${tcsd_system_ps_file} |
| fi |
| fi |
| |
| check_state_overwrite "$flags" "$tpm_state_path" |
| case $? in |
| 0) ;; |
| 1) exit 1;; |
| 2) exit 0;; |
| esac |
| |
| rm -f \ |
| "$tpm_state_path"/*permall \ |
| "$tpm_state_path"/*volatilestate \ |
| "$tpm_state_path"/*savestate \ |
| 2>/dev/null |
| if [ $? -ne 0 ]; then |
| logerr "Could not remove previous state files. Need execute access rights on the directory." |
| exit 1 |
| fi |
| |
| if [ -z "$SWTPM" ]; then |
| logerr "Default TPM 'swtpm' could not be found and was not provided using --tpm." |
| exit 1 |
| fi |
| |
| if [ ! -x "$(echo $SWTPM | cut -d " " -f1)" ]; then |
| logerr "TPM at $SWTPM is not an executable." |
| exit 1 |
| fi |
| |
| if [ $((flags & SETUP_TPM2_F)) -eq 0 ]; then |
| TCSD=`type -P tcsd` |
| if [ -z "$TCSD" ]; then |
| logerr "tcsd program not found. (PATH=$PATH)" |
| exit 1 |
| fi |
| if [ ! -x "$TCSD" ]; then |
| logerr "TSS at $TCSD is not an executable." |
| exit 1 |
| fi |
| fi |
| |
| if [ -z "$SWTPM_IOCTL" ]; then |
| logerr "Default 'swtpm_ioctl' could not be found and was not provided using --swtpm_ioctl." |
| exit 1 |
| fi |
| |
| if [ ! -x "$(echo $SWTPM_IOCTL | cut -d " " -f1)" ]; then |
| logerr "swtpm_ioctl at $SWTPM_IOCTL is not an executable." |
| exit 1 |
| fi |
| |
| if [ ! -r "$config_file" ]; then |
| logerr "Cannot access config file ${config_file}." |
| exit 1 |
| fi |
| |
| if [ -n "$cipher" ]; then |
| if ! [[ "$cipher" =~ ^(aes-128-cbc|aes-cbc|aes-256-cbc)$ ]]; |
| then |
| logerr "Unsupported cipher $cipher." |
| exit 1 |
| fi |
| cipher=",mode=$cipher" |
| fi |
| |
| if [ -n "$keyfile" ]; then |
| if [ ! -r "$keyfile" ]; then |
| logerr "Cannot access keyfile $keyfile." |
| exit 1 |
| fi |
| SWTPM="$SWTPM --key file=${keyfile}${cipher}" |
| logit " The TPM's state will be encrypted with a provided key." |
| elif [ -n "$pwdfile" ]; then |
| if [ ! -r "$pwdfile" ]; then |
| logerr "Cannot access passphrase file $pwdfile." |
| exit 1 |
| fi |
| SWTPM="$SWTPM --key pwdfile=${pwdfile}${cipher}" |
| logit " The TPM's state will be encrypted using a key derived from a passphrase." |
| elif [ -n "$keyfile_fd" ]; then |
| if ! [[ "$keyfile_fd" =~ ^[0-9]+$ ]]; then |
| logerr "--keyfile-fd parameter $keyfile_fd is not a valid file descriptor" |
| exit 1 |
| fi |
| SWTPM="$SWTPM --key fd=${keyfile_fd}${cipher}" |
| logit " The TPM's state will be encrypted with a provided key (fd)." |
| elif [ -n "$pwdfile_fd" ]; then |
| if ! [[ "$pwdfile_fd" =~ ^[0-9]+$ ]]; then |
| logerr "--pwdfile-fd parameter $pwdfile_fd is not a valid file descriptor" |
| exit 1 |
| fi |
| SWTPM="$SWTPM --key pwdfd=${pwdfile_fd}${cipher}" |
| logit " The TPM's state will be encrypted using a key derived from a passphrase (fd)." |
| fi |
| |
| # tcsd only runs as tss, so we have to be root or tss here; TPM 1.2 only |
| if [ $((flags & SETUP_TPM2_F)) -eq 0 ]; then |
| user=$(id -un) |
| if [ "$user" != "root" ] && [ "$user" != "@TSS_USER@" ]; then |
| logerr "Need to be either root or @TSS_USER@ for being able to use tcsd" |
| exit 1 |
| fi |
| fi |
| |
| logit "Starting vTPM manufacturing as $(id -n -u):$(id -n -g) @ $(date +%c)" |
| |
| if [ $((flags & SETUP_TPM2_F)) -eq 0 ]; then |
| init_tpm $flags "$config_file" "$tpm_state_path" \ |
| "$ownerpass" "$srkpass" "$vmid" "$tcsd_system_ps_file" |
| else |
| SWTPM="$SWTPM --tpm2" |
| init_tpm2 $flags "$config_file" "$tpm_state_path" \ |
| "$ownerpass" "$srkpass" "$vmid" "$pcr_banks" |
| fi |
| ret=$? |
| if [ $ret -eq 0 ]; then |
| logit "Successfully authored TPM state." |
| else |
| logerr "An error occurred. Authoring the TPM state failed." |
| fi |
| |
| logit "Ending vTPM manufacturing @ $(date +%c)" |
| |
| exit $ret |
| } |
| |
| main "$@" |