| #!/bin/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 |
| TCSD=`type -P tcsd` |
| if [ -z "$TCSD" ]; then |
| echo "Error: tcsd program not found. (PATH=$PATH)" |
| exit 1 |
| fi |
| |
| ECHO=`which echo` |
| if [ -z "$ECHO" ]; then |
| echo "Error: external echo program not found." |
| exit 1 |
| fi |
| ECHO=`which echo` |
| if [ -z "$ECHO" ]; then |
| echo "Error: external echo program not found." |
| exit 1 |
| fi |
| |
| 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_DISPLAY_RESULTS_F=4096 |
| |
| # default values for passwords |
| DEFAULT_OWNER_PASSWORD=ooo |
| DEFAULT_SRK_PASSWORD=sss |
| |
| # default configuration file |
| DEFAULT_CONFIG_FILE="/etc/swtpm_setup.conf" |
| |
| # 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_AUTHREAD=$((0x40000)) |
| TPMA_NV_NO_DA=$((0x2000000)) |
| TPMA_NV_PPWRITE=$((0x1)) |
| TPMA_NV_POLICY_DELETE=$((0x400)) |
| TPMA_NV_WRITEDEFINE=$((0x2000)) |
| |
| TPM2_NV_INDEX_EKCert=$((0x01c00000)) |
| TPM2_NV_INDEX_PlatformCert=$((0x01c08000)) |
| |
| # custom option |
| TPM2_EK_HANDLE=$((0x81010000)) |
| |
| # Default logging goes to stderr |
| LOGFILE="" |
| |
| 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 |
| } |
| |
| # 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 "$LOGFILE" ]; then |
| logparam="--logfile $LOGFILE" |
| fi |
| |
| 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 |
| if [ $((flags & SETUP_EK_CERT_F)) -ne 0 ]; then |
| cmd="$create_certs_tool \ |
| --type ek \ |
| --ek "$ek" \ |
| --dir "$certdir" \ |
| --vmid "$vmid" \ |
| ${logparam} ${params}" |
| logit " Invoking: $(echo $cmd | tr -s " ")" |
| tmp="$(eval $cmd 2>&1)" |
| ret=$? |
| if [ $ret -ne 0 ]; then |
| logerr "Error running '$cmd' : $tmp" |
| return $ret |
| fi |
| fi |
| if [ $((flags & SETUP_PLATFORM_CERT_F)) -ne 0 ]; then |
| cmd="$create_certs_tool \ |
| --type platform \ |
| --ek "$ek" \ |
| --dir "$certdir" \ |
| --vmid "$vmid" \ |
| ${logparam} ${params}" |
| logit " Invoking: $(echo $cmd | tr -s " ")" |
| tmp="$(eval $cmd 2>&1)" |
| ret=$? |
| if [ $ret -ne 0 ]; then |
| logerr "Error running '$cmd' : $tmp" |
| 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 |
| |
| while [ $ctr -lt 100 ]; do |
| TPM_PORT=$(shuf -i 30000-65535 -n 1) |
| |
| # skip used ports |
| if [ -n "$(netstat -lnpt 2>/dev/null | |
| gawk '{print $4}' | |
| grep ":${TPM_PORT} ")" ]; then |
| let ctr=$ctr+1 |
| continue |
| fi |
| |
| $swtpm --flags not-need-init -p $TPM_PORT --tpmstate dir=$swtpm_state 2>&1 1>/dev/null & |
| SWTPM_PID=$! |
| |
| # poll for open port (good) or the process to have |
| # disappeared (bad); whatever happens first |
| while :; do |
| kill -0 $SWTPM_PID 2>/dev/null |
| if [ $? -ne 0 ]; then |
| # process dead; try next socket |
| break |
| fi |
| |
| # Did swtpm open the TCP port yet? |
| if [ -n "$(netstat -napt 2>/dev/null | |
| grep " $SWTPM_PID/" | |
| grep ":$TPM_PORT ")" ]; then |
| # in rare occasions tcsd refuses connections |
| # test the connection |
| exec 100<>/dev/tcp/localhost/$TPM_PORT 2>/dev/null |
| if [ $? -ne 0 ]; then |
| stop_tpm |
| break |
| fi |
| exec 100>&- |
| echo "TPM is listening on TCP port $TPM_PORT." |
| return 0 |
| fi |
| sleep 0.1 |
| done |
| |
| let ctr=$ctr+1 |
| done |
| |
| return 1 |
| } |
| |
| # Stop the TPM by sigalling it with a SIGTERM |
| stop_tpm() |
| { |
| [ "$SWTPM_PID" != "" ] && kill -SIGTERM $SWTPM_PID |
| 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 |
| |
| export TSS_TCSD_PORT |
| |
| TCSD_CONFIG="$(mktemp)" |
| TCSD_DATA_DIR="$(mktemp -d)" |
| TCSD_DATA_FILE="$(mktemp --tmpdir=$TCSD_DATA_DIR)" |
| |
| 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=$(shuf -i 30000-65535 -n 1) |
| |
| # skip used ports |
| if [ -n "$(netstat -lnpt 2>/dev/null | |
| gawk '{print $4}' | |
| grep ":${TSS_TCSD_PORT} ")" ]; then |
| let ctr=$ctr+1 |
| continue |
| fi |
| |
| 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 |
| |
| 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 |
| |
| # TSS still alive? |
| # poll for open port (good) or the process to have |
| # disappeared (bad); whatever happens first |
| while :; do |
| kill -0 $TCSD_PID 2>/dev/null |
| if [ $? -ne 0 ]; then |
| # process dead; try next socket |
| break |
| fi |
| |
| # Did tcsd open the TCP port yet? |
| if [ -n "$(netstat -naptl 2>/dev/null | |
| grep "LISTEN" | |
| grep " $TCSD_PID/" | |
| grep ":$TSS_TCSD_PORT ")" ]; then |
| # in rare occasions tcsd refuses connections |
| # test the connection |
| exec 100<>/dev/tcp/localhost/$TSS_TCSD_PORT 2>/dev/null |
| if [ $? -ne 0 ]; then |
| stop_tcsd |
| break |
| fi |
| exec 100>&- |
| echo "TSS is listening on TCP port $TSS_TCSD_PORT." |
| return 0 |
| fi |
| sleep 0.1 |
| done |
| |
| let ctr=$ctr+1 |
| done |
| |
| return 1 |
| } |
| |
| # Stop the TSS |
| stop_tcsd() |
| { |
| [ "$TCSD_PID" != "" ] && kill -SIGTERM $TCSD_PID |
| TCSD_PID= |
| } |
| |
| # Cleanup everything including TPM, TSS, and files we may have created |
| cleanup() |
| { |
| stop_tpm |
| stop_tcsd |
| rm -rf "$TCSD_CONFIG" "$TCSD_DATA_FILE" "$TCSD_DATA_DIR" |
| } |
| |
| # 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} |
| $ECHO -en "$1" >&100 |
| |
| od -t x1 -A n -w512 <&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 |
| init_tpm() |
| { |
| local flags="$1" |
| local config_file="$2" |
| local tpm_state_path="$3" |
| local ownerpass="$4" |
| local srkpass="$5" |
| local vmid="$6" |
| |
| # 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 |
| 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] |
| ") |
| 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 $(stat -c%s "${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 $(stat -c%s "${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}'` |
| for i in $nvidxs; do |
| logit "Content of NVRAM area $i:" |
| tmp="tpm_nvread -i $i --password=$ownerpass" |
| 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 |
| a=$(expect -c " |
| spawn tpm_clear |
| expect { |
| \"Enter owner password:\" { send \"$ownerpass\n\" } |
| } |
| expect eof |
| catch wait result |
| exit [lindex \$result 3] |
| ") |
| 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 |
| stop_tpm |
| start_tpm "$SWTPM" "$tpm_state_path" |
| |
| if [ $? -ne 0 ]; then |
| logerr "Could not re-start TPM." |
| return 1 |
| fi |
| |
| TCSD_TCP_DEVICE_PORT=$TPM_PORT |
| output="$(swtpm_bios -c)" |
| if [ $? -ne 0 ]; then |
| logerr "swtpm_bios -c -o failed: $output" |
| return 1 |
| fi |
| logit "Successfully enabled and activated the TPM" |
| fi |
| |
| return 0 |
| } |
| |
| ################################# TPM 2 ################################## |
| |
| # Get a couple of random numbers from the host and stir the TPM |
| # RNG with them |
| # |
| tpm2_stirrandom() |
| { |
| local req r rsp exp |
| |
| # just the header, expecting 0x18 bytes of random data |
| req='\x80\x01\x00\x00\x00\x24\x00\x00\x01\x46\x00\x18' |
| r=$(dd if=/dev/urandom count=24 bs=1 2>/dev/null | \ |
| od -An -tx1 -w64 | sed 's/ /\\x/g') |
| |
| rsp="$(tpm_transfer "${req}${r}")" |
| |
| exp=' 80 01 00 00 00 0a 00 00 00 00' |
| if [ "$rsp" != "$exp" ]; then |
| logerr "TPM2_Stirrandom() failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| tpm2_changeeps() |
| { |
| local req rsp exp |
| |
| req='\x80\x02\x00\x00\x00\x1b\x00\x00\x01\x24\x40\x00\x00\x0c\x00\x00' |
| req+='\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00' |
| |
| 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_ChangeEPS() failed" |
| logerr " expected: $exp" |
| logerr " received: $rsp" |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # Create the primary key (EK equivalent) |
| # |
| # @param1: flags |
| tpm2_createprimary_ek_rsa() |
| { |
| local flags="$1" |
| |
| local symkeydata keyflags totlen publen off min_exp authpolicy |
| |
| if [ $((flags & SETUP_ALLOW_SIGNING_F)) -ne 0 ]; then |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, sign, decrypt |
| keyflags=$((0x000600b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x10' |
| publen=$((0x36)) |
| totlen=$((0x5f)) |
| 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)) |
| totlen=$((0x63)) |
| 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}" |
| 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 req rsp res |
| 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\x00' |
| req+='\x04\x00\x00\x00\x00' |
| # Size of TPM2B_PUBLIC |
| req+='@PUBLEN-2@' |
| # TPM_ALG_RSA, TPM_ALG_SHA256 |
| req+='\x00\x01\x00\x0b' |
| # fixedTPM, fixedParent, sensitiveDatOrigin, adminWithPolicy |
| # restricted, decrypt |
| req+='@KEYFLAGS-4@' |
| # authPolicy;32 bytes |
| req+='@AUTHPOLICYLEN-2@' |
| req+='@AUTHPOLICY@' |
| req+='@SYMKEYDATA@' |
| # scheme: TPM_ALG_NULL, keyBits: 2048bits |
| req+='\x00\x10\x08\x00' |
| # exponent |
| req+='\x00\x00\x00\x00' |
| # TPM2B_DATA |
| req+='\x00\x00' |
| # TPML_PCR_SELECTION |
| req+='\x00\x00\x00\x00\x00\x00' |
| |
| req=$(echo $req | \ |
| sed -e "s/@KEYFLAGS-4@/$(_format "$keyflags" 4)/" \ |
| -e "s/@SYMKEYDATA@/$symkeydata/" \ |
| -e "s/@PUBLEN-2@/$(_format "$publen" 2)/" \ |
| -e "s/@TOTLEN-4@/$(_format "$totlen" 4)/" \ |
| -e "s/@KEYHANDLE-4@/$primaryhandle/" \ |
| -e "s/@AUTHPOLICY@/$authpolicy/" \ |
| -e "s/@AUTHPOLICYLEN-2@/$(_format "$authpolicylen" 2)/") |
| |
| 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 |
| |
| return 0 |
| } |
| |
| # Create the primary key as an ECC key (EK equivalent) |
| # |
| # @param1: flags |
| tpm2_createprimary_ek_ecc() |
| { |
| local flags="$1" |
| |
| local req rsq exp res min_exp |
| local flags="$1" |
| |
| local req rsq exp res symkeydata keyflags totlen publen off1 off2 |
| |
| if [ $((flags & SETUP_ALLOW_SIGNING_F)) -ne 0 ]; then |
| # keyflags: fixedTPM, fixedParent, sensitiveDatOrigin, |
| # adminWithPolicy, sign, decrypt |
| keyflags=$((0x000600b2)) |
| # symmetric: TPM_ALG_NULL |
| symkeydata='\\x00\\x10' |
| publen=$((0x36)) |
| totlen=$((0x5f)) |
| 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)) |
| totlen=$((0x63)) |
| # some version of TPM2 returns 942, another 990 |
| min_exp=942 |
| # offset of length indicator for key |
| off1=222 |
| off2=324 |
| fi |
| |
| # 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+='\x40\x00\x00\x0b' |
| # 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 |
| req+='\x00\x23\x00\x0b' |
| # flags: fixedTPM, fixedParent, sensitiveDatOrigin, adminWithPolicy |
| # restricted, decrypt |
| req+='@KEYFLAGS-4@' |
| # authPolicy: size = 32 bytes |
| req+='\x00\x20' |
| req+='\x83\x71\x97\x67\x44\x84\xb3\xf8\x1a\x90\xcc\x8d' |
| req+='\x46\xa5\xd7\x24\xfd\x52\xd7\x6e\x06\x52\x0b\x64' |
| req+='\xf2\xa1\xda\x1b\x33\x14\x69\xaa' |
| req+='@SYMKEYDATA@' |
| # scheme: TPM_ALG_NULL, curveID: TPM_ECC_NIST_P256 |
| req+='\x00\x10\x00\x03' |
| # kdf->scheme: TPM_ALG_NULL |
| req+='\x00\x10' |
| # TPM2B_DATA |
| req+='\x00\x00' |
| # TPML_PCR_SELECTION |
| req+='\x00\x00\x00\x00\x00\x00\x00\x00' |
| |
| req=$(echo $req | \ |
| sed -e "s/@KEYFLAGS-4@/$(_format "$keyflags" 4)/" \ |
| -e "s/@SYMKEYDATA@/$symkeydata/" \ |
| -e "s/@PUBLEN-2@/$(_format "$publen" 2)/" \ |
| -e "s/@TOTLEN-4@/$(_format "$totlen" 4)/") |
| |
| 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 |
| |
| 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 primary key, either RSA or ECC |
| # |
| # @param1: flags |
| # @param2: non-evict handle, if any |
| tpm2_createprimary() |
| { |
| local flags="$1" |
| local nehandle="$2" |
| |
| local res handle |
| |
| tpm2_stirrandom |
| tpm2_changeeps |
| [ $? -ne 0 ] && return 1 |
| |
| if [ $((flags & SETUP_TPM2_ECC_F)) -ne 0 ]; then |
| res=$(tpm2_createprimary_ek_ecc "$flags") |
| else |
| res=$(tpm2_createprimary_ek_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| \ |
| od -t x1 -A n -w2048 | \ |
| 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 |
| } |
| |
| # 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 |
| init_tpm2() |
| { |
| local flags="$1" |
| local config_file="$2" |
| local tpm2_state_path="$3" |
| local ownerpass="$4" |
| local srkpass="$5" |
| local vmid="$6" |
| |
| # where external app writes certs into |
| local certsdir="$tpm2_state_path" |
| local ek tmp output |
| |
| local PLATFORM_CERT_FILE="$certsdir/platform.cert" |
| local EK_CERT_FILE="$certsdir/ek.cert" |
| |
| 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_EK_F)) -ne 0 ]; then |
| ek=$(tpm2_createprimary "$flags" "${TPM2_EK_HANDLE}") |
| if [ $? -ne 0 ]; then |
| logerr "tpm2_createprimary failed" |
| return 1 |
| fi |
| logit "Successfully created EK." |
| 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 |
| tpm2_nv_define \ |
| $((TPM2_NV_INDEX_EKCert)) \ |
| $((TPMA_NV_PLATFORMCREATE | \ |
| TPMA_NV_AUTHREAD | \ |
| TPMA_NV_NO_DA | \ |
| TPMA_NV_PPWRITE | \ |
| TPMA_NV_POLICY_DELETE | \ |
| TPMA_NV_WRITEDEFINE)) \ |
| $(stat -c%s "${EK_CERT_FILE}") |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area for EK certificate." |
| return 1 |
| fi |
| tpm2_nv_write \ |
| $((TPM2_NV_INDEX_EKCert)) \ |
| "${EK_CERT_FILE}" |
| if [ $? -ne 0 ]; then |
| logerr "Could not write EK certificate into" \ |
| "NVRAM location." |
| return 1 |
| fi |
| if [ $((flags & SETUP_LOCK_NVRAM_F)) -ne 0 ]; then |
| tpm2_nv_writelock \ |
| $((TPM2_NV_INDEX_EKCert)) |
| if [ $? -ne 0 ]; then |
| logerr "Could not lock EK certificate NVRAM" \ |
| "location." |
| return 1 |
| fi |
| 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 |
| tpm2_nv_define \ |
| $((TPM2_NV_INDEX_PlatformCert)) \ |
| $((TPMA_NV_PLATFORMCREATE | \ |
| TPMA_NV_AUTHREAD | \ |
| TPMA_NV_NO_DA | \ |
| TPMA_NV_PPWRITE | \ |
| TPMA_NV_POLICY_DELETE | \ |
| TPMA_NV_WRITEDEFINE)) \ |
| -s $(stat -c%s "${PLATFORM_CERT_FILE}") |
| if [ $? -ne 0 ]; then |
| logerr "Could not create NVRAM area for platform" \ |
| "certificate." |
| return 1 |
| fi |
| tpm2_nv_write \ |
| $((TPM2_NV_INDEX_PlatformCert)) \ |
| "$PLATFORM_CERT_FILE" 2>&1 >/dev/null |
| if [ $? -ne 0 ]; then |
| logerr "Could not write platform certificate into" \ |
| "NVRAM location." |
| return 1 |
| fi |
| if [ $((flags & SETUP_LOCK_NVRAM_F)) -ne 0 ]; then |
| tpm2_nv_writelock \ |
| $((TPM2_NV_INDEX_PlatformCert)) |
| if [ $? -ne 0 ]; then |
| logerr "Could not lock platform certificate" \ |
| "NVRAM location." |
| return 1 |
| fi |
| fi |
| logit "Successfully created NVRAM area for platform" \ |
| "certificate." |
| rm -f ${PLATFORM_CERT_FILE} |
| fi |
| |
| # FIXME: From here on missing functions... |
| |
| if [ $((flags & SETUP_DISPLAY_RESULTS_F)) -ne 0 ]; then |
| echo "Display of results not supported yet." |
| fi |
| |
| 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 |
| } |
| |
| 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. |
| |
| --tpm2 : Setup a TPM 2; by default a TPM 1.2 is setup. |
| |
| --createek : Create the EK |
| |
| --allow-signing : Create an EK that can also be used for signing; |
| 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 |
| |
| --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>'. |
| |
| --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>'. |
| |
| --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 |
| |
| --no-overwrite : Do not overwrite existing TPM state but silently end |
| |
| --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 |
| |
| while [ $# -ne 0 ]; do |
| case "$1" in |
| --tpm-state|--tpmstate) shift; tpm_state_path="$1";; |
| --tpm) shift; SWTPM="$1";; |
| --tpm2) flags=$((flags | SETUP_TPM2_F));; |
| --ecc) flags=$((flags | SETUP_TPM2_ECC_F));; |
| --createek) flags=$((flags | SETUP_CREATE_EK_F));; |
| --take-ownership) flags=$((flags | |
| SETUP_CREATE_EK_F|SETUP_TAKEOWN_F));; |
| --ownerpass) shift; ownerpass="$1";; |
| --owner-well-known) flags=$((flags | SETUP_OWNERPASS_ZEROS_F));; |
| --srkpass) shift; 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";; |
| --pwdfile) shift; pwdfile="$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));; |
| --version) versioninfo $0; exit 0;; |
| --help|-h|-?) usage $0; exit 0;; |
| *) logerr "Unknown option $1"; usage $0; exit 1;; |
| esac |
| shift |
| done |
| |
| [ "$ownerpass" == "" ] && ownerpass=$DEFAULT_OWNER_PASSWORD |
| [ "$srkpass" == "" ] && srkpass=$DEFAULT_SRK_PASSWORD |
| |
| 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 |
| 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 [ ! -x "$TCSD" ]; then |
| logerr "TSS at $TCSD is not an executable." |
| exit 1 |
| fi |
| |
| if [ ! -r "$config_file" ]; then |
| logerr "Cannot access config file ${config_file}." |
| exit 1 |
| fi |
| |
| if [ -n "$keyfile" ]; then |
| if [ ! -r "$keyfile" ]; then |
| logerr "Cannot access keyfile $keyfile." |
| exit 1 |
| fi |
| SWTPM="$SWTPM --key file=$keyfile" |
| 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" |
| logit " The TPM's state will be encrypted using a key derived from a passphrase." |
| 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" |
| else |
| SWTPM="$SWTPM --tpm2" |
| init_tpm2 $flags "$config_file" "$tpm_state_path" \ |
| "$ownerpass" "$srkpass" "$vmid" |
| 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 "$@" |