Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autounlock ZFS Encrypted Root Filesystems using TPM 2.0 #9852

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SUBDIRS = zfs zpool zdb zhack zinject zstream zstreamdump ztest
SUBDIRS += fsck_zfs vdev_id raidz_test zfs_ids_to_path
SUBDIRS += fsck_zfs vdev_id raidz_test zfs_ids_to_path tpm2_autounlock

if USING_PYTHON
SUBDIRS += arcstat arc_summary dbufstat
Expand Down
1 change: 1 addition & 0 deletions cmd/tpm2_autounlock/Makefile.am
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist_sbin_SCRIPTS = tpm2_autounlock.sh
176 changes: 176 additions & 0 deletions cmd/tpm2_autounlock/tpm2_autounlock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#! /bin/bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be plain /bin/sh? It looks like you have a bashism (or few) in here (I see a [[ ... ]]), but they could probably be fixed.


# Default variables
# If password is stored in "defaultpassword" field, ensure the security of the file.
defaultpassword=""
defaulttpmindex="0x1800016"
defaulttpmpcrs="sha256:0,1,2,3,8,9"

helpmsg() {
echo "tpm2_autounlock.sh -- configure the TPM 2.0 chip for zfs autounlock"
echo ""
echo "Usage: <-c, -v, or -n> -i <val>, -p <val>, -P <val>, -r <val>"
echo ""
echo "Modes: (choose up to one)"
echo "-c or --clearonly Clear TPM and ZFS properties only"
echo "-v or --verifyonly Check current contents of TPM"
echo "-n or --nvlockonly Lock the ability to read password until reboot"
echo "Not selecting any of the above will initialize the TPM for autounlock"
echo ""
echo "Options to override defaults:"
echo "-i or --index TPM NVRAM index (default: 0x1800016)"
echo "-p or --password Drive unlock password (default: <none provided>)"
echo "-P or --pcrs PCRS evaluated at unlock (default: sha256:0,1,2,3)"
echo "-r or --rootfs Rootfs being processed (default: current booted rootfs)"
echo "The password can also be piped to tpm2_autounlock.sh, though '-p' takes precedence"
}

tpm_session_execute() {
tpm2_startauthsession -Q ${define_policy} --session=s.dat
tpm2_policypassword -Q --session=s.dat
tpm2_policypcr -Q --pcr-list="$tpmpcrs" --session=s.dat --policy=policy.dat
eval "${cmd}"
returnval=$?
tpm2_flushcontext s.dat
rm s.dat
rm policy.dat
unset define_policy
unset cmd
return $returnval
}

# Check for root permissions
if [ $(id -u) -ne 0 ]; then
echo "This script must be run as root"
helpmsg
exit 1
fi

# Check for presence of needed programs
command -v zfs >/dev/null 2>&1 &&
[ $(zfs version | grep zfs-0 | awk -F 'zfs-0' '{print $2}' | awk -F '.' '{print $2}') -ge 8 ] \
|| { echo >&2 "zfs 0.8 or greater required, but not found. Aborting."; exit 1; }

for i in tpm2_startauthsession tpm2_policypassword tpm2_policypcr tpm2_flushcontext tpm2_nvdefine tpm2_nvwrite tpm2_nvread; do
command -v $i >/dev/null 2>&1 &&
[ $($i -v | awk -F 'version=' '{print $2}' | awk -F '"' '{print $2}' | awk -F '.' '{print $1}') -ge 4 ] \
|| { echo >&2 "'$i' version 4.0 or greater required, but not found. Aborting."; exit 1; }
done

# Check for presence of TPM 2.0 Hardware
if [ $(cat /sys/module/tpm/version) != "2.0" ]; then
echo "TPM 2.0 not found on system"
exit 1
fi

# Process user entered arguments
while [ "$1" != "" ]; do
case $1 in
-i | --index ) shift; tpmindex=$1;;
-P | --pcrs ) shift; tpmpcrs=$1;;
-r | --rootfs ) shift; rootfs=$1;;
-p | --password ) shift; password=$1;;
-v | --verifyonly ) verifyonly=1;;
-c | --clearonly ) clearonly=1;;
-n | --nvlockonly ) nvlockonly=1;;
-h | --help ) helpmsg; exit 0;;
* ) helpmsg; exit 1;;
esac
shift
done

# Use -r or --rootfs value if specified, otherwise try to detect running rootfs
if [ -z "$rootfs" ]; then
rootfs=$(zfs mount |awk '$2 == "/" { print $1 }')
if [ -z "$rootfs" ]; then
echo "Rootfs not specified (-r / --rootfs) and current rootfs could not be determined"
exit 1
fi
fi

# With rootfs determined, find the encryptionroot and its guid
ENCRYPTIONROOT=$(zfs get -H -o value encryptionroot "${rootfs}")
rootguid=$(zfs get guid -o value -H "${ENCRYPTIONROOT}")

# Use -i or --index value if specified, otherwise look for existing property, and finally fallback to script default
if [ -z "$tpmindex" ]; then
tpmindex=$(zfs get -H -o value org.zfsonlinux.tpm2:index "${ENCRYPTIONROOT}")
if [ "$tpmindex" = "-" ]; then
tpmindex="$defaulttpmindex"
fi
fi

# Use -P or --pcrs value if specified, otherwise look for existing property, and finally fallback to script default
if [ -z "$tpmpcrs" ]; then
tpmpcrs=$(zfs get -H -o value org.zfsonlinux.tpm2:pcrs "${ENCRYPTIONROOT}")
if [ "$tpmpcrs" = "-" ]; then
tpmpcrs="$defaulttpmpcrs"
fi
fi

# If password is needed, use argument, piped, "script default" or stdin, in that order
if [ -z "$password" ] && (( verifyonly+clearonly+nvlockonly == 0)); then
[[ -p /dev/stdin ]] && { mapfile -t; set -- "${MAPFILE[@]}"; set -- $@; }
pipepassword="$@"
if [ -n "$pipepassword" ]; then
password="$pipepassword"
elif [ -n "$defaultpassword" ]; then
password="$defaultpassword"
else
while true; do
read -s -p "Drive unlock password: " password
echo
read -s -p "Confirm Password: " password2
echo
[ "$password" = "$password2" ] && break
echo "Please try again"
done
fi
fi

# Check for invalid arguments
if (( verifyonly+clearonly+nvlockonly > 1)) ; then
echo " You can only select only one of the following: verifyonly, clearonly, or nvlockonly"
exit 1
fi

########################################
# Start execution
if [ $clearonly ]; then
tpm2_nvundefine "$tpmindex" >/dev/null 2>&1
zfs inherit org.zfsonlinux.tpm2:index "${ENCRYPTIONROOT}"
zfs inherit org.zfsonlinux.tpm2:pcrs "${ENCRYPTIONROOT}"

elif [ $verifyonly ]; then
echo "Attempting to read TPM index '$tpmindex' locked with pcrs '$tpmpcrs' and guid '$rootguid'."
define_policy="--policy"
cmd="tpm2_nvread ${tpmindex} --auth=session:s.dat+${rootguid}"
tpm_session_execute
if [ $? = 0 ]; then
echo "is the stored password."
# else
# The tpm2_nvread stdout messages will be visible to user
fi

elif [ $nvlockonly ]; then
define_policy="--policy"
cmd="tpm2_nvreadlock ${tpmindex} --auth=session:s.dat+${rootguid}"
tpm_session_execute
else
# Preemptively clear the tpm index
tpm2_nvundefine "$tpmindex" >/dev/null 2>&1

# Define NVRAM location and its access rules
cmd="tpm2_nvdefine ${tpmindex} -Q --hierarchy=o --index-auth=${rootguid} --size=512 --policy=policy.dat --attributes='policyread|policywrite|read_stclear'"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you setting --index-auth without attribute TPMA_NV_AUTHREAD? IIUC, that essentially is saying, hey this is another password but i never want to use it. I would imagine the rootguid is not a secret, and thus you really just want to drop this option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, seems redundant. "policyread and policywrite" are the only allowed access methods. The password requirement is built within the policy and is not freestanding.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well..local testing didn't like removing --index-auth=${rootguid}. It resulted in:

WARNING:esys:src/tss2-esys/api/Esys_NV_Write.c:306:Esys_NV_Write_Finish() Received TPM Error 
ERROR:esys:src/tss2-esys/api/Esys_NV_Write.c:110:Esys_NV_Write() Esys Finish ErrorCode (0x0000098e) 
ERROR: Failed to write NV area at index 0x1500016
ERROR: Tss2_Sys_NV_Write(0x98E) - tpm:session(1):the authorization HMAC check failed and DA counter incremented
ERROR: Unable to run tpm2_nvwrite

When running tpm2_autounlock -p password. Looks like it stays.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably because you still have policypassword present, which doesn't make a whole lot sense with a known value. For something like an FS encryption key, you would probably want to tie it to:

  1. PCR state
  2. User supplied secret (optional yet encouraged, depends on what your doing)
  3. Recovery from owner hierarchy auth if user forgets passphrase or PCR state changes

Typically folks avoid hard-coding PCR's due to brittleness and use policyauthorize which says, use any policy signed by key X. Not sure what prevents rollback to a bad policy or OS state on that method, may be worth investigating. There might not be a mechanism there.. so maybe owner auth and re-creating the nv index will work for upgrade path.

The question is, for automount, where can we get the user password. Not sure where you can source it, from PAM, login session, kernel keyring, etc. I really don't know much about that side of the house and don't want to give you bad advice there. One would want a secret that is tethered to the lifetime of a successful login or from the user typing it in directly.

Item 1 - Tie to a PCR value only:

tpm2_startauthsession -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9" --policy=policy.dat
tpm2_flushcontext session.ctx

tpm2_nvdefine -C o --size=512 --policy=policy.dat --attributes='policyread|policywrite|read_stclear' $NV_INDEX

tpm2_startauthsession --policy -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9" 

echo 'secret' | tpm2_nvwrite -P'session:session.ctx' -i- $NV_INDEX
tpm2_flushcontext session.ctx

# should work
tpm2_startauthsession --policy -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
tpm2_nvread -P'session:session.ctx' -s6 $NV_INDEX
tpm2_flushcontext session.ctx

Item 2 - tie to PCR and user password:

NV_INDEX=0x01800000

set -e -o pipefail

tpm2_startauthsession -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9" 
tpm2_policypassword -S session.ctx --policy=policy.dat
tpm2_flushcontext session.ctx

tpm2_nvdefine -C o -p 'userpass' --size=512 --policy=policy.dat --attributes='policyread|policywrite|read_stclear' $NV_INDEX

tpm2_startauthsession --policy -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9" 
tpm2_policypassword -S session.ctx 

echo 'secret' | tpm2_nvwrite -P'session:session.ctx+userpass' -i- $NV_INDEX
tpm2_flushcontext session.ctx

tpm2_startauthsession --policy -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
tpm2_policypassword -S session.ctx
tpm2_nvread -P'session:session.ctx+userpass' -s6 $NV_INDEX
tpm2_flushcontext session.ctx

Item 3, add in recovery option.

#!/usr/bin/env bash

NV_INDEX=0x01800000

set -e -o pipefail

# this may not work for you, my TPM currently has an empty owner password
tpm2_changeauth -c o "ownerpass"

tpm2_startauthsession -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
tpm2_policypassword -S session.ctx --policy=policy.dat
tpm2_flushcontext session.ctx

tpm2_nvdefine -C o -P 'ownerpass' -p 'userpass' --size=512 --policy=policy.dat --attributes='ownerread|policyread|policywrite|read_stclear' $NV_INDEX

tpm2_startauthsession --policy -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
tpm2_policypassword -S session.ctx

echo 'secret' | tpm2_nvwrite -P'session:session.ctx+userpass' -i- $NV_INDEX
tpm2_flushcontext session.ctx

# recovery with owner password
tpm2_nvread -C o -P'ownerpass' -s6 $NV_INDEX

# normal flow, user auth
tpm2_startauthsession --policy -S session.ctx
tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
tpm2_policypassword -S session.ctx
tpm2_nvread -P'session:session.ctx+userpass' -s6 $NV_INDEX
tpm2_flushcontext session.ctx

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in my testing I found that when when you couple auth to a permanent object like owner hiearchy DA protections stop applying unless you authorize to the NV index and specify a bad authvalue.

ie

  • tpm2_nvread -Co -P garbage $NV_INDEX will not trigger DA protections.
  • tpm2_nvread -P badsession.ctx+badpass $NV_INDEX will not trigger DA protections.
  • tpm2_nvread -P session.ctx+badpass $NV_INDEX will trigger DA implications and protections.

I started looking at using policysecret to couple to the owner hiearchy via a policy OR to see if that would work, but I was unable to get the script to run and ran out of time, sharing here so you can perhaps figure it out. The manpage and test code for tpm2_policyor sucks, so I will bug @idesai to look at this.

#!/usr/bin/env bash

NV_INDEX=0x01800000

set -e -o pipefail

# this may not work for you, my TPM currently has an empty owner password
tpm2_changeauth -c o "ownerpass"

do_session() {
  # PCR + user auth
  tpm2_startauthsession -S session.ctx
  tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
  tpm2_policypassword -S session.ctx --policy=policy1.dat
  tpm2_flushcontext session.ctx

  # Tether to owner hiearchy auth, but not via attr so DA works
  tpm2_startauthsession -S session.ctx
  tpm2_policysecret -S session.ctx -c o --policy=policy2.dat 'ownerpass'
  tpm2_flushcontext session.ctx

  # create the OR policy of them both
  tpm2_startauthsession -S session.ctx
  tpm2_policyor -S session.ctx -lpolicy1.dat,policy2.dat -L policy.dat
}

do_session_user() {
  # PCR + user auth
  tpm2_startauthsession -S session.ctx --policy
  tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
  tpm2_policypassword -S session.ctx --policy=policy1.dat
  tpm2_flushcontext session.ctx

  # create the OR policy of them both
  tpm2_startauthsession -S session.ctx --policy
  tpm2_policyor -S session.ctx -lpolicy1.dat,policy2.dat
}

do_session
tpm2_nvdefine -C o -P 'ownerpass' -p 'userpass' --size=512 --policy=policy.dat --attributes='policyread|policywrite|read_stclear' $NV_INDEX
tpm2_flushcontext session.ctx

do_session_user
echo 'secret' | tpm2_nvwrite -P'session.ctx:session.ctx+userpass' -i- $NV_INDEX
tpm2_flushcontext session.ctx

# recovery with owner password
do_session_owner
tpm2_nvread -C o -P'ownerpass' -s6 $NV_INDEX

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in my testing I found that when when you couple auth to a permanent object like owner hiearchy DA protections stop applying unless you authorize to the NV index and specify a bad authvalue.

ie

  • tpm2_nvread -Co -P garbage $NV_INDEX will not trigger DA protections.
  • tpm2_nvread -P badsession.ctx+badpass $NV_INDEX will not trigger DA protections.
  • tpm2_nvread -P session.ctx+badpass $NV_INDEX will trigger DA implications and protections.

I started looking at using policysecret to couple to the owner hiearchy via a policy OR to see if that would work, but I was unable to get the script to run and ran out of time, sharing here so you can perhaps figure it out. The manpage and test code for tpm2_policyor sucks, so I will bug @idesai to look at this.

#!/usr/bin/env bash

NV_INDEX=0x01800000

set -e -o pipefail

# this may not work for you, my TPM currently has an empty owner password
tpm2_changeauth -c o "ownerpass"

do_session() {
  # PCR + user auth
  tpm2_startauthsession -S session.ctx
  tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
  tpm2_policypassword -S session.ctx --policy=policy1.dat
  tpm2_flushcontext session.ctx

  # Tether to owner hiearchy auth, but not via attr so DA works
  tpm2_startauthsession -S session.ctx
  tpm2_policysecret -S session.ctx -c o --policy=policy2.dat 'ownerpass'
  tpm2_flushcontext session.ctx

  # create the OR policy of them both
  tpm2_startauthsession -S session.ctx
  tpm2_policyor -S session.ctx -lpolicy1.dat,policy2.dat -L policy.dat
}

do_session_user() {
  # PCR + user auth
  tpm2_startauthsession -S session.ctx --policy
  tpm2_policypcr -S session.ctx --pcr-list="sha256:0,1,2,3,8,9"
  tpm2_policypassword -S session.ctx --policy=policy1.dat
  tpm2_flushcontext session.ctx

  # create the OR policy of them both
  tpm2_startauthsession -S session.ctx --policy
  tpm2_policyor -S session.ctx -lpolicy1.dat,policy2.dat
}

do_session
tpm2_nvdefine -C o -P 'ownerpass' -p 'userpass' --size=512 --policy=policy.dat --attributes='policyread|policywrite|read_stclear' $NV_INDEX
tpm2_flushcontext session.ctx

do_session_user
echo 'secret' | tpm2_nvwrite -P'session.ctx:session.ctx+userpass' -i- $NV_INDEX
tpm2_flushcontext session.ctx

# recovery with owner password
do_session_owner
tpm2_nvread -C o -P'ownerpass' -s6 $NV_INDEX

IIUC the requirement is that,

  1. We need an authentication policy for a sealing object that if fails should trigger the DA mechanism
  2. The authentication policy should be one of two — 1. PCR state 2. Recovery Password
  3. PCR state should have rollback protection
  4. TPM2_RH_OWNER auth should be used to update new PCR states and or the recovery password.

Copy link

@idesai idesai Jan 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@williamcroberts @ghfields this is one way to do what you are trying to achieve with enhanced authorization

NV Index always read policy

tpm2_startauthsession -S session.ctx
tpm2_policycountertimer -S session.ctx -L some.policy safe
tpm2_flushcontext session.ctx
tpm2_nvdefine -C o -s 34 -a "ownerwrite|policyread|authread" -L some.policy
-p recoverypass

PolicySecret NV Index --> Recovery Password == NV Index Password

dd if=/dev/urandom bs=1 count=34 status=none | tpm2_nvwrite -C o -i- 0x01000000
tpm2_startauthsession -S session.ctx
tpm2_policysecret -S session.ctx -L policy.secret.nvindex
-c 0x01000000 recoverypass
tpm2_flushcontext session.ctx

PolicyPCR

tpm2_startauthsession -S session.ctx
tpm2_policypcr -S session.ctx -l "sha1:0,1,2,3" -L policy.pcr
tpm2_flushcontext session.ctx

Policy ( PCR || Recovery-Password)

tpm2_startauthsession -S session.ctx
tpm2_policyor -S session.ctx -l sha256:policy.pcr,policy.secret.nvindex
-L policy.or
tpm2_flushcontext session.ctx

Policy written out to NV index with owner auth.

New PCR states will replace this value.

echo "000b" | xxd -p -r | cat - policy.or | tpm2_nvwrite -C o -i- 0x01000000
tpm2_startauthsession -S session.ctx
tpm2_policyauthorizenv -S session.ctx -L policy.authnv -C 0x01000000 0x01000000
-P recoverypass
tpm2_flushcontext session.ctx

Sealing object

tpm2_createprimary -C o -c prim.ctx -Q
echo "idesaisecret"| tpm2_create -Q -C prim.ctx -u key.pub -r key.priv
-a "fixedtpm|fixedparent|adminwithpolicy" -L policy.authnv -i-
tpm2_load -C prim.ctx -u key.pub -r key.priv -c key.ctx

Unsealing with PolicyPCR - Failing this triggers DA mechanism

tpm2_startauthsession -S session_read.ctx --policy-session
tpm2_policycountertimer -S session_read.ctx safe
tpm2_startauthsession -S session.ctx --policy-session
tpm2_policypcr -S session.ctx -l "sha1:0,1,2,3"
tpm2_policyor -S session.ctx -l sha256:policy.pcr,policy.secret.nvindex
tpm2_policyauthorizenv -S session.ctx -C 0x01000000 0x01000000
-P session:session_read.ctx
tpm2_unseal -c key.ctx -p session:session.ctx | xxd -p
tpm2_flushcontext session.ctx

Unsealing with Recovery-Password - Failing this triggers DA mechanism

tpm2_startauthsession -S session_read.ctx --policy-session
tpm2_policycountertimer -S session_read.ctx safe
tpm2_startauthsession -S session.ctx --policy-session
tpm2_policysecret -S session.ctx -L policy.secret.nvindex
-c 0x01000000 recoverypass
tpm2_policyor -S session.ctx -l sha256:policy.pcr,policy.secret.nvindex
tpm2_policyauthorizenv -S session.ctx -C 0x01000000 0x01000000
-P session:session_read.ctx
tpm2_unseal -c key.ctx -p session:session.ctx | xxd -p
tpm2_flushcontext session.ctx

tpm_session_execute

# Write password to NVRAM location
define_policy="--policy"
cmd="echo $password | tpm2_nvwrite ${tpmindex} -Q --auth=session:s.dat+${rootguid} --input=-"
tpm_session_execute
echo "Note: Password storage can be verified with '--verifyonly' until reboot or locked manually with '--nvlockonly'."

# Store zfs filesystem properties
zfs set org.zfsonlinux.tpm2:index="$tpmindex" "${ENCRYPTIONROOT}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you store the index, you might want to search for an open NV slot and use that rather than requiring a hard coded default or user to specify.

tpm2_getcap handles-nv-index

Will give you the current in use handles...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true. Additionally, looking at the 4.1 changelog:
"tpm2_nvdefine: Support searching for free index if an index isn't specified."
If I bump the required version to 4.1+, I may be able to have tpm2_nvdefine do the work for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed tpm2-software/tpm2-tools#1891
I'd like to use tpm2_nvdefine to search for a usable index, but it offered an index not appropriate to the owner hierarchy as you showed in your spec document above.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true. Additionally, looking at the 4.1 changelog:
"tpm2_nvdefine: Support searching for free index if an index isn't specified."
If I bump the required version to 4.1+, I may be able to have tpm2_nvdefine do the work for me.

Yeah it just starts at index 0, perhaps we should smarten that up.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to use tpm2_nvdefine to search for a usable index, but it offered an index not appropriate to the owner hierarchy as you showed in your spec document above.

Maybe add an option --search= which makes the argument the starting index and searches until stop is reached.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is org.zfsonlinux. The PR description says org.openzfs. Existing practice for the latter is org.open-zfs.

zfs set org.zfsonlinux.tpm2:pcrs="$tpmpcrs" "${ENCRYPTIONROOT}"
fi
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ AC_CONFIG_FILES([
cmd/fsck_zfs/Makefile
cmd/mount_zfs/Makefile
cmd/raidz_test/Makefile
cmd/tpm2_autounlock/Makefile
cmd/vdev_id/Makefile
cmd/zdb/Makefile
cmd/zed/Makefile
Expand Down
11 changes: 11 additions & 0 deletions contrib/initramfs/hooks/zfs.in
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,15 @@ if [ ! -x "$DESTDIR/usr/bin/net" ]; then
chmod +x "$DESTDIR/usr/bin/net"
fi

#These are optional files to support tpm2-autounlock
if [ -x /usr/bin/tpm2_startauthsession ]; then
copy_exec /usr/bin/tpm2_nvread
copy_exec /usr/bin/tpm2_nvreadlock
copy_exec /usr/bin/tpm2_startauthsession
copy_exec /usr/bin/tpm2_policypassword
copy_exec /usr/bin/tpm2_policypcr
copy_modules_dir kernel/drivers/char/tpm
copy_exec /usr/lib/x86_64-linux-gnu/libtss2-tcti-device.so.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the tpm2 tools x86_64 only? (I could see how they might be, though they probably also work on x32 and maybe work in i386?) If not, then the architecture here should probably be detected.

fi

exit 0
20 changes: 20 additions & 0 deletions contrib/initramfs/scripts/zfs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,26 @@ decrypt_fs()
[ "$KEYSTATUS" = "unavailable" ] || return 0
TRY_COUNT=3

# Attempt to use TPM to auto-unlock
tpmindex="$(get_fs_value "$ENCRYPTIONROOT" org.zfsonlinux.tpm2:index)"
tpmpcrs="$(get_fs_value "$ENCRYPTIONROOT" org.zfsonlinux.tpm2:pcrs)"
if ! [ "$tpmindex" = "-" ] ; then
rootguid="$(get_fs_value "$ENCRYPTIONROOT" guid)"
tpm2_startauthsession --policy --session=s.dat
tpm2_policypassword -Q --session=s.dat
tpm2_policypcr -Q --pcr-list="$tpmpcrs" --session=s.dat
tpm2_nvread "$tpmindex" --auth=session:s.dat+"$rootguid" | tr -d '\0' \
| eval $ZFS load-key "${ENCRYPTIONROOT}"
if [ $? = 0 ]; then
#Lock the index from further reading until reboot
tpm2_startauthsession --policy --session=s.dat
tpm2_policypassword -Q --session=s.dat
tpm2_policypcr -Q --pcr-list="$tpmpcrs" --session=s.dat
tpm2_nvreadlock "$tpmindex" --auth=session:s.dat+"$rootguid" >> /dev/null
return 0
fi
fi

# If key is stored in a file, do not prompt
if ! [ "${KEYLOCATION}" = "prompt" ]; then
$ZFS load-key "${ENCRYPTIONROOT}"
Expand Down