-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #9852 +/- ##
==========================================
- Coverage 79.81% 79.39% -0.43%
==========================================
Files 395 385 -10
Lines 125087 121481 -3606
==========================================
- Hits 99836 96444 -3392
+ Misses 25251 25037 -214
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
# Default variables | ||
# If password is stored in "defaultpassword" field, ensure the security of the file. | ||
defaultpassword="" | ||
defaulttpmindex="0x1500016" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to:
https://trustedcomputinggroup.org/wp-content/uploads/RegistryOfReservedTPM2HandlesAndLocalities_v1p1_pub.pdf
The handle 0x1500016 is unassigned by TCG but reserved for the Platform manufacturer. You should probably pick something in the unassigned by TCG for Owner range of:
Start: 0x0180000016
End: 0x01BFFFFF16
1800000
1BFFFFF
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Will stay away from that range.
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'" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- PCR state
- User supplied secret (optional yet encouraged, depends on what your doing)
- 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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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,
- We need an authentication policy for a sealing object that if fails should trigger the DA mechanism
- The authentication policy should be one of two — 1. PCR state 2. Recovery Password
- PCR state should have rollback protection
- TPM2_RH_OWNER auth should be used to update new PCR states and or the recovery password.
There was a problem hiding this comment.
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
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}" |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
I want to thank @williamcroberts for taking the time to look at this. He is a regular voice on the tpm2 mailing list ([email protected]) and has been a great resource to me as I explored tpm2-tools. |
The existing precedent is for org.open-zfs (with a dash) for property names. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few small comments. I wish I could be of more help on the TPM stuff. Awesome work!
@@ -0,0 +1,176 @@ | |||
#! /bin/bash |
There was a problem hiding this comment.
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.
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}" |
There was a problem hiding this comment.
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
.
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 |
There was a problem hiding this comment.
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.
Half of this code should probably be rewritten as a Clevis PIN or simply discarded, since Clevis already ships with (reviewed) TPM 2.0 support. The other half should be added to Clevis so that Clevis learns how to unlock ZFS datasets. If added to Clevis / upstreamed as a PIN, it will work across all distributions. https://github.com/latchset/clevis EDIT: someone already started to use Clevis to do exactly this: https://www.reddit.com/r/zfs/comments/dimtjv/guide_setting_up_tpm2based_decryption_for_zfs_on/ |
Signed-off-by: Garrett Fields <[email protected]>
8d96d84
to
d72c06d
Compare
To follow up on using Clevis for TPM support to unlock encryptionroots, there are now also this issue and this PR available at upstream: |
This is being done by @techhazard already, not sure why the repetition of efforts, but he has a working pull request AFAIK (for Clevis). |
Motivation and Context
Presently, native encryption of a ZFS root filesystem requires a passphrase or keyfile to be available every boot. A TPM 2.0 chip has the ability to store and release data, if certain criteria are met. This PR provides an optional method, which allows TPM 2.0 to be used to automatically unlock the filesystem, without user intervention.
This is a DRAFT PR to foster discussion surrounding the method. THIS MAY NOT CONFORM TO STYLE GUIDES OR EVEN GOOD CODING PRACTICES. Code nit comments are not unwelcome, but methodology is the initial goal.
Description
This PR does the following:
org.openzfs.tpm2:index
andorg.openzfs.tpm2:pcrs
Quick (and probably correctish) PCR usage:
How Has This Been Tested?
Tpm-tools 4.x revamped its arguments. Grub 2.04 is secure boot compatible and now reports additional boot time measurements that could harden the boot protections. Therefore, this has been developed and tested solely on Ubuntu Focal pre-release (to be 20.04 LTS) on Dell TPM 2.0 hardware using UEFI boot. Testing different distros, kernels, and hardware TPM devices need to be conducted.
Types of changes
Checklist:
Signed-off-by
.Things to Explore / Current Status (will be edited over time)
panic=5
monitoring/injection to prevent snooping from the initrd promptResources
Tpm2-tools man pages
Grub manual (specifically Measured Boot Section)
UEFI PCR explanation on Pg. 2
Another PCR description on 4th page