Skip to content

Commit d72c06d

Browse files
committed
Auto-unlock ZFS native encrypted root filesystem using TPM2-tools v4.x
Signed-off-by: Garrett Fields <ghfields@gmail.com>
1 parent 417e646 commit d72c06d

File tree

6 files changed

+210
-1
lines changed

6 files changed

+210
-1
lines changed

cmd/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
SUBDIRS = zfs zpool zdb zhack zinject zstream zstreamdump ztest
2-
SUBDIRS += fsck_zfs vdev_id raidz_test zfs_ids_to_path
2+
SUBDIRS += fsck_zfs vdev_id raidz_test zfs_ids_to_path tpm2_autounlock
33

44
if USING_PYTHON
55
SUBDIRS += arcstat arc_summary dbufstat

cmd/tpm2_autounlock/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist_sbin_SCRIPTS = tpm2_autounlock.sh
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ AC_CONFIG_FILES([
7171
cmd/fsck_zfs/Makefile
7272
cmd/mount_zfs/Makefile
7373
cmd/raidz_test/Makefile
74+
cmd/tpm2_autounlock/Makefile
7475
cmd/vdev_id/Makefile
7576
cmd/zdb/Makefile
7677
cmd/zed/Makefile

contrib/initramfs/hooks/zfs.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,15 @@ if [ ! -x "$DESTDIR/usr/bin/net" ]; then
106106
chmod +x "$DESTDIR/usr/bin/net"
107107
fi
108108

109+
#These are optional files to support tpm2-autounlock
110+
if [ -x /usr/bin/tpm2_startauthsession ]; then
111+
copy_exec /usr/bin/tpm2_nvread
112+
copy_exec /usr/bin/tpm2_nvreadlock
113+
copy_exec /usr/bin/tpm2_startauthsession
114+
copy_exec /usr/bin/tpm2_policypassword
115+
copy_exec /usr/bin/tpm2_policypcr
116+
copy_modules_dir kernel/drivers/char/tpm
117+
copy_exec /usr/lib/x86_64-linux-gnu/libtss2-tcti-device.so.0
118+
fi
119+
109120
exit 0

contrib/initramfs/scripts/zfs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,26 @@ decrypt_fs()
414414
[ "$KEYSTATUS" = "unavailable" ] || return 0
415415
TRY_COUNT=3
416416

417+
# Attempt to use TPM to auto-unlock
418+
tpmindex="$(get_fs_value "$ENCRYPTIONROOT" org.zfsonlinux.tpm2:index)"
419+
tpmpcrs="$(get_fs_value "$ENCRYPTIONROOT" org.zfsonlinux.tpm2:pcrs)"
420+
if ! [ "$tpmindex" = "-" ] ; then
421+
rootguid="$(get_fs_value "$ENCRYPTIONROOT" guid)"
422+
tpm2_startauthsession --policy --session=s.dat
423+
tpm2_policypassword -Q --session=s.dat
424+
tpm2_policypcr -Q --pcr-list="$tpmpcrs" --session=s.dat
425+
tpm2_nvread "$tpmindex" --auth=session:s.dat+"$rootguid" | tr -d '\0' \
426+
| eval $ZFS load-key "${ENCRYPTIONROOT}"
427+
if [ $? = 0 ]; then
428+
#Lock the index from further reading until reboot
429+
tpm2_startauthsession --policy --session=s.dat
430+
tpm2_policypassword -Q --session=s.dat
431+
tpm2_policypcr -Q --pcr-list="$tpmpcrs" --session=s.dat
432+
tpm2_nvreadlock "$tpmindex" --auth=session:s.dat+"$rootguid" >> /dev/null
433+
return 0
434+
fi
435+
fi
436+
417437
# If key is stored in a file, do not prompt
418438
if ! [ "${KEYLOCATION}" = "prompt" ]; then
419439
$ZFS load-key "${ENCRYPTIONROOT}"

0 commit comments

Comments
 (0)