|  | 
|  | 1 | +#! /bin/bash | 
|  | 2 | + | 
|  | 3 | +# Default variables | 
|  | 4 | +	# If password is stored in "defaultpassword" field, ensure the security of the file. | 
|  | 5 | +defaultpassword=""  | 
|  | 6 | +defaulttpmindex="0x1800016" | 
|  | 7 | +defaulttpmpcrs="sha256:0,1,2,3,8,9" | 
|  | 8 | + | 
|  | 9 | +helpmsg() { | 
|  | 10 | +	echo "tpm2_autounlock.sh -- configure the TPM 2.0 chip for zfs autounlock" | 
|  | 11 | +	echo "" | 
|  | 12 | +	echo "Usage: <-c, -v, or -n>  -i <val>, -p <val>, -P <val>, -r <val>" | 
|  | 13 | +	echo "" | 
|  | 14 | +	echo "Modes: (choose up to one)" | 
|  | 15 | +	echo "-c or --clearonly	Clear TPM and ZFS properties only" | 
|  | 16 | +	echo "-v or --verifyonly	Check current contents of TPM" | 
|  | 17 | +	echo "-n or --nvlockonly	Lock the ability to read password until reboot" | 
|  | 18 | +	echo "Not selecting any of the above will initialize the TPM for autounlock" | 
|  | 19 | +	echo "" | 
|  | 20 | +	echo "Options to override defaults:" | 
|  | 21 | +	echo "-i or --index		TPM NVRAM index (default: 0x1800016)" | 
|  | 22 | +	echo "-p or --password		Drive unlock password (default: <none provided>)" | 
|  | 23 | +	echo "-P or --pcrs		PCRS evaluated at unlock (default: sha256:0,1,2,3)" | 
|  | 24 | +	echo "-r or --rootfs		Rootfs being processed (default: current booted rootfs)" | 
|  | 25 | +	echo "The password can also be piped to tpm2_autounlock.sh, though '-p' takes precedence" | 
|  | 26 | +} | 
|  | 27 | + | 
|  | 28 | +tpm_session_execute() { | 
|  | 29 | +	tpm2_startauthsession -Q ${define_policy} --session=s.dat | 
|  | 30 | +	tpm2_policypassword -Q --session=s.dat | 
|  | 31 | +	tpm2_policypcr -Q --pcr-list="$tpmpcrs" --session=s.dat --policy=policy.dat | 
|  | 32 | +	eval "${cmd}" | 
|  | 33 | +	returnval=$? | 
|  | 34 | +	tpm2_flushcontext s.dat | 
|  | 35 | +	rm s.dat | 
|  | 36 | +	rm policy.dat | 
|  | 37 | +	unset define_policy | 
|  | 38 | +	unset cmd | 
|  | 39 | +	return $returnval | 
|  | 40 | +} | 
|  | 41 | + | 
|  | 42 | +# Check for root permissions | 
|  | 43 | +if [ $(id -u) -ne 0 ]; then | 
|  | 44 | +	echo "This script must be run as root" | 
|  | 45 | +	helpmsg | 
|  | 46 | +	exit 1 | 
|  | 47 | +fi | 
|  | 48 | + | 
|  | 49 | +# Check for presence of needed programs | 
|  | 50 | +command -v zfs >/dev/null 2>&1 && | 
|  | 51 | +	[ $(zfs version | grep zfs-0 | awk -F 'zfs-0' '{print $2}' | awk -F '.' '{print $2}') -ge 8 ] \ | 
|  | 52 | +		|| { echo >&2 "zfs 0.8 or greater required, but not found.  Aborting."; exit 1; } | 
|  | 53 | + | 
|  | 54 | +for i in tpm2_startauthsession tpm2_policypassword tpm2_policypcr tpm2_flushcontext tpm2_nvdefine tpm2_nvwrite tpm2_nvread; do | 
|  | 55 | +	command -v $i >/dev/null 2>&1 && | 
|  | 56 | +	[ $($i -v | awk -F 'version=' '{print $2}' | awk -F '"' '{print $2}' | awk -F '.' '{print $1}') -ge 4 ] \ | 
|  | 57 | +		|| { echo >&2 "'$i' version 4.0 or greater required, but not found.  Aborting."; exit 1; } | 
|  | 58 | +done | 
|  | 59 | + | 
|  | 60 | +# Check for presence of TPM 2.0 Hardware | 
|  | 61 | +if [ $(cat /sys/module/tpm/version) != "2.0" ]; then | 
|  | 62 | +	echo "TPM 2.0 not found on system" | 
|  | 63 | +	exit 1 | 
|  | 64 | +fi | 
|  | 65 | + | 
|  | 66 | +# Process user entered arguments | 
|  | 67 | +while [ "$1" != "" ]; do | 
|  | 68 | +	case $1 in | 
|  | 69 | +		-i | --index ) shift; tpmindex=$1;; | 
|  | 70 | +		-P | --pcrs ) shift; tpmpcrs=$1;; | 
|  | 71 | +		-r | --rootfs ) shift; rootfs=$1;; | 
|  | 72 | +		-p | --password ) shift; password=$1;; | 
|  | 73 | +		-v | --verifyonly ) verifyonly=1;; | 
|  | 74 | +		-c | --clearonly ) clearonly=1;; | 
|  | 75 | +		-n | --nvlockonly ) nvlockonly=1;; | 
|  | 76 | +		-h | --help ) helpmsg; exit 0;; | 
|  | 77 | +		* ) helpmsg; exit 1;; | 
|  | 78 | +	esac | 
|  | 79 | +	shift | 
|  | 80 | +done | 
|  | 81 | + | 
|  | 82 | +# Use -r or --rootfs value if specified, otherwise try to detect running rootfs | 
|  | 83 | +if [ -z "$rootfs" ]; then | 
|  | 84 | +	rootfs=$(zfs mount |awk '$2 ==  "/" { print $1 }') | 
|  | 85 | +	if [ -z "$rootfs" ]; then | 
|  | 86 | +		echo "Rootfs not specified (-r / --rootfs) and current rootfs could not be determined" | 
|  | 87 | +		exit 1 | 
|  | 88 | +	fi | 
|  | 89 | +fi | 
|  | 90 | + | 
|  | 91 | +# With rootfs determined, find the encryptionroot and its guid | 
|  | 92 | +ENCRYPTIONROOT=$(zfs get -H -o value encryptionroot "${rootfs}") | 
|  | 93 | +rootguid=$(zfs get guid -o value -H "${ENCRYPTIONROOT}") | 
|  | 94 | + | 
|  | 95 | +# Use -i or --index value if specified, otherwise look for existing property, and finally fallback to script default | 
|  | 96 | +if [ -z "$tpmindex" ]; then | 
|  | 97 | +	tpmindex=$(zfs get -H -o value org.zfsonlinux.tpm2:index "${ENCRYPTIONROOT}") | 
|  | 98 | +	if [ "$tpmindex" = "-" ]; then | 
|  | 99 | +		tpmindex="$defaulttpmindex" | 
|  | 100 | +	fi | 
|  | 101 | +fi | 
|  | 102 | + | 
|  | 103 | +# Use -P or --pcrs value if specified, otherwise look for existing property, and finally fallback to script default | 
|  | 104 | +if [ -z "$tpmpcrs" ]; then | 
|  | 105 | +	tpmpcrs=$(zfs get -H -o value org.zfsonlinux.tpm2:pcrs "${ENCRYPTIONROOT}") | 
|  | 106 | +	if [ "$tpmpcrs" = "-" ]; then | 
|  | 107 | +		tpmpcrs="$defaulttpmpcrs" | 
|  | 108 | +	fi | 
|  | 109 | +fi | 
|  | 110 | + | 
|  | 111 | +# If password is needed, use argument, piped, "script default" or stdin, in that order | 
|  | 112 | +if [ -z "$password" ] && (( verifyonly+clearonly+nvlockonly == 0)); then | 
|  | 113 | +	[[ -p /dev/stdin ]] && { mapfile -t; set -- "${MAPFILE[@]}"; set -- $@; } | 
|  | 114 | +	pipepassword="$@" | 
|  | 115 | +	if [ -n "$pipepassword" ]; then | 
|  | 116 | +		password="$pipepassword" | 
|  | 117 | +	elif [ -n "$defaultpassword" ]; then | 
|  | 118 | +		password="$defaultpassword" | 
|  | 119 | +	else | 
|  | 120 | +		while true; do | 
|  | 121 | +			read -s -p "Drive unlock password: " password | 
|  | 122 | +			echo | 
|  | 123 | +   			read -s -p "Confirm Password: " password2 | 
|  | 124 | +			echo | 
|  | 125 | +  			[ "$password" = "$password2" ] && break | 
|  | 126 | +    			echo "Please try again" | 
|  | 127 | +		done | 
|  | 128 | +	fi	 | 
|  | 129 | +fi | 
|  | 130 | + | 
|  | 131 | +# Check for invalid arguments | 
|  | 132 | +if (( verifyonly+clearonly+nvlockonly > 1)) ; then | 
|  | 133 | +	echo " You can only select only one of the following: verifyonly, clearonly, or nvlockonly" | 
|  | 134 | +	exit 1 | 
|  | 135 | +fi | 
|  | 136 | + | 
|  | 137 | +######################################## | 
|  | 138 | +# Start execution | 
|  | 139 | +if [ $clearonly ]; then | 
|  | 140 | +	tpm2_nvundefine "$tpmindex" >/dev/null 2>&1 | 
|  | 141 | +	zfs inherit org.zfsonlinux.tpm2:index "${ENCRYPTIONROOT}" | 
|  | 142 | +	zfs inherit org.zfsonlinux.tpm2:pcrs "${ENCRYPTIONROOT}" | 
|  | 143 | + | 
|  | 144 | +elif [ $verifyonly ]; then | 
|  | 145 | +	echo "Attempting to read TPM index '$tpmindex' locked with pcrs '$tpmpcrs' and guid '$rootguid'." | 
|  | 146 | +	define_policy="--policy" | 
|  | 147 | +	cmd="tpm2_nvread ${tpmindex} --auth=session:s.dat+${rootguid}" | 
|  | 148 | +	tpm_session_execute | 
|  | 149 | +	if [ $? = 0 ]; then | 
|  | 150 | +		echo "is the stored password." | 
|  | 151 | +#	else | 
|  | 152 | +		# The tpm2_nvread stdout messages will be visible to user | 
|  | 153 | +	fi | 
|  | 154 | + | 
|  | 155 | +elif [ $nvlockonly ]; then | 
|  | 156 | +	define_policy="--policy" | 
|  | 157 | +	cmd="tpm2_nvreadlock  ${tpmindex} --auth=session:s.dat+${rootguid}" | 
|  | 158 | +	tpm_session_execute | 
|  | 159 | +else | 
|  | 160 | +	# Preemptively clear the tpm index | 
|  | 161 | +	tpm2_nvundefine "$tpmindex" >/dev/null 2>&1 | 
|  | 162 | + | 
|  | 163 | +	# Define NVRAM location and its access rules | 
|  | 164 | +	cmd="tpm2_nvdefine ${tpmindex} -Q --hierarchy=o --index-auth=${rootguid} --size=512 --policy=policy.dat --attributes='policyread|policywrite|read_stclear'" | 
|  | 165 | +	tpm_session_execute | 
|  | 166 | + | 
|  | 167 | +	# Write password to NVRAM location | 
|  | 168 | +	define_policy="--policy" | 
|  | 169 | +	cmd="echo $password | tpm2_nvwrite ${tpmindex} -Q --auth=session:s.dat+${rootguid} --input=-" | 
|  | 170 | +	tpm_session_execute | 
|  | 171 | +	echo "Note: Password storage can be verified with '--verifyonly' until reboot or locked manually with '--nvlockonly'." | 
|  | 172 | + | 
|  | 173 | +	# Store zfs filesystem properties | 
|  | 174 | +	zfs set org.zfsonlinux.tpm2:index="$tpmindex" "${ENCRYPTIONROOT}" | 
|  | 175 | +	zfs set org.zfsonlinux.tpm2:pcrs="$tpmpcrs" "${ENCRYPTIONROOT}" | 
|  | 176 | +fi | 
0 commit comments