Compare commits

..

118 Commits

Author SHA1 Message Date
наб
fd16dbbcd3
Force signed char since we have =-1 (fixes FTBFS on char=uchar) 2025-03-09 18:56:42 +01:00
наб
82b5118fcb
dracut: replace removed-from-dracut-ng inst_library with inst_libdir_file in TPM1
Equivalent calling convention, produces identical initrd

Fixes: https://todo.sr.ht/~nabijaczleweli/tzpfms/3
2025-03-09 04:15:15 +01:00
наб
2335a22bc7
dracut: use standard require_binaries error message in check() 2025-03-09 03:51:48 +01:00
наб
588cce3882
dracut: install TPM2 TCTIs with inst_libdir_file instead of removed-from-dracut-ng inst_library
We can't pull in 91tpm2-tss if installing TPM2 backend
instead of installing the TCTIs manually because it's
  require_binaries tpm2

Initrds produced are identical.

References: https://todo.sr.ht/~nabijaczleweli/tzpfms/3
2025-03-09 03:46:46 +01:00
наб
2764c6e0d3
format 2025-02-16 16:55:39 +01:00
наб
44f9b25314
srhtcdn.githack.com -> ra.ws.co.ls
Fixes: https://todo.sr.ht/~nabijaczleweli/tzpfms/2
2025-02-13 00:00:44 +01:00
наб
d6a2d4683d
mlockall() before calling the TPM 2024-05-31 17:58:42 +02:00
наб
dcf545a6f6
Don't destroy fzifdso manuals on CI 2024-03-29 13:31:38 +01:00
наб
48bebe9be3
tzpfms backend TPM[12] -> tzpfms TPM[12] backend 2024-03-11 18:24:55 +01:00
наб
81add25df9
tools -> programs for programs 2024-03-11 13:10:20 +01:00
наб
56ada0e31c
Deduplicate <unistd.h> 2024-03-10 03:33:09 +01:00
наб
f3bb25ee57
getrandom() -> getentropy() 2024-03-10 03:32:17 +01:00
наб
887f9b6386
Now with_stdin_at() doesn't need the scope wrapper 2024-03-10 03:28:51 +01:00
наб
1d39364a02
Clang complains about ZFS headers 2024-03-05 22:48:30 +01:00
наб
7e4ea2c2da
clang-format 2024-03-05 19:25:25 +01:00
наб
3d082a3efd
No need to free lines before exit 2024-03-05 19:21:00 +01:00
наб
fc57e89e37
argv[0] in error prefix 2024-03-05 19:20:38 +01:00
наб
a77e6c1d80
reallocarray when reallocarraying 2024-03-05 19:19:40 +01:00
наб
52a4871ce0
Clean up zfs-tpm1x-clear-key (parse_key_props() is already in main_clear.hpp) 2024-03-04 12:33:41 +01:00
наб
984c5d5354
tzpfms [version] TZPFMS_VERSION 2024-03-03 20:43:44 +01:00
наб
7c0393e894
Clean up with_stdin_at error handling 2024-03-03 18:59:57 +01:00
наб
2118cc3679
Flatten shellcheck invocation 2024-03-03 16:57:46 +01:00
наб
83664adbc8
Decargocultise i-t prereqs 2024-03-03 14:30:04 +01:00
наб
a20c275ceb
use :? to early-exit instead of :- and hope for a good error 2024-03-03 14:08:37 +01:00
наб
1f9fe814fe
Use map for nsswitch.conf uniquification. Don't try to find [NOTFOUND or whatever stanzas as libraries 2024-03-03 14:05:42 +01:00
наб
5df51d163e
^(group|hosts) 2024-03-03 10:43:38 +01:00
наб
e271fc1ea2
Finally put the WRAPPING_KEY_LEN inlining into zfs.hpp 2024-03-02 22:14:44 +01:00
наб
31f4ef215c
Document /usr/share/tzpfms/. Ported to zfs 2.2 2024-03-02 21:14:30 +01:00
наб
f5996ad11a
:v 2024-03-01 13:12:22 +01:00
наб
f81d510053
Align dracut (and i-t but no changes there) with modern ZFS 2024-03-01 12:08:32 +01:00
наб
2fc0bffe7d
crosslink to fzifdso 2024-02-29 02:55:01 +01:00
наб
a4294c83c5
] ; 2024-02-29 02:05:58 +01:00
наб
6a143b6e8b
am i dying 2024-02-29 01:42:14 +01:00
наб
b31a131eb3
It's /usr/share/tpfms obv 2024-02-29 01:30:32 +01:00
наб
d3d43f281b
needless awk 2024-02-29 01:30:32 +01:00
наб
f0dfcc2110
Support modern nonroot necessities in dracut. quiet is actually used in mount.h
Fixes: fcd89b3a93c7f94ffdefa6e6cfb19eecb517c20b
2024-02-29 01:30:32 +01:00
наб
a0246cb48e
Compat with zfs 2.2+'s nvlist_lookup_string() taking const char ** 2024-02-28 19:30:59 +01:00
наб
fcd89b3a93
Useless quiet=y 2024-02-28 18:57:00 +01:00
наб
ba81298aaa
Read /usr/lib/tzpfms/$backend from /libexec/tzpfms-zfs-load-key@ to support fzifdso-style plugins 2024-02-28 18:34:59 +01:00
наб
69a1bcc2ce
Printing -> writing when used for writing 2024-02-28 18:14:19 +01:00
наб
bf78dcbc80
ar --thin for internal library 2023-12-20 16:39:06 +01:00
наб
f322a48302
PKG_CONFIG is the canonical spelling acc'g to http://crossqa.debian.net/build/urlview_1b-1_arm64_20231205033603.log 2023-12-12 16:37:50 +01:00
наб
cfd02ff6f3
Needless constexpr broke build but only on current sid GCC? 2023-12-06 03:22:03 +01:00
наб
8997b47acf
Common objects need to be in an archive to get correctly -flto/--as-neededed
Partial revert of e78ba80bc90fdb78f4ce47d12519280fe2e5981c
2023-12-06 03:10:34 +01:00
наб
a85bcbeca2
Remove licence badge from README (the git.sr.ht webview generates this in top-right now, so no need to update it to licences-MIT,%200BSD or whatever) 2023-11-29 20:11:18 +01:00
наб
d0cd0c93f9
Right "extension" 2023-11-27 01:03:28 +01:00
наб
7329bfb5f6
Also build contrib on CI 2023-11-26 01:55:49 +01:00
наб
12d4b36c69
When autogenerating TZPFMS_DATE, do it per file 2023-11-26 01:47:57 +01:00
наб
f4b3cd202a
out/locale for sensibler install. upload like from ratrun 2023-11-26 01:34:06 +01:00
наб
b3764d8626
No point to libzfs_fini() as the last thing in main, either 2023-11-25 23:31:13 +01:00
наб
b8b856870c
Commit to signing tarballs 2023-11-25 22:07:26 +01:00
наб
1f0d78963e
Handle SOURCE_DATE_EPOCH (https://reproducible-builds.org/docs/source-date-epoch/) 2023-11-25 21:35:35 +01:00
наб
31dbb443af
Do build translations on CI 2023-11-25 18:36:26 +01:00
наб
6b3cc6971d
Don't free() allocated statics 2023-11-25 18:11:01 +01:00
наб
286180b138
https://git.sr.ht/~nabijaczleweli/groff-1.23-unfucking 2023-11-25 17:45:29 +01:00
наб
c651450b5d
Axe unused fd.cpp:read_exact() 2023-11-25 17:44:40 +01:00
наб
1ae0e258a7
Correctly echo intr instead of hard ^C 2023-11-25 17:44:39 +01:00
наб
47cd20aa32
Translate to Polish 2023-11-25 17:40:59 +01:00
наб
e78ba80bc9
-MD. bookworm manpages.d.o link 2023-11-25 15:34:38 +01:00
наб
8e02233ad5
Simplify Makefile, make it correcter and more canonical 2023-11-25 15:13:16 +01:00
наб
c727114dce
REUSEify licence layout. SPDXify final two in-line licence headers 2023-11-25 14:52:57 +01:00
наб
aa1c21fc27
Twitter link pivot 2023-07-16 19:02:10 +02:00
наб
1345229369
New OAuth release token 2023-06-12 19:48:14 +02:00
наб
d51d8a3c86
bookworm tcsd wants /etc/tcsd.conf root:tss 2023-06-12 19:30:50 +02:00
наб
39c3201f63
Fix individual links to zfs-tpm{2,1x}-change-key in README
Reported-in: https://old.reddit.com/r/zfs/comments/ppo9rl/zfs_encryption_and_tpm/j42mvyx/?context=3
2023-01-12 20:35:06 +01:00
наб
6a8d97e43a
licen{s => c}e for noun, natch 2022-12-12 21:06:31 +01:00
наб
f403ffebad
Size mdoc(7) lists correctly 2022-12-04 02:51:05 +01:00
наб
d2dcf95b0f
Just use pipe to read passphrase from helper instead of memfd weirdness 2022-12-04 02:26:10 +01:00
наб
720a0103a7
Fix ^Ding out of the new passphrase prompt 2022-12-04 01:37:56 +01:00
наб
b5ff262534
Sell-out moment 2022-12-04 00:58:08 +01:00
наб
0f2642ba92
Provide blank Esys_Create{Primary,}() metadata
This was yielding
  Couldn't create primary encryption key: tpm:parameter(3):structure is the wrong size
errors on ASRock X670E Pro RS + AMD Ryzen 5 7600X: just kill it;
it's unclear if it's remotely useful besides adding some needless salt

Reported and validated by Lars Strojny:
  https://twitter.com/lstrojny/status/1599182208752766976
2022-12-04 00:57:36 +01:00
наб
d950de0ae4
Actually copy in the CreatePrimary parameters to the right slots
This mimicks tpm2-tools; I don't think it matters any, since I'm pretty
sure these are just unused for our application, but
2022-12-04 00:32:44 +01:00
наб
b811862e0a
swtpm is in Debian now :) 2022-12-03 22:15:22 +01:00
наб
02195933bc
Use SHA256 as the default name algorithm for the TPM2 primary key
This mirrors an analogous change in tpm2-tools
  5900ed818f
and brings us back in line with the internally-documented equivalent
line-up; quoth the above:
> Using SHA1 as the default was a poor choice,
> as the whole point of TPM2.0 was SHA256+ algorithm support.

And the change to the code we butchered from tpm2-tss:
  3c1e7173b3
> Due to the fact that sha1 is outdated and the integration tests are
> often used as an example for developers, the integration tests that
> still use sha1 have been converted to sha256.
2022-12-03 21:32:50 +01:00
наб
023ffc98ae
0..23 -> [0, 23] 2022-12-03 21:19:40 +01:00
наб
af6835d586
Recommend new Debian repo signing 2022-11-04 00:01:00 +01:00
наб
005ee41545
sort | uniq -> LC_ALL=C sort -u 2022-10-29 22:52:43 +02:00
наб
29c58cfe9b
awk -> cut 2022-10-29 22:50:57 +02:00
наб
62e896642b
Silence SHA256_*() OpenSSL API deprecation warnings for now 2022-09-30 21:07:12 +02:00
наб
aef018ba1e
Fix builds with less header-leaky libstdc++es 2022-09-30 20:08:27 +02:00
наб
d26a07511f
Correct tilda in manuals 2022-09-30 20:00:07 +02:00
наб
fa91ff6c67
-P-pa4 2022-06-17 23:16:11 +02:00
наб
3651105e0f
Accept TZPFMS_{VERSION,DATE} from environment in Makefile 2021-12-20 20:23:14 +01:00
наб
b45a331747
Add out/systemd to release bundle. Spin auth token 2021-12-20 20:21:46 +01:00
наб
af6928d525
Add zfs-mount-generator integration
Funny moment: https://twitter.com/nabijaczleweli/status/1472986504272261124
2021-12-20 19:28:13 +01:00
наб
ce3560b797
Fix -[yu] typo in z-t-list.8 OPTIONS 2021-12-20 17:34:39 +01:00
наб
00703a1517
Upcast time_t to i64 2021-11-29 16:20:40 +01:00
наб
32da9f7c81
Fix TZPFMS_VERSION injexion into manuals 2021-11-28 20:02:22 +01:00
наб
1937610e54
struct timespec::tv_nsec is the syscall long, not actual long! 2021-11-28 18:17:00 +01:00
наб
5d7ebf3d19
Fix -flto[=full] -Wl,--as-needed not working on sid anymore(?) 2021-11-28 18:06:37 +01:00
наб
503ac72235
Proof zfs-tpm1x-change-key.8 2021-11-28 17:13:15 +01:00
наб
e0b0de31b9
Don't force POSIXLY_CORRECT. Reject extraneous argument 2021-11-28 01:40:23 +01:00
наб
49f0a05c33
Add PCR binding. password => passphrase in manuals
All logically distinct modes are now:
  TPM1.X: passphraseless, PCRs, passphrase, passphrase & PCRs
  TPM2:   passphraseless, PCRs, passphrase, passphrase | PCRs

TPM2 sees a backward-incompatible ";pcr list" addition to its handle

Cf. https://twitter.com/nabijaczleweli/status/1463707170793562117
2021-11-28 01:33:58 +01:00
наб
de9b591546
Include tpm2_dictionarylockout like tpm_resetdalock 2021-11-27 22:46:40 +01:00
наб
c9cd46ab95
Build with -g by default. Don't strip 2021-11-25 22:49:47 +01:00
наб
303ea58c2f
Import parse_uint() from voreutils. Summarise all .sos. Fix systemd integration in README to match. Flatten hV adding 2021-11-25 16:33:26 +01:00
наб
09748712fb
Note libzfs 2.1 works 2021-11-24 18:55:55 +01:00
наб
2757a01c92
Remove SEE ALSOs that were just the git.sr.ht link 2021-11-24 13:37:04 +01:00
наб
a4cc3cd7e0
Use actual dataset names instead of owo/uwu/awa/&c. in zfs-tpm-list.8 2021-11-24 13:35:09 +01:00
наб
98bd432a50
Final-proof passphrase.h 2021-11-24 13:23:22 +01:00
наб
4e41ae1f14
Add TZPFMS_PASSPHRASE_HELPER{,_MAN} make tunables 2021-11-20 13:12:01 +01:00
наб
f02bf1c094
Proof password.h round 2 2021-11-20 12:13:11 +01:00
наб
ea0a5bd52e
Proof passphrase.h. Fix helper for empty output, fortify against ENOMEM and mmap(2) error 2021-11-19 00:37:08 +01:00
наб
8df40d5506
Fix lib[std]c++ dependency by initialising helper path explicitly 2021-11-15 19:12:19 +01:00
наб
12189bc0d5
Change TPM2 creation metadata to 'UNIX.ns dataset version' from 'dataset RFC3339 version' ‒ there's only 64 bytes to play with 2021-11-15 19:04:40 +01:00
наб
9c8ee8d68c
Add $TZPFMS_PASSPHRASE_HELPER. Always include the TPM back-end and/or dataset name in password what-fors 2021-11-15 18:59:07 +01:00
наб
74ec450e16
Preprocess with pp.awk, fixing space columnation 2021-11-12 19:41:38 +01:00
наб
a0ca4c3a97
apt-get on CI 2021-11-10 18:40:30 +01:00
наб
d8d29927c7
Proof manuals 2021-11-10 18:34:47 +01:00
наб
c1ab9a5a1a
Fold print; return err; into return print, err; 2021-11-10 15:02:43 +01:00
наб
dc8bc7acb7
Use proper PRI* macros for foreign types 2021-11-10 14:48:07 +01:00
наб
d5107f9415
Clean up shell in tandem with https://github.com/openzfs/zfs/pull/12652 2021-10-21 23:57:47 +02:00
наб
01fa1b6031
No authors -> no gsfonts. Also what's a state 2021-10-15 23:37:30 +02:00
наб
9817d9d8ae
Remove authors list 2021-10-15 23:33:45 +02:00
наб
6ae9df1c95
Rewrite manual in mdoc 2021-10-15 23:16:04 +02:00
наб
5b0685ec94
src:zfs-linux 2.0.0-1~exp1 works 2021-01-06 15:40:54 +01:00
наб
3174a66e9e
Note the Debian package 2020-12-08 19:40:19 +01:00
наб
1a5f1f9498
Note initrd regens for TPM1.X 2020-12-08 18:14:35 +01:00
63 changed files with 2623 additions and 976 deletions

View File

@ -1,35 +1,44 @@
image: debian/sid image: debian/sid
secrets: secrets:
- ccb6777e-650b-4fa2-87e1-e2342f5bb605 # tzpfms SSH key - ccb6777e-650b-4fa2-87e1-e2342f5bb605 # tzpfms SSH key
- 513a97a6-300c-4749-93a7-7816b2c4f41d # tzpfms auth token - b26aef46-5e87-45b1-a64d-d136ccde36a7 # tzpfms auth token
packages: packages:
- clang - clang
- llvm-dev - llvm-dev
- pkg-config - pkg-config
- libtss2-dev - libtss2-dev
- libtspi-dev - libtspi-dev
- ronn - libssl-dev
- gettext
- mandoc
- shellcheck - shellcheck
- curl - curl
- groff
- ghostscript
tasks: tasks:
- get-zfs: | - get-zfs: |
sudo sed -i 's/main/main contrib non-free/' /etc/apt/sources.list sudo sed -i 's/main/main contrib/' /etc/apt/sources.list
sudo apt update sudo apt-get update
sudo apt install -y libzfslinux-dev sudo apt-get install -y libzfslinux-dev
- build-gcc: | - build-gcc: |
cd tzpfms cd tzpfms
(cd src/bin/; ln -s ../../contrib/*.cpp .)
make make
find out/ -maxdepth 1 -type f -exec readelf -d {} + find out/ -maxdepth 1 -type f -exec readelf -d {} + | tee /dev/stderr | grep Shared | LC_ALL=C sort -u
make clean make clean
find src/bin/ -type l -delete
- build-clang: | - build-clang: |
cd tzpfms cd tzpfms
CC=clang CXX=clang++ make CC=clang CXX=clang++ make
find out/ -maxdepth 1 -type f -exec readelf -d {} + find out/ -maxdepth 1 -type f -exec readelf -d {} + | tee /dev/stderr | grep Shared | LC_ALL=C sort -u
- manpages: | - manpages: |
sudo sh -c 'curl https://git.sr.ht/~nabijaczleweli/groff-1.23-unfucking/blob/trunk/mdoc.local >> /etc/groff/mdoc.local'
git -C tzpfms/ worktree add ../tzpfms-man man git -C tzpfms/ worktree add ../tzpfms-man man
cd tzpfms-man cd tzpfms-man
git ls-tree -z --name-only HEAD | xargs -0 rm -r git ls-tree -z --name-only HEAD | grep -z tpm | xargs -0 rm -r
cp -pr ../tzpfms/out/man/* . cp -pr ../tzpfms/out/man/* .
sed -e 's/…/.../g' $(ls *.[12345678] | awk -F. '{print $2 "\t" $1}' | sort | awk -F'\t' '{print $2 "." $1}') | groff -K utf8 -tpe -mdoc -Tps -dpaper=a4 -P-pa4 > tzpfms.ps
ps2pdf -sPAPERSIZE=a4 tzpfms.ps tzpfms.pdf
git add . git add .
git config user.email "nabijaczleweli/autouploader@nabijaczleweli.xyz" git config user.email "nabijaczleweli/autouploader@nabijaczleweli.xyz"
git config user.name "наб autouploader" git config user.name "наб autouploader"
@ -38,20 +47,34 @@ tasks:
ssh-keyscan git.sr.ht > ~/.ssh/known_hosts ssh-keyscan git.sr.ht > ~/.ssh/known_hosts
git push git push
- release: | - release: |
set -o posix # bash in non-POSIX mode doesn't process aliases non-interactively. good shell.
tag="$(git -C tzpfms describe --abbrev=0 --tags || echo HEAD~1)" tag="$(git -C tzpfms describe --abbrev=0 --tags || echo HEAD~1)"
[ "$(git -C tzpfms rev-list -n1 "$tag")" = "$(git -C tzpfms rev-list -n1 HEAD)" ] || exit 0 [ "$(git -C tzpfms rev-list -n1 "$tag")" = "$(git -C tzpfms rev-list -n1 HEAD)" ] || exit 0
mkdir "tzpfms-$tag-bin-amd64" mkdir "tzpfms-$tag-bin-amd64"
mv tzpfms/out/zfs-tpm* "tzpfms-$tag-bin-amd64" mv tzpfms/out/zfs-tpm* "tzpfms-$tag-bin-amd64"
mv tzpfms/out/locale "tzpfms-$tag-bin-amd64"
mv tzpfms/out/dracut "tzpfms-$tag-dracut" mv tzpfms/out/dracut "tzpfms-$tag-dracut"
mv tzpfms/out/initramfs-tools "tzpfms-$tag-initramfs-tools" mv tzpfms/out/initramfs-tools "tzpfms-$tag-initramfs-tools"
mv tzpfms/out/systemd "tzpfms-$tag-systemd"
mv tzpfms/out/man "tzpfms-$tag-man" mv tzpfms/out/man "tzpfms-$tag-man"
for s in bin-amd64 dracut initramfs-tools man; do mv tzpfms-man/tzpfms.ps "tzpfms-$tag-manual.ps"
mv tzpfms-man/tzpfms.pdf "tzpfms-$tag-manual.pdf"
for s in bin-amd64 dracut initramfs-tools systemd man; do
tar -caf "tzpfms-$tag-$s.tbz2" "tzpfms-$tag-$s" tar -caf "tzpfms-$tag-$s.tbz2" "tzpfms-$tag-$s"
done done
sha256sum *.tbz2 "tzpfms-$tag-manual.ps" "tzpfms-$tag-manual.pdf"
set +x # Avoid echoing the token set +x # Avoid echoing the token
for f in *.tbz2; do alias curl="curl --oauth2-bearer $(cat ~/.release-token)"
curl -H "Authorization: Bearer $(cat ~/.release-token)" \ id="$(curl -F operations='{"query": "{me {repository(name: \"tzpfms\") {id}}}"}' -F map='{}' https://git.sr.ht/query | tr -cd 0-9)"
-XPOST \ for f in *.tbz2 "tzpfms-$tag-manual.ps" "tzpfms-$tag-manual.pdf"; do
-F "file=@$f" \ curl -F operations='{
"https://git.sr.ht/api/repos/tzpfms/artifacts/$tag" "query": "mutation($id: Int!, $rev: String!, $file: Upload!) {uploadArtifact(repoId: $id, revspec: $rev, file: $file) { created, filename, checksum } }",
"variables": { "file": null, "rev": "'"$(git -C tzpfms rev-list -n1 "$tag")"'", "id": '"$id"' }
}' \
-F map='{
"a": ["variables.file"]
}' \
-F a=@"$f" \
https://git.sr.ht/query
echo
done done

13
.gitignore vendored
View File

@ -4,17 +4,22 @@
!.build.yml !.build.yml
!.travis.yml !.travis.yml
!gh_rsa.enc !gh_rsa.enc
!LICENSE
!.clang-format !.clang-format
!*Makefile !Makefile
!*.sublime-project !*.sublime-project
!*.md !*.md
!*.awk !*.awk
!LICENSES
!LICENSES/**
!src !src
!src/** !src/**
!man !man
!man/** !man/**
!ext !contrib
!ext/** !contrib/**
!initrd !initrd
!initrd/** !initrd/**
!init.d
!init.d/**
!po
!po/**

10
LICENSES/0BSD.txt Normal file
View File

@ -0,0 +1,10 @@
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

200
Makefile
View File

@ -1,93 +1,163 @@
# The MIT License (MIT) # SPDX-License-Identifier: MIT
# Copyright (c) 2020 наб <nabijaczleweli@nabijaczleweli.xyz>
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
include configMakefile -include Makefile.local
TZPFMS_VERSION ?= "$(patsubst v%,%,$(shell git describe))"
SOURCE_DATE_EPOCH ?=
TZPFMS_DATE ?=
AWK ?= awk
SED ?= sed
MANDOC ?= mandoc
PKG_CONFIG ?= pkgconf
SHELLCHECK ?= shellcheck
NOLOCREGEN ?=
OUTDIR := out/
BLDDIR := out/build/
SYSTEMD_SYSTEM_UNITDIR := $(shell $(PKG_CONFIG) --variable=systemd_system_unit_dir systemd 2>/dev/null || echo /usr/lib/systemd/system)
ifneq "$(TZPFMS_DATE)" ""
MANUAL_DATE := $(TZPFMS_DATE)
else ifneq "$(SOURCE_DATE_EPOCH)" ""
MANUAL_DATE := $(shell LC_ALL=C date -d@$(SOURCE_DATE_EPOCH) '+%B %e, %Y')
else
MANUAL_DATE = $(shell LC_ALL=C date -d@$(shell git log -1 --no-show-signature --format=%at $(1)) '+%B %e, %Y')
endif
LDDLLS := rt tspi $(OS_LD_LIBS)
PKGS := libzfs libzfs_core tss2-esys tss2-rc PKGS := libzfs libzfs_core tss2-esys tss2-rc
LDAR := $(LNCXXAR) $(foreach l,,-L$(BLDDIR)$(l)) $(foreach dll,$(LDDLLS),-l$(dll)) $(shell pkg-config --libs $(PKGS)) PKG_CONFIG_LIBS := $(shell $(PKG_CONFIG) --libs $(PKGS))
INCAR := $(foreach l,$(foreach l,,$(l)/include),-isystemext/$(l)) $(foreach l,,-isystem$(BLDDIR)$(l)/include) $(shell pkg-config --cflags $(PKGS)) PKG_CONFIG_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKGS))
VERAR := $(foreach l,TZPFMS,-D$(l)_VERSION='$($(l)_VERSION)')
BINARY_SOURCES := $(sort $(wildcard $(SRCDIR)bin/*.cpp $(SRCDIR)bin/**/*.cpp)) LDFLAGS += -Wl,--as-needed
COMMON_SOURCES := $(filter-out $(BINARY_SOURCES),$(sort $(wildcard $(SRCDIR)*.cpp $(SRCDIR)**/*.cpp $(SRCDIR)**/**/*.cpp $(SRCDIR)**/**/**/*.cpp))) LDLIBS += -lrt -ltspi -lcrypto $(PKG_CONFIG_LIBS)
MANPAGE_HEADERS := $(sort $(wildcard $(MANDIR)*.h)) CXXFLAGS += -g -O3 -std=c++17 -fno-exceptions -fno-rtti -Wall -Wextra -pipe -fPIC $(PKG_CONFIG_CFLAGS)
MANPAGE_SOURCES := $(sort $(wildcard $(MANDIR)*.md.pp)) CPPFLAGS += -MD -DTZPFMS_VERSION='$(TZPFMS_VERSION)'
INITRD_HEADERS := $(sort $(wildcard $(INITRDDIR)*.h)) BINARY_SOURCES := $(sort $(wildcard src/bin/*.cpp src/bin/**/*.cpp))
COMMON_SOURCES := $(filter-out $(BINARY_SOURCES),$(sort $(wildcard src/*.cpp src/**/*.cpp src/**/**/*.cpp src/**/**/**/*.cpp)))
MANPAGE_HEADERS := $(sort $(wildcard man/*.h))
MANPAGE_SOURCES := $(sort $(wildcard man/*.[0-8].pp))
INITRD_HEADERS := $(sort $(wildcard initrd/*.h))
ifneq (,$(shell command -v msgfmt))
LOCALES := $(wildcard po/*.po)
endif
.PHONY : all clean build shellcheck i-t dracut man ifdef TZPFMS_PASSPHRASE_HELPER
.SECONDARY: CPPFLAGS += -DTZPFMS_PASSPHRASE_HELPER='$(TZPFMS_PASSPHRASE_HELPER)'
endif
ifdef TZPFMS_PASSPHRASE_HELPER_MAN
DEF_TPH_MAN := .Pp\nDefault:\n.No '\'' Ns $(TZPFMS_PASSPHRASE_HELPER_MAN) Ns '\'' .
else
DEF_TPH_MAN ?= .
endif
all : build man shellcheck i-t dracut ifneq "$(findstring clang,$(shell $(CXX) --version))" ""
# GCC doesn't have this granularity
CXXFLAGS += -flto=full -pedantic -Wno-gnu-statement-expression -Wno-gnu-include-next -Wno-gnu-conditional-omitted-operand -Wno-c++20-designator -Wno-variadic-macros -Wno-c99-extensions
ifeq "$(AR)" "ar"
# Need this because -flto=full produces objects FSF ranlib (ar s) can't index
override AR := llvm-ar
endif
else
CXXFLAGS += -flto
endif
.PHONY : all locales clean build shellcheck i-t dracut init.d-systemd manpages htmlpages
.SECONDARY :
all : build locales manpages htmlpages shellcheck i-t init.d-systemd dracut
shellcheck : i-t dracut shellcheck : i-t dracut
find $(OUTDIR)initramfs-tools/ $(OUTDIR)dracut -name '*.sh' -exec echo $(SHELLCHECK) --exclude SC1091 {} + | sh -x $(SHELLCHECK) --exclude SC1091,SC2093 $(foreach l,$(OUTDIR)initramfs-tools/ $(OUTDIR)dracut/ init.d/,$(wildcard $(l)*.sh $(l)**/*.sh $(l)**/**/*.sh $(l)**/**/**/*.sh $(l)**/**/**/**/*.sh $(l)**/**/**/**/**/*.sh $(l)**/**/**/**/**/**/*.sh $(l)**/**/**/**/**/**/**/*.sh $(l)**/**/**/**/**/**/**/**/*.sh))
clean : clean :
rm -rf $(OUTDIR) rm -rf $(OUTDIR)
build : $(subst $(SRCDIR)bin/,$(OUTDIR),$(subst .cpp,$(EXE),$(BINARY_SOURCES))) build : $(subst src/bin/,$(OUTDIR),$(subst .cpp,,$(BINARY_SOURCES)))
man : $(OUTDIR)man/index.txt manpages : $(patsubst man/%.pp,$(OUTDIR)man/%,$(MANPAGE_SOURCES))
htmlpages : $(patsubst man/%.pp,$(OUTDIR)man/%.html,$(MANPAGE_SOURCES)) $(OUTDIR)man/style.css
locales : $(patsubst po/%.po,$(OUTDIR)locale/%/LC_MESSAGES/tzpfms.mo,$(LOCALES))
i-t : $(OUTDIR)initramfs-tools/usr/share/initramfs-tools/hooks/tzpfms $(OUTDIR)initramfs-tools/usr/share/tzpfms/initramfs-tools-zfs-patch.sh i-t : $(OUTDIR)initramfs-tools/usr/share/initramfs-tools/hooks/tzpfms $(OUTDIR)initramfs-tools/usr/share/tzpfms/initramfs-tools-zfs-patch.sh
dracut : $(patsubst $(INITRDDIR)dracut/%,$(OUTDIR)dracut/usr/lib/dracut/modules.d/91tzpfms/%,$(sort $(wildcard $(INITRDDIR)dracut/*.sh))) dracut : $(patsubst initrd/dracut/%,$(OUTDIR)dracut/usr/lib/dracut/modules.d/91tzpfms/%,$(sort $(wildcard initrd/dracut/*.sh)))
init.d-systemd : $(OUTDIR)systemd/$(SYSTEMD_SYSTEM_UNITDIR)/zfs-load-key@.service.d/tzpfms.conf $(OUTDIR)systemd/usr/libexec/tzpfms-zfs-load-key@ $(foreach l,$(subst init.d/systemd/usr-share-tzpfms/,,$(wildcard init.d/systemd/usr-share-tzpfms/*)),$(OUTDIR)systemd/usr/share/tzpfms/$(l))
$(OUTDIR)man/index.txt : $(MANDIR)index.txt $(patsubst $(MANDIR)%.pp,$(OUTDIR)man/%,$(MANPAGE_SOURCES)) $(BLDDIR)tzpfms.pot: src/*.[ch]pp src/bin/*.[ch]pp
@mkdir -p $(dir $@) @mkdir -p $(@D)
cp $< $(dir $@) $(NOLOCREGEN)xgettext --check=ellipsis-unicode --from-code=UTF-8 -c -io- $^ | sed -n '/^#[:.]/,$$p' > $@
$(RONN) --organization="tzpfms developers" $(filter-out $<,$^) $(NOLOCREGEN)$(foreach l,$(LOCALES),msgmerge --backup=off --no-wrap -Uiq $(l) $@ &&) :
$(RONN) --organization="tzpfms developers" -f $(filter-out $<,$^) @>> $@
$(OUTDIR)initramfs-tools/usr/share/initramfs-tools/hooks/tzpfms: $(INITRDDIR)initramfs-tools/hook $(INITRD_HEADERS) $(OUTDIR)locale/%/LC_MESSAGES/tzpfms.mo : po/%.po $(BLDDIR)tzpfms.pot
@mkdir -p $(@D)
msgfmt --statistics --check-format --check-domain -o $@ $<
$(OUTDIR)initramfs-tools/usr/share/initramfs-tools/hooks/tzpfms: initrd/initramfs-tools/hook $(INITRD_HEADERS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(AWK) -f pp.awk $< > $@ $(AWK) -f pp.awk $< > $@
chmod --reference $< $@ chmod --reference $< $@
$(OUTDIR)initramfs-tools/usr/share/tzpfms/initramfs-tools-zfs-patch.sh: $(INITRDDIR)initramfs-tools/zfs-patch.sh $(INITRD_HEADERS) $(OUTDIR)initramfs-tools/usr/share/tzpfms/initramfs-tools-zfs-patch.sh: initrd/initramfs-tools/zfs-patch.sh $(INITRD_HEADERS)
@mkdir -p $(dir $@)
$(AWK) -f pp.awk $< > $@
chmod --reference $< $@
$(OUTDIR)systemd/$(SYSTEMD_SYSTEM_UNITDIR)/zfs-load-key@.service.d/tzpfms.conf : init.d/systemd/zfs-load-key@.service.d-tzpfms.conf
@mkdir -p $(dir $@)
ln -f $< $@ || cp $< $@
$(OUTDIR)systemd/usr/libexec/tzpfms-zfs-load-key@ : init.d/systemd/libexec-tzpfms-zfs-load-key@.sh
@mkdir -p $(dir $@)
ln -f $< $@ || cp $< $@
$(OUTDIR)systemd/usr/share/tzpfms/% : init.d/systemd/usr-share-tzpfms/%
@mkdir -p $(dir $@)
ln -f $< $@ || cp $< $@
# The d-v-o-s string starts at "BSD" (hence the "BSD General Commands Manual" default); we're not BSD, so hide it
# Can't put it at the very top, since man(1) only loads mdoc *after* the first mdoc macro (.Dd in our case)
$(OUTDIR)man/% : man/%.pp $(MANPAGE_HEADERS)
@mkdir -p $(dir $@)
$(AWK) -f pp.awk $< TZPFMS_PASSPHRASE_HELPER_MAN='$(DEF_TPH_MAN)' | $(AWK) '/^$$/ {prev_empty=1; next} $$1 == "#" && $$2 ~ /^[0-9]*$$/ {prev_empty=0; next} {if(prev_empty) print ""; prev_empty=0; print}' | $(AWK) '$$0 == ".Dd" {$$2 = "$(call MANUAL_DATE,$<)"} $$1 == ".Dt" { print ".ds doc-volume-operating-system" } $$0 == ".Os" {$$2 = "tzpfms"; $$3 = $(TZPFMS_VERSION)} {print}' > $@
! $(MANDOC) -Tlint $@ 2>&1 | grep -vE -e 'mandoc: outdated mandoc.db' -e 'STYLE: referenced manual not found' -e 'STYLE: operating system explicitly specified: Os tzpfms' -e 'WARNING: cross reference to self: Xr zfs-tpm.*-change-key 8' -e 'STYLE: input text line longer than 80 bytes'
# The "WARNING: unknown font, skipping request: TS.+fC[RBI]" one: see https://bugs.debian.org/992002
$(OUTDIR)man/%.html : $(OUTDIR)man/%
@mkdir -p $(dir $@)
( cd $(OUTDIR)man/ && $(MANDOC) -Thtml -Ostyle="style.css",man="%N.%S.html;https://manpages.debian.org/bookworm/%N.%S" ) < $< | \
$(AWK) '/^<h1/ {in_syn = $$0 ~ /id="SYNOPSIS"/} /^<br/ {if(in_syn) next} {print}' | \
$(SED) -Ee 's/ title=".."//g' -e 's/<a class="permalink" href="#([^"]*)"><span class="No" id="([^"]*)">/<a><span class="No">/g' -e 's#manpages.debian.org/[^/]*/ESYS_CONTEXT.3#mankier.com/3/ESYS_CONTEXT#g' > $@
$(OUTDIR)man/style.css : man/style.css
@mkdir -p $(dir $@)
cp $^ $@
$(BLDDIR)libtzpfms.a : $(subst src/,$(BLDDIR)obj/,$(subst .cpp,.o,$(COMMON_SOURCES)))
$(AR) --thin crs $@ $^
$(OUTDIR)% : $(BLDDIR)obj/bin/%.o $(BLDDIR)libtzpfms.a
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(BLDDIR)obj/%.o : src/%.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
$(OUTDIR)dracut/usr/lib/dracut/modules.d/91tzpfms/% : initrd/dracut/% $(INITRD_HEADERS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(AWK) -f pp.awk $< > $@ $(AWK) -f pp.awk $< > $@
chmod --reference $< $@ chmod --reference $< $@
$(OUTDIR)%$(EXE) : $(subst $(SRCDIR),$(OBJDIR),$(subst .cpp,$(OBJ),$(SRCDIR)bin/%.cpp $(COMMON_SOURCES))) include $(wildcard $(BLDDIR)*/*.d $(BLDDIR)*/*/*.d)
@mkdir -p $(dir $@)
$(CXX) $(CXXAR) -o$@ $^ $(PIC) -Wl,--as-needed $(LDAR)
$(STRIP) $(STRIPAR) $@
$(OBJDIR)%$(OBJ) : $(SRCDIR)%.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXAR) $(INCAR) $(VERAR) -c -o$@ $^
$(BLDDIR)test/%$(OBJ) : $(TSTDIR)%.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXAR) $(INCAR) -I$(SRCDIR) $(VERAR) -c -o$@ $^
$(OUTDIR)man/%.md : $(MANDIR)%.md.pp $(MANPAGE_HEADERS)
@mkdir -p $(dir $@)
$(AWK) -f pp.awk $< > $@
$(OUTDIR)dracut/usr/lib/dracut/modules.d/91tzpfms/% : $(INITRDDIR)dracut/% $(INITRD_HEADERS)
@mkdir -p $(dir $@)
$(AWK) -f pp.awk $< > $@
chmod --reference $< $@

View File

@ -1,7 +1,9 @@
# tzpfms [![builds.sr.ht badge](//builds.sr.ht/~nabijaczleweli/tzpfms.svg)](https://builds.sr.ht/~nabijaczleweli/tzpfms) [![Licence](//img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) # tzpfms [![builds.sr.ht badge](//builds.sr.ht/~nabijaczleweli/tzpfms.svg)](//builds.sr.ht/~nabijaczleweli/tzpfms)
TPM-based encryption keys for ZFS datasets. TPM-based encryption keys for ZFS datasets.
## [Manpages](//git.sr.ht/~nabijaczleweli/tzpfms/tree/man) See also [fzifdso](//git.sr.ht/~nabijaczleweli/fzifdso) for FIDO2/WebAuthn-based (YubiKey, Somu, &c.) keys.
## [Manpages](//ra.ws.co.ls/~nabijaczleweli/tzpfms/blob/man/zfs-tpm-list.8.html) ([PDF](//ra.ws.co.ls/~nabijaczleweli/tzpfms/blob/man/tzpfms.pdf))
### Why? ### Why?
@ -18,51 +20,71 @@ Essentially BitLocker, but for ZFS
a random raw key is generated and sealed to the TPM (both 2 and 1.x supported) with an additional optional password in front of it, a random raw key is generated and sealed to the TPM (both 2 and 1.x supported) with an additional optional password in front of it,
tying the dataset to the platform and an additional optional secret (or to the posession of the back-up). tying the dataset to the platform and an additional optional secret (or to the posession of the back-up).
Additionally, 1.x TPMs support PCR binding with and without passwords.
2 TPMs support PCR binding without a password and PCR binding *OR* a password both may be set, and any can be used to unseal (exclusive by default to prevent foot-guns).
Both dracut (with/without Plymouth) (with/without hostonly) (only on systemd systems, I don't have a test-bed for the non-systemd path) Both dracut (with/without Plymouth) (with/without hostonly) (only on systemd systems, I don't have a test-bed for the non-systemd path)
and initramfs-tools (with/without Plymouth) are supported for [ZFS-on-root](https://nabijaczleweli.xyz/content/blogn_t/005-low-curse-zfs-on-root.html) set-ups. and initramfs-tools (with/without Plymouth) are supported for [ZFS-on-root](//nabijaczleweli.xyz/content/blogn_t/005-low-curse-zfs-on-root.html) set-ups.
### Building ### Building
You'll need `pkg-config`, `ronn`, `shellcheck`, `libzfslinux-dev`, `libtss2-dev`, `libtspi-dev`, and `make` should hopefully Just Work™ if you have a C++17-capable compiler. You'll need `pkgconf`, `shellcheck`, `libzfslinux-dev` (0.8.x and 2.[012].x work), `libtss2-dev`, `libtspi-dev`, `libssl-dev`, and `make` should hopefully Just Work™ if you have a C++17-capable compiler.
The output binaries are trimmed of extraneous dependencies, so they're all just libc + libzfs and friends + the chosen TPM back-end, if any. The output binaries are trimmed of extraneous dependencies, so they're all just libc + libzfs and friends + the chosen TPM back-end, if any + libcrypto for TPM2 PCR handling.
`mandoc` is required for HTML manuals. Set `MANDOC=true` to forgo this.
The default `$TZPFMS_PASSPHRASE_HELPER` is the null string.
To set a different default, set `TZPFMS_PASSPHRASE_HELPER` and `TZPFMS_PASSPHRASE_HELPER_MAN` for `make``$`s need to be double-escaped and `'`s need to be full-`'` escaped (i.e. `'\''`).
As an example, for a sensible default value of `exec systemd-ask-password --id="tzpfms:$2" "$1:"` for OOB systemd integration, pass `TZPFMS_PASSPHRASE_HELPER='exec systemd-ask-password --id="tzpfms:$$2" "$$1:"'` and `TZPFMS_PASSPHRASE_HELPER_MAN='Ic exec Nm systemd-ask-password Fl -id Ns Li = Ns Qo Li tzpfms:\& Ns Ar $$2 Qc Qo Ar $$1 Ns Li ":\&" Qc'`.
### Installation ### Installation
Copy the `out/zfs-tpm*` binaries corresponding to the back-ends you want to `/sbin`, Copy the `out/zfs-tpm*` binaries corresponding to the back-ends you want to `/sbin`,
continue as the [manual](//git.sr.ht/~nabijaczleweli/tzpfms/tree/man/zfs-tpm2-change-key.md) [page](//git.sr.ht/~nabijaczleweli/tzpfms/tree/man/zfs-tpm1x-change-key.md) instructs. continue as the [manual](//ra.ws.co.ls/~nabijaczleweli/tzpfms/blob/man/zfs-tpm2-change-key.8.html) [page](//ra.ws.co.ls/~nabijaczleweli/tzpfms/blob/man/zfs-tpm1x-change-key.8.html) instructs.
For initrd support, copy the content of either `out/dracut/` or `out/initramfs-tools/` over `/`; For initrd support, copy the content of either `out/dracut/` or `out/initramfs-tools/` over `/`;
these need `zfs-tpm-list` but will work with any combination of back-end `*-load-key` binaries. these need `zfs-tpm-list` but will work with any combination of back-end `*-load-key` binaries
(local TPM1.X initrds need to be updated when the system state changes (e.g. the TPM is taken ownership of)).
<!-- #### From Debian repository To integrate with [zfs-mount-generator(8)](//manpages.debian.org/bookworm/zfsutils-linux/zfs-mount-generator.8.html)
[copy](//lfs.nabijaczleweli.xyz/0017-twitter-export#1472986504272261124) `out/systemd/` over `/`.
#### From Debian repository
The following line in `/etc/apt/sources.list` or equivalent: The following line in `/etc/apt/sources.list` or equivalent:
```apt ```apt
deb https://debian.nabijaczleweli.xyz sid main deb [signed-by=/etc/apt/keyrings/nabijaczleweli.asc] https://debian.nabijaczleweli.xyz sid main
``` ```
With [my PGP key](//nabijaczleweli.xyz/pgp.txt) (the two URLs are interchangeable): With [my PGP key](//nabijaczleweli.xyz/pgp.txt) (the two URLs are interchangeable):
```sh ```sh
wget -O- https://debian.nabijaczleweli.xyz/nabijaczleweli.gpg.key | sudo apt-key add sudo wget -O/etc/apt/keyrings/nabijaczleweli.asc https://debian.nabijaczleweli.xyz/nabijaczleweli.gpg.key
# or sudo wget -O/etc/apt/keyrings/nabijaczleweli.asc https://nabijaczleweli.xyz/pgp.txt
sudo wget -O/etc/apt/trusted.gpg.d/nabijaczleweli.asc //keybase.io/nabijaczleweli/pgp_keys.asc
``` ```
(you may need to create /etc/apt/keyrings on apt <2.4.0 (<=bullseye) manually).
Then the usual Then the usual
```sh ```sh
sudo apt update sudo apt update
sudo apt install tzpfms sudo apt install tzpfms-tpm2 tzpfms-dracut
``` ```
will work on amd64, x32, and i386. will work on amd64, x32, and i386.
See the [repository README](//debian.nabijaczleweli.xyz/README) for more information. --> See the [repository README](//debian.nabijaczleweli.xyz/README) for more information.
#### From tar-ball
Release tarballs are signed with <nabijaczleweli@nabijaczleweli.xyz> (pull with WKD, but `7D69 474E 8402 8C5C C0C4 4163 BCFD 0B01 8D26 58F1`).
аnd stored in git notes as-if via [the example program](//man.sr.ht/git.sr.ht/#signing-tagsx27-tarballs)
and are thus available on the [refs listing](https://git.sr.ht/~nabijaczleweli/tzpfms/refs)/tag page as .tar.gz.asc.
### Testing ### Testing
#### TPM2 #### TPM2
Build [`swtpm`](//github.com/stefanberger/swtpm), then prepare and run it: Install [`swtpm`](//github.com/stefanberger/swtpm), then prepare and run it:
```sh ```sh
swtpm_setup --tpmstate tpm2-state --tpm2 --createek --display --logfile /dev/stdout --overwrite swtpm_setup --tpmstate tpm2-state --tpm2 --createek --display --logfile /dev/tty --overwrite
swtpm socket --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --tpm2 --tpmstate dir=tpm2-state --flags not-need-init --log level=10 swtpm socket --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --tpm2 --tpmstate dir=tpm2-state --flags not-need-init --log level=10
``` ```
@ -72,10 +94,10 @@ ln -s /usr/lib/i386-linux-gnu/libtss2-tcti-{swtpm,default}.so
``` ```
#### TPM1.x #### TPM1.x
Build [`swtpm`](//github.com/stefanberger/swtpm), then prepare and run it and Install [`swtpm`](//github.com/stefanberger/swtpm), then prepare and run it and
([hopefully](https://github.com/stefanberger/swtpm/issues/5#issuecomment-210607890)) [TrouSerS](//sourceforge.net/projects/trousers), as `root`/`tpm`: ([hopefully](//github.com/stefanberger/swtpm/issues/5#issuecomment-210607890)) [TrouSerS](//sourceforge.net/projects/trousers), as `root`/`tpm`:
```sh ```sh
swtpm_setup --tpmstate tpm1x-state --createek --display --logfile /dev/stdout --overwrite swtpm_setup --tpmstate tpm1x-state --createek --display --logfile /dev/tty --overwrite
swtpm cuse -n tpm --tpmstate dir=tpm1x-state --seccomp action=none --log level=10,file=/dev/fd/4 4>&1 swtpm cuse -n tpm --tpmstate dir=tpm1x-state --seccomp action=none --log level=10,file=/dev/fd/4 4>&1
swtpm_ioctl -i /dev/tpm swtpm_ioctl -i /dev/tpm
TPM_DEVICE=/dev/tpm swtpm_bios TPM_DEVICE=/dev/tpm swtpm_bios
@ -107,12 +129,25 @@ There's [the tracker](//todo.sr.ht/~nabijaczleweli/tzpfms), but also see the lis
## Contributing ## Contributing
Send a patch inline, as an attachment, or a git link and a ref to pull from to Send a patch inline, as an attachment, or a git link and a ref to pull from to
[the list](//lists.sr.ht/~nabijaczleweli/tzpfms) ([~nabijaczleweli/tzpfms@lists.sr.ht](mailto:~nabijaczleweli/tzpfms)) or [me](mailto:nabijaczleweli@nabijaczleweli.xyz) [the list](//lists.sr.ht/~nabijaczleweli/tzpfms) ([~nabijaczleweli/tzpfms@lists.sr.ht](mailto:~nabijaczleweli/tzpfms@lists.sr.ht)) or [me](mailto:nabijaczleweli@nabijaczleweli.xyz)
directly. I'm not picky, just please include the repo name in the subject prefix. directly. I'm not picky, just please include the repo name in the subject prefix.
### "Ecosystem"
Put a line containing the `zfs-{...}-load-key` program name followed by a list of required services (if any) into `/usr/share/tzpfms/${tzpfms.backend}`
and the `zfs-load-key@.service` drop-in will understand it and use it to load.
Known compatible backends:
| `/usr/share/tzpfms/` | data | from | for |
| -------------------- | ------------------------------------- | ---------------------------------------------- | -------------- |
| `TPM1.X` | `zfs-tpm1x-load-key trousers.service` | [tzpfms](//sr.ht/~nabijaczleweli/tzpfms) | TPM |
| `TPM2` | `zfs-tpm2-load-key` | [tzpfms](//sr.ht/~nabijaczleweli/tzpfms) | TPM |
| `FIDO2` | `zfs-fido2-load-key` | [fzifdso](//git.sr.ht/~nabijaczleweli/fzifdso) | FIDO2/WebAuthn |
## Discussion ## Discussion
Please use the tracker, the list, or [Twitter](//twitter.com/nabijaczleweli/status/1315137083380559873). Please use the tracker, the list, or [mastussy](//101010.pl/@nabijaczleweli) (formerly [Twitter](//lfs.nabijaczleweli.xyz/0017-twitter-export#1315137083380559873)).
## Special thanks ## Special thanks
@ -120,3 +155,5 @@ To all who support further development on Patreon, in particular:
* ThePhD * ThePhD
* Embark Studios * Embark Studios
* Jasper Bekkers
* EvModder

View File

@ -1,68 +0,0 @@
# The MIT License (MIT)
# Copyright (c) 2020 наб <nabijaczleweli@nabijaczleweli.xyz>
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
EXE :=
DLL := .so
PIC := -fPIC
OS_LD_LIBS :=
CXXVER := $(shell $(CXX) --version)
ifneq "$(findstring clang,$(CXXVER))" ""
# GCC doesn't have this granularity
CXXSPECIFIC := -flto=full -pedantic -Wno-gnu-statement-expression -Wno-gnu-include-next
else
CXXSPECIFIC := -flto
endif
ifneq "$(ADDITIONAL_INCLUDE_DIR)" ""
INCCXXAR := -isystem$(ADDITIONAL_INCLUDE_DIR)
else
INCCXXAR :=
endif
ifneq "$(ADDITIONAL_LINK_DIR)" ""
LNCXXAR := -L$(ADDITIONAL_LINK_DIR)
else
LNCXXAR :=
endif
TZPFMS_VERSION := "$(patsubst v%,%,$(shell git describe))"
INCCMAKEAR := CXXFLAGS="$(INCCXXAR)"
LNCMAKEAR := LDFLAGS="$(LNCXXAR)"
AWK ?= awk
RONN ?= ronn
SHELLCHECK ?= shellcheck
OBJ := .o
CXXAR := -O3 -std=c++17 -fno-exceptions -Wall -Wextra $(CXXSPECIFIC) -pipe $(INCCXXAR) $(PIC)
STRIP ?= strip
STRIPAR := --strip-all --remove-section=.comment --remove-section=.note
OUTDIR := out/
BLDDIR := out/build/
OBJDIR := $(BLDDIR)obj/
SRCDIR := src/
TSTDIR := test/
MANDIR := man/
INITRDDIR := initrd/

2
contrib/README Normal file
View File

@ -0,0 +1,2 @@
These are development aids, not for distribution.
Link them to src/bin/ to build.

View File

@ -0,0 +1,59 @@
/* SPDX-License-Identifier: MIT */
#include <stdio.h>
#include "../main.hpp"
#include "../tpm1x.hpp"
#define THIS_BACKEND "TPM1.X"
int main(int argc, char ** argv) {
uint32_t * pcrs{};
size_t pcrs_len{};
bool just_read{};
return do_main(
argc, argv, "RP:", "[-R] [-P PCR[,PCR]...]",
[&](auto o) {
switch(o) {
case 'R':
return just_read = true, 0;
case 'P':
return tpm1x_parse_pcrs(optarg, pcrs, pcrs_len);
default:
__builtin_unreachable();
}
},
[&](auto) {
return with_tpm1x_session([&](auto ctx, auto, auto) {
TSS_HTPM tpm_h{};
TRY_TPM1X("extract TPM from context", Tspi_Context_GetTpmObject(ctx, &tpm_h));
for(size_t i = 0; i < pcrs_len; i++) {
char buf[512];
snprintf(buf, sizeof(buf), "muddle PCR %" PRIu32 "", pcrs[i]);
BYTE * val{};
uint32_t val_len{};
if(just_read)
TRY_TPM1X(buf, Tspi_TPM_PcrRead(tpm_h, pcrs[i], &val_len, &val));
else {
BYTE data[TPM_SHA1_160_HASH_LEN];
getentropy(data, sizeof(data));
TRY_TPM1X(buf, Tspi_TPM_PcrExtend(tpm_h, pcrs[i], sizeof(data), data, nullptr, &val_len, &val));
}
printf("PCR%u: ", pcrs[i]);
for(auto i = 0u; i < val_len; ++i)
printf("%02hhX", ((uint8_t *)val)[i]);
printf("\n");
}
return 0;
});
});
}

View File

@ -0,0 +1,30 @@
#!/bin/sh
# SPDX-License-Identifier: MIT
DSET="$1"
exec 2>>/dev/kmsg
zfs-tpm-list -H "$DSET" | while read -r _ backend keystatus coherent; do
[ "$keystatus" = 'available' ] && exit
[ "$coherent" = 'yes' ] || {
printf "%s\n" "${0##*/}[$$]: $DSET: incoherent tzpfms back-end $backend." "You might need to restore from back-up!" >&2
exit 1
}
unlock=; deps=
read -r unlock deps 2>/dev/null < "/usr/share/tzpfms/$backend"
command -v "$unlock" >/dev/null || {
printf "%s\n" "${0##*/}[$$]: $DSET: unknown tzpfms back-end $backend." >&2
exit # fall through, maybe there's another handler
}
# shellcheck disable=2086
[ -n "$deps" ] && systemctl start $deps
# shellcheck disable=2016
[ -z "$TZPFMS_PASSPHRASE_HELPER" ] && export TZPFMS_PASSPHRASE_HELPER='exec systemd-ask-password --id="tzpfms:$2" "$1:"'
exec "$unlock" "$DSET"
done
# Dataset doesn't exist, fall through

View File

@ -0,0 +1 @@
zfs-tpm1x-load-key trousers.service

View File

@ -0,0 +1 @@
zfs-tpm2-load-key

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: MIT
[Service]
ExecStartPre=/usr/libexec/tzpfms-zfs-load-key@ %I

View File

@ -1,50 +1,57 @@
#!/bin/sh #!/bin/sh
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# shellcheck disable=SC2086
#include "../install.h" #include "../install.h"
_get_backend() { _get_backend() {
rootfs="$(awk '$2 == "/" && $3 == "zfs" {print $1; exit 1}' /etc/mtab)" OIFS="$IFS"
[ -z "$rootfs" ] && return 1 IFS='
'
rootfses="$(awk '$2 ~ "^(/|/etc|/bin|/lib|/lib??|/libx32|/usr)$" && $3 == "zfs" {print $1}' /etc/mtab)"
[ -z "$rootfses" ] && IFS="$OIFS" && return 1
eroot="$(zfs get encryptionroot -Ho value "$rootfs")" eroots="$(zfs get encryptionroot -Ho value $rootfses | sort -u | grep -vFxe '' -e '-')"
[ -z "$eroot" ] || [ "$eroot" = "-" ] && return 1 [ -z "$eroots" ] && IFS="$OIFS" && return 1
backends="$(zfs-tpm-list -H $eroots | cut -f2 | sort -u)"
[ -z "$backends" ] && IFS="$OIFS" && return 1
backend="$(zfs-tpm-list -H "$eroot" | awk -F'\t' '{print $2}')" IFS="$OIFS"
[ -n "$backend" ] return 0
return
} }
_install_tpm2() { _install_tpm2() {
inst_binary zfs-tpm2-load-key inst_binary zfs-tpm2-load-key
# shellcheck disable=SC2046 inst_libdir_file 'libtss2-tcti*.so*'
inst_library $(find /usr/lib -name 'libtss2-tcti*.so*') # TODO: there's got to be a better way™! command -v tpm2_dictionarylockout > /dev/null && inst_binary tpm2_dictionarylockout
} }
_install_tpm1x() { _install_tpm1x() {
inst_binary zfs-tpm1x-load-key inst_binary zfs-tpm1x-load-key
INSTALL_TPM1X{inst_binary tcsd; inst_binary ip; inst_binary ss, initdir, inst_simple, inst_simple, inst_simple, inst_library} INSTALL_TPM1X{inst_binary tcsd; inst_binary ip; inst_binary ss, initdir, inst_simple, inst_simple, inst_simple, inst_libdir_file}
command -v tpm_resetdalock > /dev/null && inst_binary tpm_resetdalock command -v tpm_resetdalock > /dev/null && inst_binary tpm_resetdalock
} }
check() { check() {
command -v zfs-tpm-list > /dev/null || return 1 require_binaries zfs-tpm-list || return
# shellcheck disable=SC2154 # shellcheck disable=SC2154
if [ -n "$hostonly" ]; then if [ -n "$hostonly" ]; then
_get_backend || return _get_backend || return
[ "$backend" = "TPM2" ] && command -v zfs-tpm2-load-key > /dev/null && return 0 for backend in $backends; do
[ "$backend" = "TPM1.X" ] && command -v zfs-tpm1x-load-key > /dev/null && return 0 [ "$backend" = "TPM2" ] && command -v zfs-tpm2-load-key > /dev/null && return 0
[ "$backend" = "TPM1.X" ] && command -v zfs-tpm1x-load-key > /dev/null && return 0
done
return 1 return 1
fi fi
return 0 return 0
} }
@ -61,11 +68,13 @@ installkernel() {
install() { install() {
inst_binary zfs-tpm-list inst_binary zfs-tpm-list
if [ -n "${hostonly}" ]; then if [ -n "$hostonly" ]; then
_get_backend _get_backend
[ "$backend" = "TPM2" ] && _install_tpm2 for backend in $backends; do
[ "$backend" = "TPM1.X" ] && _install_tpm1x [ "$backend" = "TPM2" ] && _install_tpm2
[ "$backend" = "TPM1.X" ] && _install_tpm1x
done
else else
command -v zfs-tpm2-load-key > /dev/null && _install_tpm2 command -v zfs-tpm2-load-key > /dev/null && _install_tpm2
command -v zfs-tpm1x-load-key > /dev/null && _install_tpm1x command -v zfs-tpm1x-load-key > /dev/null && _install_tpm1x

View File

@ -3,62 +3,53 @@
#include "../mount.h" #include "../mount.h"
WITH_PROMPTABLE_TTY{< /dev/console > /dev/console 2>&1}
#include "../zfs-lib.sh.h"
# Only run on systemd systems, mimicking zfs-dracut's zfs-load-key.sh; TODO: "see mount-zfs.sh for non-systemd systems", confer README # Only run on systemd systems, mimicking zfs-dracut's zfs-load-key.sh; TODO: "see mount-zfs.sh for non-systemd systems", confer README
[ -d /run/systemd ] || exit 0 [ -d /run/systemd ] || return 0
. "/lib/dracut-lib.sh" . "/lib/dracut-lib.sh"
decode_root_args || return 0
# If root is not "ZFS=" or "zfs:", or rootfstype is not "zfs" then we aren't supposed to handle it # There is a race between the zpool import and the pre-mount hooks, so we wait for a pool to be imported
root="${root:=$(getarg root=)}" while ! systemctl is-active --quiet zfs-import.target; do
rootfstype="${rootfstype:=$(getarg rootfstype=)}" systemctl is-failed --quiet zfs-import-cache.service zfs-import-scan.service && return 1
[ "${root##zfs:}" = "$root" ] && [ "${root##ZFS=}" = "$root" ] && [ "$rootfstype" != "zfs" ] && exit 0 sleep 0.1s
done
BOOTFS="$root"
if [ "$BOOTFS" = "zfs:AUTO" ]; then
BOOTFS="$(zpool get -Ho value bootfs | grep -m1 -vFx -)"
fi
[ "$(zpool get -Ho value feature@encryption "${BOOTFS%%/*}")" = 'active' ] || return 0
TZPFMS_TPM1X="$(getarg TZPFMS_TPM1X=)"
[ -z "$TZPFMS_TPM1X" ] || export TZPFMS_TPM1X
getarg 0 quiet && quiet=y getarg 0 quiet && quiet=y
# There is a race between the zpool import and the pre-mount hooks, so we wait for a pool to be imported tzpfms_load() {
while [ "$(zpool list -H)" = "" ]; do set -- "$(zfs get -Ho value encryptionroot "$1")"
sleep 0.1s [ "$1" = "-" ] && return 0
systemctl is-failed --quiet zfs-import-cache.service zfs-import-scan.service && exit 1
done
# Match this sexion to i-t/zfs-patch.sh
if [ "$root" = "zfs:AUTO" ] ; then if command -v zfs-tpm2-load-key > /dev/null && [ -n "$(zfs-tpm-list -Hub TPM2 "$1")" ]; then
BOOTFS="$(zpool list -H -o bootfs | awk '!/^-$/ {print; exit}')" with_promptable_tty zfs-tpm2-load-key "$1"
else return
BOOTFS="${root##zfs:}"
BOOTFS="${BOOTFS##ZFS=}"
fi
WITH_PROMPTABLE_TTY{< /dev/console > /dev/console 2>&1}
# If pool encryption is active and the zfs command understands '-o encryption'
if [ "$(zpool list -H -o feature@encryption "$(echo "$BOOTFS" | awk -F/ '{print $1}')")" = "active" ]; then
ENCRYPTIONROOT="$(zfs get -H -o value encryptionroot "$BOOTFS")"
if ! [ "${ENCRYPTIONROOT}" = "-" ]; then
# Match this sexion to i-t/zfs-patch.sh
if command -v zfs-tpm2-load-key > /dev/null && ! [ "$(zfs-tpm-list -Hub TPM2 "$ENCRYPTIONROOT")" = "" ]; then
with_promptable_tty zfs-tpm2-load-key "$ENCRYPTIONROOT"
exit
fi
if command -v zfs-tpm1x-load-key > /dev/null && ! [ "$(zfs-tpm-list -Hub TPM1.X "$ENCRYPTIONROOT")" = "" ]; then
POTENTIALLY_START_TCSD{ss -ltO, > /dev/console 2>&1}
with_promptable_tty zfs-tpm1x-load-key "$ENCRYPTIONROOT"; err="$?"
POTENTIALLY_KILL_TCSD{}
exit "$err"
fi
# Fall through to zfs-dracut's zfs-load-key.sh
fi fi
fi
if command -v zfs-tpm1x-load-key > /dev/null && [ -n "$(zfs-tpm-list -Hub TPM1.X "$1")" ]; then
POTENTIALLY_START_TCSD{ss -ltO, > /dev/console 2>&1}
with_promptable_tty zfs-tpm1x-load-key "$1"; err="$?"
POTENTIALLY_KILL_TCSD{}
return "$err"
fi
}
tzpfms_load "$BOOTFS"
for_relevant_root_children "$BOOTFS" tzpfms_load

View File

@ -5,8 +5,7 @@
#include "../install.h" #include "../install.h"
PREREQ="zfs" [ "$1" = "prereqs" ] && { echo zfs; exit; }
[ "$1" = "prereqs" ] && exec echo "$PREREQ"
. /usr/share/initramfs-tools/hook-functions . /usr/share/initramfs-tools/hook-functions
@ -14,12 +13,11 @@ PREREQ="zfs"
# which should be compatible with other hooks doing the same thing # which should be compatible with other hooks doing the same thing
[ "${verbose:-n}" = "y" ] && echo "Patching /scripts/zfs" [ "${verbose:-n}" = "y" ] && echo "Patching /scripts/zfs"
sed -Ei 's/^decrypt_fs\(\)/__tzpfms__&/' "$DESTDIR/scripts/zfs" sed -Ei 's/^decrypt_fs\(\)/__tzpfms__&/' "$DESTDIR/scripts/zfs"
cat /usr/share/tzpfms/initramfs-tools-zfs-patch.sh >> "$DESTDIR/scripts/zfs" cat /usr/share/tzpfms/initramfs-tools-zfs-patch.sh >> "$DESTDIR/scripts/zfs"
for x in zfs-tpm-list zfs-tpm2-load-key zfs-tpm1x-load-key tpm_resetdalock tcsd $(find /usr/lib -name 'libtss2-tcti*.so*'); do # TODO: there's got to be a better way™! for x in zfs-tpm-list zfs-tpm2-load-key tpm2_dictionarylockout zfs-tpm1x-load-key tpm_resetdalock tcsd $(find /usr/lib -name 'libtss2-tcti*.so*'); do # TODO: there's got to be a better way™!
xloc="$(command -v "$x")" xloc="$(command -v "$x")" && copy_exec "$xloc"
[ "$xloc" = "" ] || copy_exec "$xloc"
done done
INSTALL_TPM1X{, DESTDIR, copy_file rule, copy_file config, copy_file state, copy_exec} INSTALL_TPM1X{, DESTDIR, copy_file rule, copy_file config, copy_file state, copy_exec}

View File

@ -17,17 +17,17 @@ decrypt_fs() {
# First three lines borrowed from /scripts/zfs#decrypt_fs() # First three lines borrowed from /scripts/zfs#decrypt_fs()
# If pool encryption is active and the zfs command understands '-o encryption' # If pool encryption is active and the zfs command understands '-o encryption'
if [ "$(zpool list -H -o feature@encryption "$(echo "$fs" | awk -F/ '{print $1}')")" = "active" ]; then if [ "$(zpool list -H -o feature@encryption "${fs%%/*}")" = "active" ]; then
ENCRYPTIONROOT="$(get_fs_value "$fs" encryptionroot)" ENCRYPTIONROOT="$(get_fs_value "$fs" encryptionroot)"
if ! [ "$ENCRYPTIONROOT" = "-" ]; then if ! [ "$ENCRYPTIONROOT" = "-" ]; then
# Match this sexion to dracut/tzpfms-load-key.sh # Match this sexion to dracut/tzpfms-load-key.sh
if command -v zfs-tpm2-load-key > /dev/null && ! [ "$(zfs-tpm-list -Hub TPM2 "$ENCRYPTIONROOT")" = "" ]; then if command -v zfs-tpm2-load-key > /dev/null && [ -n "$(zfs-tpm-list -Hub TPM2 "$ENCRYPTIONROOT")" ]; then
with_promptable_tty zfs-tpm2-load-key "$ENCRYPTIONROOT" with_promptable_tty zfs-tpm2-load-key "$ENCRYPTIONROOT"
return return
fi fi
if command -v zfs-tpm1x-load-key > /dev/null && ! [ "$(zfs-tpm-list -Hub TPM1.X "$ENCRYPTIONROOT")" = "" ]; then if command -v zfs-tpm1x-load-key > /dev/null && [ -n "$(zfs-tpm-list -Hub TPM1.X "$ENCRYPTIONROOT")" ]; then
POTENTIALLY_START_TCSD{netstat -lt, } POTENTIALLY_START_TCSD{netstat -lt, }
with_promptable_tty zfs-tpm1x-load-key "$ENCRYPTIONROOT"; err="$?" with_promptable_tty zfs-tpm1x-load-key "$ENCRYPTIONROOT"; err="$?"
POTENTIALLY_KILL_TCSD{} POTENTIALLY_KILL_TCSD{}

View File

@ -6,8 +6,8 @@
PREREQ PREREQ
# tcsd exits if it doesn't have tss group and user # tcsd exits if it doesn't have tss group and user
grep tss /etc/group >> "${TARGET_DIR:-MUST_EXIST}/etc/group" grep tss /etc/group >> "${TARGET_DIR:?must exist}/etc/group"
grep tss /etc/passwd >> "${TARGET_DIR:-MUST_EXIST}/etc/passwd" grep tss /etc/passwd >> "${TARGET_DIR:?must exist}/etc/passwd"
for f in /lib/udev/rules.d/*tpm*; do for f in /lib/udev/rules.d/*tpm*; do
INST_UDEV "$f" INST_UDEV "$f"
@ -15,13 +15,13 @@
if [ -e /etc/tcsd.conf ]; then if [ -e /etc/tcsd.conf ]; then
INST_CFG /etc/tcsd.conf INST_CFG /etc/tcsd.conf
chown tss:tss "${TARGET_DIR:-MUST_EXIST}/etc/tcsd.conf" chown root:tss "${TARGET_DIR:?must exist}/etc/tcsd.conf"
system_ps_file="$(awk -F '[[:space:]]*=[[:space:]]*' '!/^[[:space:]]*#/ && !/^$/ && $1 ~ /system_ps_file$/ {gsub(/[[:space:]]*$/, "", $2); print $2}' /etc/tcsd.conf)" system_ps_file="$(awk -F '[[:space:]]*=[[:space:]]*' '!/^[[:space:]]*#/ && !/^$/ && $1 ~ /system_ps_file$/ {gsub(/[[:space:]]*$/, "", $2); print $2}' /etc/tcsd.conf)"
system_ps_file="${system_ps_file:-/var/lib/tpm/system.data}" system_ps_file="${system_ps_file:-/var/lib/tpm/system.data}"
fi fi
# tcsd can't find SRK if state not present, refuses to start if it doesn't have parent to tss dir # tcsd can't find SRK if state not present, refuses to start if it doesn't have parent to tss dir
mkdir -p "${TARGET_DIR:-MUST_EXIST}/$(dirname "$(dirname "$system_ps_file")")" mkdir -p "${TARGET_DIR:?must exist}/$(dirname "$(dirname "$system_ps_file")")"
[ -f "$system_ps_file" ] && INST_STATE "$system_ps_file" [ -f "$system_ps_file" ] && INST_STATE "$system_ps_file"
} }
@ -32,7 +32,7 @@
if [ -e /etc/nsswitch.conf ]; then if [ -e /etc/nsswitch.conf ]; then
INST_CFG /etc/nsswitch.conf INST_CFG /etc/nsswitch.conf
databases="$(awk '/^group|hosts/ {for(i = 2; i <= NF; ++i) print $i}' /etc/nsswitch.conf | sort | uniq)" databases="$(awk '/^(group|hosts)/ {for(i = 2; i <= NF; ++i) if($i !~ /[^a-z0-9_-]/) db[$i]=0} END {for(d in db) print d}' /etc/nsswitch.conf)"
for db in $databases; do for db in $databases; do
for f in /lib/*/"libnss_$db"*; do for f in /lib/*/"libnss_$db"*; do
INST_LIB "$f" INST_LIB "$f"

View File

@ -4,28 +4,26 @@
#define WITH_PROMPTABLE_TTY(REDIREXIONS) #define WITH_PROMPTABLE_TTY(REDIREXIONS)
# This sucks a lot of ass, since we don't know the questions or the amount thereof beforehand # This sucks a lot of ass, since we don't know the questions or the amount thereof beforehand
# (0-2 (owner hierarchy/ownership + sealed object, both optional) best-case and 0-6 worst-case (both entered wrong twice)). # (0-2 (owner hierarchy/ownership + sealed object, both optional) best-case and 0-6 worst-case (both entered wrong twice)).
# Plymouth doesn't allow us to actually check what the splash status was, and ioctl(KDGETMODE) isn't reliable;
# ideally, we'd only clear the screen if we were making the switch, but not if the user was already switched to the log output.
# Instead, clear if there's a "quiet", leave alone otherwise, and always restore;
# cmdline option "plymouth.ignore-show-splash" can be used to disable splashes altogether, if desired.
with_promptable_tty() { with_promptable_tty() {
if command -v plymouth > /dev/null && plymouth --ping; then if plymouth --ping 2>/dev/null; then
plymouth hide-splash # shellcheck disable=SC2016
# shellcheck disable=SC2217 TZPFMS_PASSPHRASE_HELPER='exec plymouth ask-for-password --prompt="$1: "' "$@" 2>/run/tzpfms-err; ret="$?"
[ "${quiet:-n}" = "y" ] && printf '\033c' REDIREXIONS [ -s /run/tzpfms-err ] && plymouth display-message --text="$(cat /run/tzpfms-err)"
elif [ -e /run/systemd/system ] && command -v systemd-ask-password > /dev/null; then # --no-tty matches zfs and actually works
"$@" REDIREXIONS; ret="$?" # shellcheck disable=SC2016
TZPFMS_PASSPHRASE_HELPER='exec systemd-ask-password --no-tty --id="tzpfms:$2" "$1:"' "$@" 2>/run/tzpfms-err; ret="$?"
plymouth show-splash
else else
# Mimic /scripts/zfs#decrypt_fs(): setting "printk" temporarily to "7" will allow prompt even if kernel option "quiet" # Mimic /scripts/zfs#decrypt_fs(): setting "printk" temporarily to "7" will allow prompt even if kernel option "quiet"
printk="$(awk '{print $1}' /proc/sys/kernel/printk)" read -r printk _ < /proc/sys/kernel/printk
[ "$printk" = "7" ] || echo 7 > /proc/sys/kernel/printk [ "$printk" = "7" ] || echo 7 > /proc/sys/kernel/printk
"$@" REDIREXIONS; ret="$?" TZPFMS_PASSPHRASE_HELPER="${TZPFMS_PASSPHRASE_HELPER:-}" "$@" REDIREXIONS; ret="$?" # allow overriding in cmdline, but always set to raze default
[ "$printk" = "7" ] || echo "$printk" > /proc/sys/kernel/printk [ "$printk" = "7" ] || echo "$printk" > /proc/sys/kernel/printk
fi fi
[ -s /run/tzpfms-err ] && cat /run/tzpfms-err >&2
[ -s /run/tzpfms-err ] && [ "$ret" -ne 0 ] && sed 's;^;'"$1"': ;' /run/tzpfms-err >> /dev/kmsg
rm -f /run/tzpfms-err
return "$ret" return "$ret"
} }
#endefine #endefine

85
initrd/zfs-lib.sh.h Normal file
View File

@ -0,0 +1,85 @@
# SPDX-License-Identifier: 0BSD
# dbda45160ffa43e5ecf0498a609230f1afee7b3f (zfs-2.2.99-270-gdbda45160)
# for_relevant_root_children DATASET EXEC
# Runs "EXEC dataset mountpoint" for all children of DATASET that are needed for system bringup
# Used by zfs-nonroot-necessities.service and friends, too!
for_relevant_root_children() {
dataset="${1}"
exec="${2}"
zfs list -t filesystem -Ho name,mountpoint,canmount -r "${dataset}" |
(
_ret=0
while IFS=" " read -r dataset mountpoint canmount; do
[ "$canmount" != "on" ] && continue
case "$mountpoint" in
/etc|/bin|/lib|/lib??|/libx32|/usr)
# If these aren't mounted we may not be able to get to the real init at all, or pollute the dataset holding the rootfs
"${exec}" "${dataset}" "${mountpoint}" || _ret=$?
;;
*)
# Up to the real init to remount everything else it might need
;;
esac
done
exit "${_ret}"
)
}
# Parse root=, rootfstype=, return them decoded and normalised to zfs:AUTO for auto, plain dset for explicit
#
# True if ZFS-on-root, false if we shouldn't
#
# Supported values:
# root=
# root=zfs
# root=zfs:
# root=zfs:AUTO
#
# root=ZFS=data/set
# root=zfs:data/set
# root=zfs:ZFS=data/set (as a side-effect; allowed but undocumented)
#
# rootfstype=zfs AND root=data/set <=> root=data/set
# rootfstype=zfs AND root= <=> root=zfs:AUTO
#
# '+'es in explicit dataset decoded to ' 's.
decode_root_args() {
if [ -n "$rootfstype" ]; then
[ "$rootfstype" = zfs ]
return
fi
xroot=$(getarg root=)
rootfstype=$(getarg rootfstype=)
# shellcheck disable=SC2249
case "$xroot" in
""|zfs|zfs:|zfs:AUTO)
root=zfs:AUTO
rootfstype=zfs
return 0
;;
ZFS=*|zfs:*)
root="${xroot#zfs:}"
root="${root#ZFS=}"
root=$(echo "$root" | tr '+' ' ')
rootfstype=zfs
return 0
;;
esac
if [ "$rootfstype" = "zfs" ]; then
case "$xroot" in
"") root=zfs:AUTO ;;
*) root=$(echo "$xroot" | tr '+' ' ') ;;
esac
return 0
fi
return 1
}

View File

@ -1,15 +1,31 @@
## TPM1.X back-end configuration .\" SPDX-License-Identifier: MIT
.
### TPM selection .Sh TPM1.X back-end configuration
.Ss TPM selection
The tzpfms suite connects to a local tcsd(8) process (at `localhost:30003`) by default. The
Use the environment variable `TZPFMS_TPM1X` to specify a remote TCS hostname. .Nm tzpfms
suite connects to a local
The TrouSerS tcsd(8) daemon will try `/dev/tpm0`, then `/udev/tpm0`, then `/dev/tpm`; .Xr tcsd 8
process
.Pq at Pa localhost:30003
by default.
Use the environment variable
.Ev TZPFMS_TPM1X
to specify a remote TCS hostname.
.Pp
The TrouSerS
.Xr tcsd 8
daemon will try
.Pa /dev/tpm0 ,
then
.Pa /udev/tpm0 ,
then
.Pa /dev/tpm ;
by occupying one of the earlier ones with, for example, shell redirection, a later one can be selected. by occupying one of the earlier ones with, for example, shell redirection, a later one can be selected.
.
### See also .Ss See also
The TrouSerS project page at
The TrouSerS project page at <https://sourceforge.net/projects/trousers>. .Lk https:/\&/sourceforge.net/projects/trousers .
.Pp
The TPM 1.2 main specification index at &lt;<https://trustedcomputinggroup.org/resource/tpm-main-specification>&gt;. The TPM 1.2 main specification index at
.Lk https:/\&/trustedcomputinggroup.org/resource/tpm-main-specification .

View File

@ -1,17 +1,36 @@
## TPM2 back-end configuration .\" SPDX-License-Identifier: MIT
.
### Environment variables .Sh TPM2 back-end configuration
.Ss Environment variables
* `TSS2_LOG`=: .Bl -tag -compact -width ".Ev TSS2_LOG"
Any of: *NONE*, *ERROR*, *WARNING*, *INFO*, *DEBUG*, *TRACE*. Default: *WARNING*. .It Ev TSS2_LOG
Any of:
### TPM selection .Sy NONE , ERROR , WARNING , INFO , DEBUG , TRACE .
Default:
The library `libtss2-tcti-default.so` can be linked to any of the `libtss2-tcti-*.so` libraries to select the default, .Sy WARNING .
otherwise `/dev/tpmrm0`, then `/dev/tpm0`, then `localhost:2321` will be tried, in order (see ESYS_CONTEXT(3)). .El
.
### See also .Ss TPM selection
The library
The tpm2-tss git repository at <https://github.com/tpm2-software/tpm2-tss> and the documentation at <https://tpm2-tss.readthedocs.io>. .Nm libtss2-tcti-default.so
can be linked to any of the
The TPM 2.0 specifications, mainly at &lt;<https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf>&gt; and related pages. .Pa libtss2-tcti-*.so
libraries to select the default, otherwise
.Pa /dev/tpmrm0 ,
then
.Pa /dev/tpm0 ,
then
.Pa localhost:2321
will be tried, in order
.Pq see Xr ESYS_CONTEXT 3 .
.
.Ss See also
The tpm2-tss git repository at
.Lk https:/\&/github.com/tpm2-software/tpm2-tss
and the documentation at
.Lk https:/\&/tpm2-tss.readthedocs.io .
.Pp
The TPM 2.0 specifications, mainly at
.Lk https:/\&/trustedcomputinggroup.org/resource/tpm-library-specification/ ,
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf ,
and related pages.

View File

@ -1,16 +1,21 @@
## AUTHOR .\" SPDX-License-Identifier: MIT
.
Written by наб &lt;<nabijaczleweli@nabijaczleweli.xyz>&gt; .Sh SPECIAL THANKS
## SPECIAL THANKS
To all who support further development, in particular: To all who support further development, in particular:
.Bl -bullet -offset 4n -compact -width "@"
* ThePhD .It
* Embark Studios ThePhD
.It
## REPORTING BUGS Embark Studios
.It
&lt;<https://todo.sr.ht/~nabijaczleweli/tzpfms>&gt; Jasper Bekkers
.It
&lt;<mailto:~nabijaczleweli/tzpfms@lists.sr.ht>&gt;, archived at &lt;<https://lists.sr.ht/~nabijaczleweli/tzpfms>&gt; EvModder
.El
.
.Sh REPORTING BUGS
.Lk https:/\&/todo.sr.ht/\(tinabijaczleweli/tzpfms
.Pp
.Mt \(tinabijaczleweli/tzpfms@lists.sr.ht ,
archived at
.Lk https:/\&/lists.sr.ht/\(tinabijaczleweli/tzpfms .

View File

@ -1,13 +0,0 @@
zfs-tpm2-change-key(8) zfs-tpm2-change-key.8.ronn
zfs-tpm2-load-key(8) zfs-tpm2-load-key.8.ronn
zfs-tpm2-clear-key(8) zfs-tpm2-clear-key.8.ronn
zfs-tpm1x-change-key(8) zfs-tpm1x-change-key.8.ronn
zfs-tpm1x-load-key(8) zfs-tpm1x-load-key.8.ronn
zfs-tpm1x-clear-key(8) zfs-tpm1x-clear-key.8.ronn
zfs-tpm-list(8) zfs-tpm-list.8.ronn
zfs(8) https://manpages.debian.org/bullseye/zfsutils-linux/zfs.8.en.html
tcsd(8) https://manpages.debian.org/bullseye/trousers/tcsd.8.en.html
tpm2_unseal(1) https://manpages.debian.org/bullseye/tpm2-tools/tpm2_unseal.1.en.html
ESYS_CONTEXT(3) https://www.mankier.com/3/ESYS_CONTEXT

36
man/passphrase.h Normal file
View File

@ -0,0 +1,36 @@
.\" SPDX-License-Identifier: MIT
.
.Sh ENVIRONMENT VARIABLES
.Bl -tag -compact -width 4n
.It Ev TZPFMS_PASSPHRASE_HELPER
By default, passphrases are prompted for and read in on the standard output and input streams.
If
.Ev TZPFMS_PASSPHRASE_HELPER
is set and nonempty, it will be run via
.Pa /bin/ Ns Nm sh Fl c
to provide each passphrase, instead.
.Pp
The standard output stream of the helper is tied to an anonymous file and used in its entirety as the passphrase, except for a trailing new-line, if any.
The arguments are:
.Bl -tag -compact -offset 2n -width ".Li $1"
.It Li $1
Pre-formatted noun phrase with all the information below, for use as a prompt
.\" Passphrase for tarta-zoot
.\" New passphrase for tarta-zoot (again)
.It Li $2
Either the dataset name or the element of the TPM hierarchy being prompted for
.It Li $3
.Qq new
if this is for a new passphrase, otherwise blank
.It Li $4
.Qq again
if it's the second prompt for that passphrase, otherwise blank
.El
.Pp
If the helper doesn't exist
.Pq the shell exits with Sy 127 ,
a diagnostic is issued and the normal prompt is used as fall-back.
If it fails for any other reason, the prompting is aborted.
.
TZPFMS_PASSPHRASE_HELPER_MAN{}
.El

297
man/style.css Normal file
View File

@ -0,0 +1,297 @@
/* $OpenBSD: mandoc.css,v 1.33 2019/06/02 16:50:46 schwarze Exp $ */
/*
* Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
*
* Written by Ingo Schwarze <schwarze@openbsd.org>.
* I place this file into the public domain.
* Permission to use, copy, modify, and distribute it for any purpose
* with or without fee is hereby granted, without any conditions.
*/
/* Tooltips removed. */
/* Global defaults. */
html { max-width: 65em;
--bg: #FFFFFF;
--fg: #000000; }
body { background: var(--bg);
color: var(--fg);
font-family: Helvetica,Arial,sans-serif; }
h1 { font-size: 110%; }
table { margin-top: 0em;
margin-bottom: 0em;
border-collapse: collapse; }
/* Some browsers set border-color in a browser style for tbody,
* but not for table, resulting in inconsistent border styling. */
tbody { border-color: inherit; }
tr { border-color: inherit; }
td { vertical-align: top;
padding-left: 0.2em;
padding-right: 0.2em;
border-color: inherit; }
ul, ol, dl { margin-top: 0em;
margin-bottom: 0em; }
li, dt { margin-top: 1em; }
.permalink { border-bottom: thin dotted;
color: inherit;
font: inherit;
text-decoration: inherit; }
* { clear: both }
/* Search form and search results. */
fieldset { border: thin solid silver;
border-radius: 1em;
text-align: center; }
input[name=expr] {
width: 25%; }
table.results { margin-top: 1em;
margin-left: 2em;
font-size: smaller; }
/* Header and footer lines. */
table.head { width: 100%;
border-bottom: 1px dotted #808080;
margin-bottom: 1em;
font-size: smaller; }
td.head-vol { text-align: center; }
td.head-rtitle {
text-align: right; }
table.foot { width: 100%;
border-top: 1px dotted #808080;
margin-top: 1em;
font-size: smaller; }
td.foot-os { text-align: right; }
/* Sections and paragraphs. */
.manual-text {
margin-left: 3.8em; }
.Nd { }
section.Sh { }
h1.Sh { margin-top: 1.2em;
margin-bottom: 0.6em;
margin-left: -3.2em; }
section.Ss { }
h2.Ss { margin-top: 1.2em;
margin-bottom: 0.6em;
margin-left: -1.2em;
font-size: 105%; }
.Pp { margin: 0.6em 0em; }
.Sx { }
.Xr { }
/* Displays and lists. */
.Bd { }
.Bd-indent { margin-left: 3.8em; }
.Bl-bullet { list-style-type: disc;
padding-left: 1em; }
.Bl-bullet > li { }
.Bl-dash { list-style-type: none;
padding-left: 0em; }
.Bl-dash > li:before {
content: "\2014 "; }
.Bl-item { list-style-type: none;
padding-left: 0em; }
.Bl-item > li { }
.Bl-compact > li {
margin-top: 0em; }
.Bl-enum { padding-left: 2em; }
.Bl-enum > li { }
.Bl-compact > li {
margin-top: 0em; }
.Bl-diag { }
.Bl-diag > dt {
font-style: normal;
font-weight: bold; }
.Bl-diag > dd {
margin-left: 0em; }
.Bl-hang { }
.Bl-hang > dt { }
.Bl-hang > dd {
margin-left: 5.5em; }
.Bl-inset { }
.Bl-inset > dt { }
.Bl-inset > dd {
margin-left: 0em; }
.Bl-ohang { }
.Bl-ohang > dt { }
.Bl-ohang > dd {
margin-left: 0em; }
.Bl-tag { margin-top: 0.6em;
margin-left: 5.5em; }
.Bl-tag > dt {
float: left;
margin-top: 0em;
margin-left: -5.5em;
padding-right: 0.5em;
vertical-align: top; }
.Bl-tag > dd {
clear: right;
column-count: 1; /* Force block formatting context. */
width: 100%;
margin-top: 0em;
margin-left: 0em;
margin-bottom: 0.6em;
vertical-align: top; }
.Bl-compact { margin-top: 0em; }
.Bl-compact > dd {
margin-bottom: 0em; }
.Bl-compact > dt {
margin-top: 0em; }
.Bl-column { }
.Bl-column > tbody > tr { }
.Bl-column > tbody > tr > td {
margin-top: 1em; }
.Bl-compact > tbody > tr > td {
margin-top: 0em; }
.Rs { font-style: normal;
font-weight: normal; }
.RsA { }
.RsB { font-style: italic;
font-weight: normal; }
.RsC { }
.RsD { }
.RsI { font-style: italic;
font-weight: normal; }
.RsJ { font-style: italic;
font-weight: normal; }
.RsN { }
.RsO { }
.RsP { }
.RsQ { }
.RsR { }
.RsT { text-decoration: underline; }
.RsU { }
.RsV { }
.eqn { }
.tbl td { vertical-align: middle; }
.HP { margin-left: 3.8em;
text-indent: -3.8em; }
/* Semantic markup for command line utilities. */
table.Nm { }
code.Nm { font-style: normal;
font-weight: bold;
font-family: monospace; }
.Fl { font-style: normal;
font-weight: bold;
font-family: monospace; }
.Cm { font-style: normal;
font-weight: bold;
font-family: monospace; }
.Ar { font-style: italic;
font-weight: normal;
font-family: monospace; }
.Op { display: inline; }
.Ic { font-style: normal;
font-weight: bold;
font-family: monospace; }
.Ev { font-style: normal;
font-weight: normal;
font-family: monospace; }
.Pa { font-style: italic;
font-weight: normal; }
/* Semantic markup for function libraries. */
.Lb { }
code.In { font-style: normal;
font-weight: bold;
font-family: inherit; }
a.In { }
.Fd { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Ft { font-style: italic;
font-weight: normal; }
.Fn { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Fa { font-style: italic;
font-weight: normal; }
.Vt { font-style: italic;
font-weight: normal; }
.Va { font-style: italic;
font-weight: normal; }
.Dv { font-style: normal;
font-weight: normal;
font-family: monospace; }
.Er { font-style: normal;
font-weight: normal;
font-family: monospace; }
/* Various semantic markup. */
.An { }
.Lk { }
.Mt { }
.Cd { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Ad { font-style: italic;
font-weight: normal; }
.Ms { font-style: normal;
font-weight: bold; }
.St { }
.Ux { }
/* Physical markup. */
.Bf { display: inline; }
.No { font-style: normal;
font-weight: normal; }
.Em { font-style: italic;
font-weight: normal; }
.Sy { font-style: normal;
font-weight: bold; }
.Li { font-style: normal;
font-weight: normal;
font-family: monospace; }
/* Tooltip support. */
h1.Sh, h2.Ss { position: relative; }
.Li, .An, .Ar, .Cd, .Cm, .Dv, .Em, .Er, .Ev, .Fa, .Fd, .Fl, .Fn, .Ft,
.Ic, code.In, .Lb, .Lk, .Ms, .Mt, .Nd, code.Nm, .Pa, .Rs,
.St, .Sx, .Sy, .Va, .Vt, .Xr {
display: inline-block;
position: relative; }
/* Overrides to avoid excessive margins on small devices. */
@media (max-width: 37.5em) {
.manual-text {
margin-left: 0.5em; }
h1.Sh, h2.Ss { margin-left: 0em; }
.Bd-indent { margin-left: 2em; }
.Bl-hang > dd {
margin-left: 2em; }
.Bl-tag { margin-left: 2em; }
.Bl-tag > dt {
margin-left: -2em; }
.HP { margin-left: 2em;
text-indent: -2em; }
}
/* Overrides for a dark color scheme for accessibility. */
@media (prefers-color-scheme: dark) {
html { --bg: #1E1F21;
--fg: #EEEFF1; }
:link { color: #BAD7FF; }
:visited { color: #F6BAFF; }
}

115
man/zfs-tpm-list.8.pp Normal file
View File

@ -0,0 +1,115 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM-LIST 8
.Os
.
.Sh NAME
.Nm zfs-tpm-list
.Nd print dataset tzpfms metadata
.Sh SYNOPSIS
.Nm
.Op Fl H
.Op Fl r Ns \&| Ns Fl d Ar depth
.Op Fl a Ns \&| Ns Fl b Ar back-end
.Op Fl u Ns \&| Ns Fl l
.Oo Ar filesystem Ns \&| Ns Ar volume Oc Ns
.
.Sh DESCRIPTION
Lists the following properties on encryption roots:
.Bl -tag -compact -offset 4n -width ".Li keystatus"
.It Li name
.It Li back-end
the
.Nm tzpfms
back-end
.Pq e.g. Sy TPM2 No for Xr zfs-tpm2-change-key 8 or Sy TPM1.X No for Xr zfs-tpm1x-change-key 8 ,
or
.Qq Sy -
if none is configured
.It Li keystatus
.Sy available
or
.Sy unavailable
.It Li coherent
.Sy yes
if either both
.Li xyz.nabijaczleweli:tzpfms.backend
and
.Li xyz.nabijaczleweli:tzpfms.key
are present or missing,
.Sy no
otherwise
.El
.Pp
Incoherent datasets require immediate operator attention, with either the appropriate
.Nm zfs-tpm*-clear-key
program or
.Nm zfs Cm change-key
and
.Nm zfs Cm inherit
\(em if the key becomes unloaded, they will require restoration from back-up.
However, this should never occur, unless something went horribly wrong with the dataset properties.
.Pp
If no datasets are specified, all matching encryption roots are listed \(em by default, those managed by
.Nm tzpfms .
.
.Sh OPTIONS
.Bl -tag -compact -width ".Fl b Ar back-end"
.It Fl H
Scripting mode \(em remove headers and separate fields by a single tab instead of columnating them with spaces.
.Pp
.It Fl r
Recurse into all descendants of specified datasets.
.It Fl d Ar depth
Recurse at most
.Ar depth
datasets deep.
Default:
.Sy 0 .
.Pp
.It Fl a
List all encryption roots, even ones not managed by
.Nm tzpfms .
.It Fl b Ar back-end
List only encryption roots with the specified
.Nm tzpfms
.Ar back-end .
.Pp
.It Fl u
List only encryption roots whose keys are unavailable.
.It Fl l
List only encryption roots whose keys are available.
.El
.
.Sh EXAMPLES
.Bd -literal -compact
.Li $ Nm
NAME BACK-END KEYSTATUS COHERENT
tarta-zoot TPM1.X available yes
tarta-zoot/home TPM2 unavailable yes
.Li $ Nm Fl ad0
NAME BACK-END KEYSTATUS COHERENT
filling - available yes
.Li $ Nm Fl b Sy TPM2
NAME BACK-END KEYSTATUS COHERENT
tarta-zoot/home TPM2 unavailable yes
.Li $ Nm Fl ra Ar tarta-zoot
NAME BACK-END KEYSTATUS COHERENT
tarta-zoot TPM1.X available yes
tarta-zoot/home TPM2 unavailable yes
tarta-zoot/bkp - available yes
tarta-zoot/vm - available yes
.Li $ Nm Fl al
NAME BACK-END KEYSTATUS COHERENT
filling - available yes
tarta-zoot TPM1.X available yes
tarta-zoot/bkp - available yes
tarta-zoot/vm - available yes
.Ed
.
#include "common.h"

View File

@ -1,80 +0,0 @@
zfs-tpm-list(8) -- print dataset tzpfms metadata
================================================
## SYNOPSIS
`zfs-tpm-list` [-H] [-r\|-d *depth*] [-a\|-b *back-end*] [-u\|-l] [*filesystem*\|*volume*]
## DESCRIPTION
zfs-tpm-list(8) lists the following properties on encryption roots:
* `name`,
* `back-end`: the tzpfms back-end (e.g. "TPM2" for zfs-tpm2-change-key(8) or "TPM1.X" for zfs-tpm1x-change-key(8)),
or "-" if none is configured,
* `keystatus`: "available" or "unavailable",
* `coherent`: "yes" if either both `xyz.nabijaczleweli:tzpfms.backend` and `xyz.nabijaczleweli:tzpfms.key` are present or missing, "no" otherwise.
Incoherent datasets require immediate operator attention, with either the appropriate zfs-tpm\*-clear-key program or zfs(8) change-key and zfs(8) inherit
if the key becomes unloaded, they will require restoration from back-up.
However, they should never occur, unless something went terribly wrong with the dataset properties.
If no datasets are specified, lists all matching encryption roots.
The default filter is to list all roots managed by tzpfms.
The `-a` and `-b` [OPTIONS]() can be used to either list all roots or only ones backed by a particular end, respectively.
## OPTIONS
* `-H`:
Used for scripting mode. Do not print headers and separate fields by a single tab instead of arbitrary white space.
* `-r`:
Recurse into all descendant datasets. Default if no datasets listed on the command-line.
* `-d` *depth*:
Recurse at most *depth* datasets deep. Defaults to zero if datasets were listed on the command-line.
* `-a`:
List all encryption roots, even ones not managed by tzpfms.
* `-b` *back-end*:
List only encryption roots with tzpfms back-end *back-end*.
* `-l`:
List only encryption roots whose keys are available.
* `-u`:
List only encryption roots whose keys are unavailable.
## EXAMPLES
$ zfs-tpm-list
NAME BACK-END KEYSTATUS COHERENT
owo/venc TPM2 unavailable yes
owo/enc TPM1.X available yes
$ zfs-tpm-list -ad0
NAME BACK-END KEYSTATUS COHERENT
awa - available yes
$ zfs-tpm-list -b TPM2
NAME BACK-END KEYSTATUS COHERENT
owo/venc TPM2 unavailable yes
$ zfs-tpm-list -ra owo
NAME BACK-END KEYSTATUS COHERENT
owo/venc TPM2 unavailable yes
owo/vtnc - available yes
owo/v nc - available yes
owo/enc TPM1.X available yes
$ zfs-tpm-list -al
NAME BACK-END KEYSTATUS COHERENT
awa - available yes
owo/vtnc - available yes
owo/v nc - available yes
owo/enc TPM1.X available yes
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

View File

@ -0,0 +1,125 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM1X-CHANGE-KEY 8
.Os
.
.Sh NAME
.Nm zfs-tpm1x-change-key
.Nd change ZFS dataset key to one stored on the TPM
.Sh SYNOPSIS
.Nm
.Op Fl b Ar backup-file
.Op Fl P Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns
.Ar dataset
.
.Sh DESCRIPTION
To normalise the
.Ar dataset ,
.Nm
will open its encryption root in its stead.
.Nm
will
.Em never
create or destroy encryption roots; use
.Xr zfs-change-key 8
for that.
.Pp
First, a connection is made to the TPM, which
.Em must
be TPM-1.X-compatible.
.Pp
If
.Ar dataset
was previously encrypted with
.Nm tzpfms
and the
.Sy TPM1.X
back-end was used, the metadata will be silently cleared.
Otherwise, or in case of an error, data required for manual intervention will be written to the standard error stream.
.Pp
Next, a new wrapping key is generated on the TPM, optionally backed up
.Pq see Sx OPTIONS ,
and sealed on the TPM;
the user is prompted for an optional passphrase to protect the key with,
and for the SRK passphrase, set when taking ownership, if not "well-known" (all zeroes).
.Pp
The following properties are set on
.Ar dataset :
.Bl -bullet -compact -offset 4n -width "@"
.It
.Li xyz.nabijaczleweli:tzpfms.backend Ns = Ns Sy TPM1.X
.It
.Li xyz.nabijaczleweli:tzpfms.key Ns = Ns Ar parent-key-blob Ns Cm \&: Ns Ar sealed-object-blob
.El
.Pp
.Li tzpfms.backend
identifies this dataset for work with
.Sy TPM1.X Ns -back-ended
.Nm tzpfms
programs
.Pq namely Xr zfs-tpm1x-change-key 8 , Xr zfs-tpm1x-load-key 8 , and Xr zfs-tpm1x-clear-key 8 .
.Pp
.Li tzpfms.key
is a colon-separated pair of hexadecimal-string (i.e. "4F7730" for "Ow0") blobs;
the first one represents the RSA key protecting the blob,
and it is protected with either the passphrase, if provided, or the SHA1 constant
.Li CE4CF677875B5EB8993591D5A9AF1ED24A3A8736 ;
the second represents the sealed object containing the wrapping key,
and is protected with the SHA1 constant
.Li B9EE715DBE4B243FAA81EA04306E063710383E35 .
There exists no other user-land tool for decrypting this; perhaps there should be.
.\"" TODO: make an LD_PRELOADable for extracting the key maybe?
.Pp
Finally, the equivalent of
.Nm zfs Cm change-key Fl o Li keylocation=prompt Fl o Li keyformat=raw Ar dataset
is performed with the new key.
If an error occurred, best effort is made to clean up the properties,
or to issue a note for manual intervention into the standard error stream.
.Pp
A final verification should be made by running
.Nm zfs-tpm1x-load-key Fl n Ar dataset .
If that command succeeds, all is well,
but otherwise the dataset can be manually rolled back to a passphrase with
.Nm zfs-tpm1x-clear-key Ar dataset
.Pq or, if that fails to work, Nm zfs Cm change-key Fl o Li keyformat=passphrase Ar dataset ,
and you are hereby asked to report a bug, please.
.Pp
.Nm zfs-tpm1x-clear-key Ar dataset
can be used to clear the properties and go back to using a passphrase.
.
.Sh OPTIONS
.Bl -tag -compact -width ".Fl b Ar backup-file"
.It Fl b Ar backup-file
Save a back-up of the key to
.Ar backup-file ,
which must not exist beforehand.
This back-up
.Em must
be stored securely, off-site.
In case of a catastrophic event, the key can be loaded by running
.Dl Nm zfs Cm load-key Ar dataset Li < Ar backup-file
.Pp
.
.It Fl P Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns
Bind the key to space- or comma-separated
.Ar PCR Ns s
\(em if they change, the wrapping key will not be able to be unsealed.
The minimum number of PCRs for a PC TPM is
.Sy 24 Pq numbered Bq Sy 0 , 23 .
For most, this is also the maximum.
.El
.
#include "passphrase.h"
.
#include "backend-tpm1x.h"
.
#include "common.h"
.
.Sh SEE ALSO
.\" Match this to zfs-tpm2-change-key.8:
PCR allocations:
.Lk https:/\&/wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers
and
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf ,
Section 2.3.4 "PCR Usage", Table 1.

View File

@ -1,62 +0,0 @@
zfs-tpm1x-change-key(8) -- change ZFS dataset key to one stored on the TPM
==========================================================================
## SYNOPSIS
`zfs-tpm1x-change-key` [-b file] <dataset>
## DESCRIPTION
To normalise `dataset`, zfs-tpm1x-change-key(8) will open its encryption root in its stead.
zfs-tpm1x-change-key(8) will *never* create or destroy encryption roots; use **zfs(8) change-key** for that.
First, a connection is made to the TPM, which *must* be TPM-1.X-compatible.
If `dataset` was previously encrypted with tzpfms and the *TPM1.X* back-end was used, the metadata will be silently cleared.
Otherwise, or in case of an error, data required for manual intervention will be printed to the standard error stream.
Next, a new wrapping key is be generated on the TPM, optionally backed up (see [OPTIONS][]),
and sealed on the TPM;
the user is prompted for an optional passphrase to protect the key with,
and for the SRK passphrase, set when taking ownership, if it is not "well-known" (all zeroes).
The following properties are set on `dataset`:
* `xyz.nabijaczleweli:tzpfms.backend`=`TPM1.X`
* `xyz.nabijaczleweli:tzpfms.key`=*(parent key blob)*`:`*(sealed object blob)*
`tzpfms.backend` identifies this dataset for work with *TPM1.X*-back-ended tzpfms tools
(namely zfs-tpm1x-change-key(8), zfs-tpm1x-load-key(8), and zfs-tpm1x-clear-key(8)).
`tzpfms.key` is a colon-separated pair of hexadecimal-string (i.e. "4F7730" for "Ow0") blobs;
the first one represents the RSA key protecting the blob,
and it is protected with either the password, if provided, or the SHA1 constant *CE4CF677875B5EB8993591D5A9AF1ED24A3A8736*;
the second represents the sealed object containing the wrapping key,
and is protected with the SHA1 constant *B9EE715DBE4B243FAA81EA04306E063710383E35*.
There exists no other user-land tool for decrypting this; perhaps there should be.
#comment (TODO: make an LD_PRELOADable for extracting the key maybe)
Finally, the equivalent of **zfs(8) change-key -o keylocation=prompt -o keyformat=raw dataset** is performed with the new key.
If an error occurred, best effort is made to clean up the properties,
or to issue a note for manual intervention into the standard error stream.
A final verification should be made by running **zfs-tpm1x-load-key(8) -n dataset**.
If that command succeeds, all is well,
but otherwise the dataset can be manually rolled back to a password with **zfs-tpm1x-clear-key(8) dataset** (or, if that fails to work, **zfs(8) change-key -o keyformat=passphrase dataset**), and you are hereby asked to report a bug, please.
**zfs-tpm1x-clear-key(8) dataset** can be used to clear the properties and go back to using a password.
## OPTIONS
* `-b` *file*:
Save a back-up of the key to *file*, which must not exist beforehand.
This back-up **must** be stored securely, off-site.
In case of a catastrophic event, the key can be loaded by running **zfs(8) load-key dataset < backup-file**.
#include "backend-tpm1x.h"
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

View File

@ -0,0 +1,38 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM1X-CLEAR-KEY 8
.Os
.
.Sh NAME
.Nm zfs-tpm1x-clear-key
.Nd rewrap ZFS dataset key in passsword and clear tzpfms TPM1.X metadata
.Sh SYNOPSIS
.Nm
.Ar dataset
.
.Sh DESCRIPTION
After verifying
.Ar dataset
was encrypted with the
.Nm tzpfms
.Sy TPM1.X
backend:
.Bl -enum -compact -offset 2n -width 2n
.It
performs the equivalent of
.Nm zfs Cm change-key Fl o Li keylocation=prompt Fl o Li keyformat=passphrase Ar dataset ,
.It
removes the
.Li xyz.nabijaczleweli:tzpfms.\& Ns Brq Li backend , key
properties from
.Ar dataset .
.El
.Pp
See
.Xr zfs-tpm1x-change-key 8
for a detailed description.
.
#include "backend-tpm1x.h"
.
#include "common.h"

View File

@ -1,23 +0,0 @@
zfs-tpm1x-clear-key(8) -- rewrap ZFS dataset key in passsword and clear tzpfms TPM1.X metadata
==============================================================================================
## SYNOPSIS
`zfs-tpm1x-clear-key` <dataset>
## DESCRIPTION
zfs-tpm1x-clear-key(8), after verifying that `dataset` was encrypted with tzpfms backend *TPM1.X* will:
1. perform the equivalent of **zfs(8) change-key -o keylocation=prompt -o keyformat=passphrase dataset**,
2. remove the `xyz.nabijaczleweli:tzpfms.{backend,key}` properties from `dataset`.
See zfs-tpm1x-change-key(8) for a detailed description.
#include "backend-tpm1x.h"
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

View File

@ -0,0 +1,46 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM1X-LOAD-KEY 8
.Os
.
.Sh NAME
.Nm zfs-tpm1x-load-key
.Nd load TPM1.X-encrypted ZFS dataset key
.Sh SYNOPSIS
.Nm
.Op Fl n
.Ar dataset
.
.Sh DESCRIPTION
After verifying
.Ar dataset
was encrypted with the
.Nm tzpfms
.Sy TPM1.X
backend,
unseals the key and load it into
.Ar dataset .
.Pp
The user is first prompted for the SRK passphrase, set when taking ownership, if not "well-known" (all zeroes);
then for the additional passphrase, set when creating the key, if one was set.
.Pp
See
.Xr zfs-tpm1x-change-key 8
for a detailed description.
.
.Sh OPTIONS
.Bl -tag -compact -width ".Fl n"
.It Fl n
Do a no-op/dry run, can be used even if the key is already loaded.
Equivalent to
.Nm zfs Cm load-key Ns 's
.Fl n
option.
.El
.
#include "passphrase.h"
.
#include "backend-tpm1x.h"
.
#include "common.h"

View File

@ -1,28 +0,0 @@
zfs-tpm1x-load-key(8) -- load tzpfms TPM1.X-encrypted ZFS dataset key
=====================================================================
## SYNOPSIS
`zfs-tpm1x-load-key` [-n] <dataset>
## DESCRIPTION
zfs-tpm1x-load-key(8), after verifying that `dataset` was encrypted with tzpfms backend *TPM1.X* will unseal the key and load it into `dataset`.
The user is prompted for, first, the SRK passphrase, set when taking ownership, if it's not "well-known" (all zeroes),
then the additional passphrase set when creating the key, if it was provided.
See zfs-tpm1x-change-key(8) for a detailed description.
## OPTIONS
* `-n`:
Do a no-op/dry run, can be used even if the key is already loaded. Equivalent to **zfs(8) load-key**'s `-n` option.
#include "backend-tpm1x.h"
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

View File

@ -0,0 +1,167 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM2-CHANGE-KEY 8
.Os
.
.Sh NAME
.Nm zfs-tpm2-change-key
.Nd change ZFS dataset key to one stored on the TPM
.Sh SYNOPSIS
.Nm
.Op Fl b Ar backup-file
.Oo
.Fl P Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns Ns \: Ns Oo Cm + Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns Oc Ns
.Op Fl A
.Oc
.Ar dataset
.
.Sh DESCRIPTION
To normalise
.Ar dataset ,
.Nm
will open its encryption root in its stead.
.Nm
will
.Em never
create or destroy encryption roots; use
.Xr zfs-change-key 8
for that.
.Pp
First, a connection is made to the TPM, which
.Em must
be TPM-2.0-compatible.
.Pp
If
.Ar dataset
was previously encrypted with
.Nm tzpfms
and the
.Sy TPM2
back-end was used, the previous key will be freed from the TPM.
Otherwise, or in case of an error, data required for manual intervention will be written to the standard error stream.
.Pp
Next, a new wrapping key is generated on the TPM, optionally backed up
.Pq see Sx OPTIONS ,
and sealed to a persistent object on the TPM under the owner hierarchy;
if there is a passphrase set on the owner hierarchy, the user is prompted for it;
the user is always prompted for an optional passphrase to protect the sealed object with.
.Pp
The following properties are set on
.Ar dataset :
.Bl -bullet -compact -offset 4n -width "@"
.It
.Li xyz.nabijaczleweli:tzpfms.backend Ns = Ns Sy TPM2
.It
.Li xyz.nabijaczleweli:tzpfms.key Ns = Ns Ar persistent-object-ID Ns \: Ns Op Cm ;\& Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns Ns Oo Cm + Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns Oc Ns
.El
.Pp
.Li tzpfms.backend
identifies this dataset for work with
.Sy TPM2 Ns -back-ended
.Nm tzpfms
programs
.Pq namely Xr zfs-tpm2-change-key 8 , Xr zfs-tpm2-load-key 8 , and Xr zfs-tpm2-clear-key 8 .
.Pp
.Li tzpfms.key
is an integer representing the sealed object, optionally followed by a semicolon and PCR list as specified with
.Fl P ,
normalised to be
.Nm tpm-tools Ns -toolchain-compatible ;
if needed, it can be passed to
.Nm tpm2_unseal Fl c Ev ${tzpfms.key Ns Cm %% Ns Li ;* Ns Ev }\&
with
.Fl p Qq Li str:\& Ns Ev ${passphrase}
or
.Fl p Qq Li pcr:\& Ns Ev ${tzpfms.key Ns Cm # Ns Li *; Ns Ev }\& ,
as the case may be, or equivalent, for back-up
.Pq see Sx OPTIONS .
If you have a sealed key you can access with that or equivalent tool and set both of these properties, it will funxion seamlessly.
.Pp
Finally, the equivalent of
.Nm zfs Cm change-key Fl o Li keylocation=prompt Fl o Li keyformat=raw Ar dataset
is performed with the new key.
If an error occurred, best effort is made to clean up the persistent object and properties,
or to issue a note for manual intervention into the standard error stream.
.Pp
A final verification should be made by running
.Nm zfs-tpm2-load-key Fl n Ar dataset .
If that command succeeds, all is well,
but otherwise the dataset can be manually rolled back to a passphrase with
.Nm zfs-tpm2-clear-key Ar dataset
.Pq or, if that fails to work, Nm zfs Cm change-key Fl o Li keyformat=passphrase Ar dataset ,
and you are hereby asked to report a bug, please.
.Pp
.Nm zfs-tpm2-clear-key Ar dataset
can be used to free the TPM persistent object and go back to using a passphrase.
.
.Sh OPTIONS
.Bl -tag -compact -width ".Fl b Ar backup-file"
.It Fl b Ar backup-file
Save a back-up of the key to
.Ar backup-file ,
which must not exist beforehand.
This back-up
.Em must
be stored securely, off-site.
In case of a catastrophic event, the key can be loaded by running
.Dl Nm zfs Cm load-key Ar dataset Li < Ar backup-file
.Pp
.
.It Fl P Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns Ns Oo Cm + Ns Ar algorithm Ns Cm \&: Ns Ar PCR Ns Oo Ns Cm \&, Ns Ar PCR Oc Ns Oc Ns
Bind the key to space- or comma-separated
.Ar PCR Ns s
within their corresponding hashing
.Ar algorithm
\(em if they change, the wrapping key will not be able to be unsealed.
There are
.Sy 24
PCRs, numbered
.Bq Sy 0 , 23 .
.Pp
.Ar algorithm
may be any of case-insensitive
.Qq Sy sha1 ,
.Qq Sy sha256 ,
.Qq Sy sha384 ,
.Qq Sy sha512 ,
.Qq Sy sm3_256 ,
.Qq Sy sm3-256 ,
.Qq Sy sha3_256 ,
.Qq Sy sha3-256 ,
.Qq Sy sha3_384 ,
.Qq Sy sha3-384 ,
.Qq Sy sha3_512 ,
or
.Qq Sy sha3-512 ,
and must be supported by the TPM.
.Pp
.
.It Fl A
With
.Fl P ,
also prompt for a passphrase.
This is skipped by default because the passphrase is
.Em OR Ns ed
with the PCR policy \(em the wrapping key can be unsealed
.Em either
passphraseless with the right PCRs
.Em or
with the passphrase, and this is usually not the intent.
.El
.
#include "passphrase.h"
.
#include "backend-tpm2.h"
.
#include "common.h"
.
.Sh SEE ALSO
.Xr tpm2_unseal 1
.Pp
.\" Match this to zfs-tpm1x-change-key.8:
PCR allocations:
.Lk https:/\&/wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers
and
.Lk https:/\&/trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf ,
Section 2.3.4 "PCR Usage", Table 1.

View File

@ -1,58 +0,0 @@
zfs-tpm2-change-key(8) -- change ZFS dataset key to one stored on the TPM
=========================================================================
## SYNOPSIS
`zfs-tpm2-change-key` [-b file] <dataset>
## DESCRIPTION
To normalise `dataset`, zfs-tpm2-change-key(8) will open its encryption root in its stead.
zfs-tpm2-change-key(8) will *never* create or destroy encryption roots; use **zfs(8) change-key** for that.
First, a connection is made to the TPM, which *must* be TPM-2.0-compatible.
If `dataset` was previously encrypted with tzpfms and the *TPM2* back-end was used, the previous key will be freed from the TPM.
Otherwise, or in case of an error, data required for manual intervention will be printed to the standard error stream.
Next, a new wrapping key is be generated on the TPM, optionally backed up (see [OPTIONS][]),
and sealed to a persistent object on the TPM under the owner hierarchy;
if there is a passphrase set on the owner hierarchy, the user is prompted for it;
the user is always prompted for an optional passphrase to protect the sealed object with.
The following properties are set on `dataset`:
* `xyz.nabijaczleweli:tzpfms.backend`=`TPM2`
* `xyz.nabijaczleweli:tzpfms.key`=*(ID of persistent object)*
`tzpfms.backend` identifies this dataset for work with *TPM2*-back-ended tzpfms tools
(namely zfs-tpm2-change-key(8), zfs-tpm2-load-key(8), and zfs-tpm2-clear-key(8)).
`tzpfms.key` is an integer representing the sealed object;
if needed, it can be passed to **tpm2_unseal(1) -c ${tzpfms.key} [-p ${password}]** or equivalent for back-up (see [OPTIONS][]).
If you have a sealed key you can access with that or equivalent tool and set both of these properties, it will funxion seamlessly.
Finally, the equivalent of **zfs(8) change-key -o keylocation=prompt -o keyformat=raw dataset** is performed with the new key.
If an error occurred, best effort is made to clean up the persistent object and properties,
or to issue a note for manual intervention into the standard error stream.
A final verification should be made by running **zfs-tpm2-load-key(8) -n dataset**.
If that command succeeds, all is well,
but otherwise the dataset can be manually rolled back to a password with **zfs-tpm2-clear-key(8) dataset** (or, if that fails to work, **zfs(8) change-key -o keyformat=passphrase dataset**), and you are hereby asked to report a bug, please.
**zfs-tpm2-clear-key(8) dataset** can be used to free the TPM persistent object and go back to using a password.
## OPTIONS
* `-b` *file*:
Save a back-up of the key to *file*, which must not exist beforehand.
This back-up **must** be stored securely, off-site.
In case of a catastrophic event, the key can be loaded by running **zfs(8) load-key dataset < backup-file**.
#include "backend-tpm2.h"
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

View File

@ -0,0 +1,43 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM2-CLEAR-KEY 8
.Os
.
.Sh NAME
.Nm zfs-tpm2-clear-key
.Nd rewrap ZFS dataset key in passsword and clear tzpfms TPM2 metadata
.Sh SYNOPSIS
.Nm
.Ar dataset
.
.Sh DESCRIPTION
After verifying
.Ar dataset
was encrypted with the
.Nm tzpfms
.Sy TPM2
backend:
.Bl -enum -compact -offset 2n -width 2n
.It
performs the equivalent of
.Nm zfs Cm change-key Fl o Li keylocation=prompt Fl o Li keyformat=passphrase Ar dataset ,
.It
frees the sealed key previously used to encrypt
.Ar dataset ,
.It
removes the
.Li xyz.nabijaczleweli:tzpfms.\& Ns Brq Li backend , key
properties from
.Ar dataset .
.El
.Pp
See
.Xr zfs-tpm2-change-key 8
for a detailed description.
.
#include "passphrase.h"
.
#include "backend-tpm2.h"
.
#include "common.h"

View File

@ -1,24 +0,0 @@
zfs-tpm2-clear-key(8) -- rewrap ZFS dataset key in passsword and clear tzpfms TPM2 metadata
===========================================================================================
## SYNOPSIS
`zfs-tpm2-clear-key` <dataset>
## DESCRIPTION
zfs-tpm2-clear-key(8), after verifying that `dataset` was encrypted with tzpfms backend *TPM2* will:
1. perform the equivalent of **zfs(8) change-key -o keylocation=prompt -o keyformat=passphrase dataset**,
2. free the sealed key previously used to encrypt `dataset`,
3. remove the `xyz.nabijaczleweli:tzpfms.{backend,key}` properties from `dataset`.
See zfs-tpm2-change-key(8) for a detailed description.
#include "backend-tpm2.h"
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

View File

@ -0,0 +1,45 @@
.\" SPDX-License-Identifier: MIT
.
.Dd
.Dt ZFS-TPM2-LOAD-KEY 8
.Os
.
.Sh NAME
.Nm zfs-tpm2-load-key
.Nd load TPM2-encrypted ZFS dataset key
.Sh SYNOPSIS
.Nm
.Op Fl n
.Ar dataset
.
.Sh DESCRIPTION
After verifying
.Ar dataset
was encrypted with the
.Nm tzpfms
.Sy TPM2
backend,
unseals the key and loads it into
.Ar dataset .
.Pp
The user is prompted for the additional passphrase, set when creating the key, if one was set.
.Pp
See
.Xr zfs-tpm2-change-key 8
for a detailed description.
.
.Sh OPTIONS
.Bl -tag -compact -width ".Fl n"
.It Fl n
Do a no-op/dry run, can be used even if the key is already loaded.
Equivalent to
.Nm zfs Cm load-key Ns 's
.Fl n
option.
.El
.
#include "passphrase.h"
.
#include "backend-tpm1x.h"
.
#include "common.h"

View File

@ -1,25 +0,0 @@
zfs-tpm2-load-key(8) -- load tzpfms TPM2-encrypted ZFS dataset key
==================================================================
## SYNOPSIS
`zfs-tpm2-load-key` [-n] <dataset>
## DESCRIPTION
zfs-tpm2-load-key(8), after verifying that `dataset` was encrypted with tzpfms backend *TPM2* will unseal the key and load it into `dataset`.
See zfs-tpm2-change-key(8) for a detailed description.
## OPTIONS
* `-n`:
Do a no-op/dry run, can be used even if the key is already loaded. Equivalent to **zfs(8) load-key**'s `-n` option.
#include "backend-tpm2.h"
#include "common.h"
## SEE ALSO
&lt;<https://git.sr.ht/~nabijaczleweli/tzpfms>&gt;

282
po/pl.po Normal file
View File

@ -0,0 +1,282 @@
# SPDX-License-Identifier: 0BSD
msgid ""
msgstr "Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: src/fd.cpp:63
#, c-format
msgid "Passphrase for %s"
msgstr "Hasło do %s"
#: src/fd.cpp:65
#, c-format
msgid "Passphrase for %s (again)"
msgstr "Powtórz hasło do %s"
#: src/fd.cpp:67
#, c-format
msgid "New passphrase for %s"
msgstr "Nowe hasło do %s"
#. newkey && again
#: src/fd.cpp:69
#, c-format
msgid "New passphrase for %s (again)"
msgstr "Powtórz howe hasło do %s"
#. exit status
#: src/fd.cpp:116
#, c-format
msgid "Helper '%s' failed with %d.\n"
msgstr "'%s' zawiódł: %d.\n"
#: src/fd.cpp:119
#, c-format
msgid "Helper '%s' died to signal %d: %s.\n"
msgstr "'%s' zabity przez sygnał %d: %s.\n"
#: src/fd.cpp:155
#, c-format
msgid "Enter passphrase for %s: "
msgstr "Hasło do %s"
#: src/fd.cpp:157
#, c-format
msgid "Enter passphrase for new %s: "
msgstr "Hasło do nowego %s"
#: src/fd.cpp:159
#, c-format
msgid "Re-enter passphrase for %s: "
msgstr "Ponownie, hasło do %s"
#. again && newkey
#: src/fd.cpp:161
#, c-format
msgid "Re-enter passphrase for new %s: "
msgstr "Ponownie, hasło do nowego %s"
#: src/fd.cpp:238 src/fd.cpp:254
#, c-format
msgid "Passphrase too long (max %zu)\n"
msgstr "Hasło za długie (maks. %zu)\n"
#: src/fd.cpp:252
#, c-format
msgid "Passphrase too short (min %u)\n"
msgstr "Hasło za krótkie (maks. %u)\n"
#: src/fd.cpp:262
#, c-format
msgid "Provided passphrases do not match.\n"
msgstr "Hasła się różnią\n"
#: src/main.hpp:44 src/main.hpp:57 src/main.hpp:62
#, c-format
msgid "usage: %s [-hV] %s%s%s\n"
msgstr "składnia: %s [-hV] %s%s%s\n"
#. as-in argument in a usage string
#: src/main.hpp:71
msgid "dataset"
msgstr "dataset"
#: src/main.hpp:75
#, c-format
msgid "No dataset to act on?\n"
"usage: %s [-hV] %s%sdataset\n"
msgstr "Nie podano datasetu?\n"
"składnia: %s [-hV] %s%sdataset\n"
#: src/main.hpp:80
#, c-format
msgid "usage: %s [-hV] %s%sdataset\n"
msgstr "składnia: %s [-hV] %s%sdataset\n"
#: src/main.hpp:90
#, c-format
msgid "Dataset %s not encrypted?\n"
msgstr "Dataset %s nie jest zaszyfrowany?\n"
#: src/main.hpp:92
#, c-format
msgid "Using dataset %s's encryption root %s instead.\n"
msgstr "Używam zamiast %s jego korzenia szyfrowania %s.\n"
#. user-supplied parameter, errno
#: src/tpm1x.cpp:101
#, c-format
msgid "PCR %s: %s\n"
msgstr "PCR %s: %s\n"
#. user-supplied parameter
#: src/tpm1x.cpp:104
#, c-format
msgid "PCR %s: too large (max 229).\n"
msgstr "PCR %s: za duży (maks. 229)\n"
#. == TPM2_NUM_PCR_BANKS
#: src/tpm2.cpp:116
#, c-format
msgid "Too many PCR banks specified! Can only have up to %zu\n"
msgstr "Zbyt wiele grup PCRów! Mogę przyjąć maks. %zu\n"
#. comma-separated list follows
#: src/tpm2.cpp:133
#, c-format
msgid "Unknown hash algorithm %s.\n"
"Can be any of case-insensitive "
msgstr "Nie znany algorytm haszowania %s.\n"
"Można użyć dowolnego z (rozmiar liter nie ma znaczenia): "
#. user-passed parameter
#: src/tpm2.cpp:164
#, c-format
msgid "PCR bank \"%s\": no algorithm; need alg:PCR[,PCR]…\n"
msgstr "PCR bank \"%s\": nie podano algorytmu; potrzebuję alg:PCR[,PCR]…\n"
#. %s=dataset name, then TPM2. noun for "Enter passphrase for" prompt
#. %s=dataset, then TPM1.X. noun for "Enter passphrase for" prompt
#: src/tpm2.cpp:374 src/bin/zfs-tpm1x-change-key.cpp:109
#, c-format
msgid "%s %s wrapping key (or empty for none)"
msgstr "klucza zawijania %2$s dla %1$s (puste żeby nie używać żadnego)"
#. %s=dataset name, then TPM2. noun for "Enter passphrase for" prompt
#. %s=dataset name, then TPM1.x. noun for "Enter passphrase for" prompt
#: src/tpm2.cpp:425 src/bin/zfs-tpm1x-load-key.cpp:59
#, c-format
msgid "%s %s wrapping key"
msgstr "klucza zawijania %2$s dla %1$s"
#: src/tpm2.cpp:439
#, c-format
msgid "Couldn't unseal wrapping key with PCR policy: %s\n"
msgstr "Nie udało się rozpieczętować klucza zawijania z polityką PCR: %s\n"
#: src/zfs.cpp:110
#, c-format
msgid "You might need to run \"zfs inherit %s %s\" and \"zfs inherit %s %s\" to fully clear metadata!\n"
msgstr "Możliwe, że potrzebujesz uruchomić \"zfs inherit %s %s\" i \"zfs inherit %s %s\" żeby całkowicie pozbyć się metadanych!\n"
#: src/zfs.cpp:125
#, c-format
msgid "Dataset %s not encrypted with tzpfms!\n"
msgstr "Dataset %s nie jest szyfrowany tzpfms!\n"
#: src/zfs.cpp:127
#, c-format
msgid "Dataset %s encrypted with tzpfms back-end %s, but we are %s.\n"
msgstr "Dataset %s szyfrowany tzpfms %s, ale ten program rozumie %s.\n"
#: src/zfs.cpp:131
#, c-format
msgid "Dataset %s missing key data.\n"
msgstr "Dataset %s nie ma klucza.\n"
#. / Mimic libzfs error output
#: src/zfs.hpp:28
#, c-format
msgid "Key change error: Key must be loaded.\n"
msgstr "Błąd zmiany klucza: Klucz musi być załadowany.\n"
#. dataset name: (null), 0A123...
#. dataset name: TPM1.X, (null)
#: src/zfs.hpp:68
#, c-format
msgid "Inconsistent tzpfms metadata for %s: back-end is %s, but handle is %s?\n"
msgstr "Niespójne metadane tzpfms dla %s: tzpfms %s ale obiekt z kluczem %s?\n"
#: src/zfs.hpp:73
#, c-format
msgid "Dataset %s was encrypted with tzpfms back-end %s before, but we are %s. You will have to free handle %s for back-end %s manually!\n"
msgstr "Dataset %s był zaszyfrowany tzpfms %s, ale ten program rozumie %s. Konieczne będzie ręczne usunięcie obiektu z kluczem %s %s!\n"
#: src/zfs_meat.cpp:29
#, c-format
msgid "Key for %s changed\n"
msgstr "Klucz do %s zmieniony\n"
#: src/zfs_meat.cpp:42
#, c-format
msgid "Key for %s OK\n"
msgstr "Klucz do %s OK\n"
#: src/zfs_meat.cpp:44
#, c-format
msgid "Key for %s loaded\n"
msgstr "Klucz do %s załadowany\n"
#: src/bin/zfs-tpm1x-change-key.cpp:26
msgid "[-b backup-file] [-P PCR[,PCR]…]"
msgstr "[-b plik-z-backupem] [-P PCR[,PCR]…]"
#. 0A1234... follows
#: src/bin/zfs-tpm1x-load-key.cpp:67
#, c-format
msgid "Wrong sealed data length (%u != %zu): "
msgstr "Zła długość zaplombowanych danych (%u != %zu): "
#: src/bin/zfs-tpm2-change-key.cpp:21
msgid "[-b backup-file] [-P algorithm:PCR[,PCR]…[+algorithm:PCR[,PCR]…]… [-A]]"
msgstr "[-b plik-z-backupem] [-P algorytm:PCR[,PCR]…[+algorytm:PCR[,PCR]…]… [-A]]"
#: src/bin/zfs-tpm2-change-key.cpp:68
#, c-format
msgid "Couldn't parse previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c %s\" or equivalent!\n"
msgstr "Nie udało się rozczytać poprzedniego obiektu z kluczem dla %s. Możliwe, że potrzeba będzie uruchomić \"tpm2_evictcontrol -c %s\", albo jego ekwiwalent!\n"
#: src/bin/zfs-tpm2-change-key.cpp:74
#, c-format
msgid "Couldn't free previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c 0x%X\" or equivalent!\n"
msgstr "Nie udało się uwolnić poprzedniego obiektu z kluczem dla %s. Możliwe, że potrzeba będzie uruchomić \"tpm2_evictcontrol -c 0x%X\", albo jego ekwiwalent!\n"
#: src/bin/zfs-tpm2-change-key.cpp:89
#, c-format
msgid "Couldn't free persistent handle. You might need to run \"tpm2_evictcontrol -c 0x%X\" or equivalent!\n"
msgstr "Nie udało się uwolnić obiektu z kluczem. Możliwe, że potrzeba będzie uruchomić \"tpm2_evictcontrol -c 0x%X\", albo jego ekwiwalent!\n"
#. for KEYSTATUS column
#: src/bin/zfs-tpm-list.cpp:44
msgid "unavailable"
msgstr "niedostępny"
#: src/bin/zfs-tpm-list.cpp:44
msgid "available"
msgstr "dostępny"
#. for COHERENT column
#: src/bin/zfs-tpm-list.cpp:46
msgid "no"
msgstr "nie"
#: src/bin/zfs-tpm-list.cpp:46
msgid "yes"
msgstr "tak"
#: src/bin/zfs-tpm-list.cpp:88
msgid "[-H] [-r|-d max] [-a|-b back-end] [-u|-l]"
msgstr "[-H] [-r|-d maks.] [-a|-b rodzaj-tzpfms] [-u|-l]"
#: src/bin/zfs-tpm-list.cpp:88
msgid "[filesystem|volume]…"
msgstr "[system-plików|wolumin]…"
#: src/bin/zfs-tpm-list.cpp:149 src/bin/zfs-tpm-list.cpp:170
msgid "NAME"
msgstr "NAZWA"
#: src/bin/zfs-tpm-list.cpp:150 src/bin/zfs-tpm-list.cpp:170
msgid "BACK-END"
msgstr "RODZAJ"
#: src/bin/zfs-tpm-list.cpp:151 src/bin/zfs-tpm-list.cpp:170
msgid "KEYSTATUS"
msgstr "KLUCZ"
#: src/bin/zfs-tpm-list.cpp:170
msgid "COHERENT"
msgstr "SPÓJNY"

9
pp.awk
View File

@ -10,6 +10,13 @@ BEGIN {
dir = ARGV[1] dir = ARGV[1]
sub(/[^\/]+$/, "", dir) sub(/[^\/]+$/, "", dir)
for (i = 2; i < ARGC; ++i) {
eq = index(ARGV[i], "=")
v = substr(ARGV[i], eq + 1)
gsub(/\\n/, "\n", v)
macro_contents[substr(ARGV[i], 1, eq - 1)] = v
}
incfile = "" incfile = ""
} }
@ -27,8 +34,6 @@ function input() {
while((getline < incfile) == 1) while((getline < incfile) == 1)
input() input()
incfile = "" incfile = ""
} else if(NF >= 1 && $1 == "#comment") {
// just dont
} else if(NF >= 2 && $1 == "#define") { } else if(NF >= 2 && $1 == "#define") {
split($2, nameargs, "(") split($2, nameargs, "(")
macroname = nameargs[1] macroname = nameargs[1]

View File

@ -6,12 +6,13 @@
#include "../zfs.hpp" #include "../zfs.hpp"
#include <algorithm> #include <algorithm>
#include <wchar.h>
#define TZPFMS_BACKEND_MAX_LEN 16 #define TZPFMS_BACKEND_MAX_LEN 16
enum class key_loadedness : char { enum class key_loadedness : signed char {
none = -1, none = -1,
unloaded = 0, unloaded = 0,
loaded = 1, loaded = 1,
@ -19,8 +20,11 @@ enum class key_loadedness : char {
/// zfs(8) uses struct zprop_get_cbdata_t, which is powerful, but inscrutable; we have a fixed format, which makes this easier /// zfs(8) uses struct zprop_get_cbdata_t, which is powerful, but inscrutable; we have a fixed format, which makes this easier
struct output_line { struct output_line {
static const char * const key_available_display[2]; static const char * key_available_display[2];
static const char * const coherent_display[2]; static const char * coherent_display[2];
static uint8_t key_available_display_width[2];
static void init_display();
char name[ZFS_MAX_DATASET_NAME_LEN + 1]; char name[ZFS_MAX_DATASET_NAME_LEN + 1];
@ -36,8 +40,42 @@ struct output_line {
const char * backend_display() const { return (this->backend[0] != '\0') ? this->backend : "-"; } const char * backend_display() const { return (this->backend[0] != '\0') ? this->backend : "-"; }
}; };
const char * const output_line::key_available_display[2]{"unavailable", key_available_display[0] + 2}; // for KEYSTATUS column
const char * const output_line::coherent_display[2]{"no", "yes"}; const char * output_line::key_available_display[2]{gettext_noop("unavailable"), gettext_noop("available")};
// for COHERENT column
const char * output_line::coherent_display[2]{gettext_noop("no"), gettext_noop("yes")};
uint8_t output_line::key_available_display_width[2];
// infallible strings only
static int strwidth(const char * str) {
int ret{};
wchar_t c;
mbstate_t state{};
auto len = strlen(str);
for(;;)
switch(auto rd = mbrtowc(&c, str, len, &state)) {
case 0: // NUL
case(size_t)-1: // EILSEQ
case(size_t)-2: // ENODATA
return ret;
default:
ret += wcwidth(c) ?: 0;
str += rd;
len -= rd;
break;
}
}
void output_line::init_display() {
key_available_display[false] = gettext(key_available_display[false]);
key_available_display[true] = gettext(key_available_display[true]);
key_available_display_width[false] = strwidth(key_available_display[false]);
key_available_display_width[true] = strwidth(key_available_display[true]);
coherent_display[false] = gettext(coherent_display[false]);
coherent_display[true] = gettext(coherent_display[true]);
}
int main(int argc, char ** argv) { int main(int argc, char ** argv) {
@ -47,7 +85,7 @@ int main(int argc, char ** argv) {
const char * backend_restrixion = nullptr; const char * backend_restrixion = nullptr;
auto key_loadedness_restrixion = key_loadedness::none; auto key_loadedness_restrixion = key_loadedness::none;
return do_bare_main( return do_bare_main(
argc, argv, "Hrd:ab:ul", "[-H] [-r|-d max] [-a|-b back-end] [-u|-l]", "[filesystem|volume]…", argc, argv, "Hrd:ab:ul", gettext_noop("[-H] [-r|-d max] [-a|-b back-end] [-u|-l]"), gettext_noop("[filesystem|volume]…"),
[&](auto arg) { [&](auto arg) {
switch(arg) { switch(arg) {
case 'H': case 'H':
@ -57,10 +95,8 @@ int main(int argc, char ** argv) {
maxdepth = SIZE_MAX; maxdepth = SIZE_MAX;
break; break;
case 'd': case 'd':
if(parse_int(optarg, maxdepth)) { if(!parse_uint(optarg, maxdepth))
fprintf(stderr, "%s is not an integer\n", optarg); return fprintf(stderr, "%s: -d %s: %s\n", argv[0], optarg, strerror(errno)), __LINE__;
return __LINE__;
}
break; break;
case 'a': case 'a':
print_nontzpfms = true; print_nontzpfms = true;
@ -80,8 +116,6 @@ int main(int argc, char ** argv) {
[&](auto libz) { [&](auto libz) {
output_line * lines{}; output_line * lines{};
size_t lines_len{}; size_t lines_len{};
quickscope_wrapper lines_deleter{[&] { free(lines); }};
TRY_MAIN(for_all_datasets(libz, argv + optind, maxdepth, [&](auto dataset) { TRY_MAIN(for_all_datasets(libz, argv + optind, maxdepth, [&](auto dataset) {
boolean_t dataset_is_root; boolean_t dataset_is_root;
@ -93,10 +127,9 @@ int main(int argc, char ** argv) {
TRY_MAIN(lookup_userprop(dataset, PROPNAME_BACKEND, backend)); TRY_MAIN(lookup_userprop(dataset, PROPNAME_BACKEND, backend));
TRY_MAIN(lookup_userprop(dataset, PROPNAME_KEY, handle)); TRY_MAIN(lookup_userprop(dataset, PROPNAME_KEY, handle));
++lines_len; lines = TRY_PTR("allocate line buffer", reinterpret_cast<output_line *>(reallocarray(lines, ++lines_len, sizeof(output_line))));
lines = TRY_PTR("allocate line buffer", reinterpret_cast<output_line *>(realloc(lines, sizeof(output_line) * lines_len)));
auto & cur_line = lines[lines_len - 1]; auto & cur_line = lines[lines_len - 1];
strncpy(cur_line.name, zfs_get_name(dataset), ZFS_MAX_DATASET_NAME_LEN); strncpy(cur_line.name, zfs_get_name(dataset), ZFS_MAX_DATASET_NAME_LEN);
strncpy(cur_line.backend, (backend && strlen(backend) <= TZPFMS_BACKEND_MAX_LEN) ? backend : "\0", TZPFMS_BACKEND_MAX_LEN); strncpy(cur_line.backend, (backend && strlen(backend) <= TZPFMS_BACKEND_MAX_LEN) ? backend : "\0", TZPFMS_BACKEND_MAX_LEN);
// Tristate available/unavailable/none, but it's gonna be either available or unavailable on envryption roots, so // Tristate available/unavailable/none, but it's gonna be either available or unavailable on envryption roots, so
@ -106,35 +139,35 @@ int main(int argc, char ** argv) {
return 0; return 0;
})); }));
output_line::init_display();
size_t max_name_len = 0; size_t max_name_len = 0;
size_t max_backend_len = 0; size_t max_backend_len = 0;
size_t max_key_available_len = 0; size_t max_key_available_len = 0;
size_t max_coherent_len = 0;
auto separator = "\t"; auto separator = "\t";
if(human) { if(human) {
max_name_len = strlen("NAME"); max_name_len = strwidth(gettext("NAME"));
max_backend_len = strlen("BACK-END"); max_backend_len = strwidth(gettext("BACK-END"));
max_key_available_len = strlen("KEYSTATUS"); max_key_available_len = strwidth(gettext("KEYSTATUS"));
max_coherent_len = strlen("COHERENT");
separator = " "; separator = " ";
for(auto cur = lines; cur != lines + lines_len; ++cur) for(auto cur = lines; cur != lines + lines_len; ++cur)
if(cur->included(print_nontzpfms, backend_restrixion, key_loadedness_restrixion)) { if(cur->included(print_nontzpfms, backend_restrixion, key_loadedness_restrixion)) {
max_name_len = std::max(max_name_len, strlen(cur->name)); max_name_len = std::max(max_name_len, strlen(cur->name));
max_backend_len = std::max(max_backend_len, strlen(cur->backend_display())); max_backend_len = std::max(max_backend_len, strlen(cur->backend_display()));
max_key_available_len = std::max(max_key_available_len, strlen(output_line::key_available_display[cur->key_available])); max_key_available_len = std::max(max_key_available_len, static_cast<size_t>(output_line::key_available_display_width[cur->key_available]));
} }
} }
auto println = [&](auto name, auto backend, auto key_available, auto coherent) { auto println = [&](auto name, auto backend, auto key_available, auto coherent) {
printf("%-*s%s%-*s%s%-*s%s%-*s\n", // printf("%-*s%s%-*s%s%-*s%s%s\n", //
static_cast<int>(max_name_len), name, separator, // static_cast<int>(max_name_len), name, separator, //
static_cast<int>(max_backend_len), backend, separator, // static_cast<int>(max_backend_len), backend, separator, //
static_cast<int>(max_key_available_len), key_available, separator, // static_cast<int>(max_key_available_len), key_available, separator, //
static_cast<int>(max_coherent_len), coherent); coherent);
}; };
if(human) if(human)
println("NAME", "BACK-END", "KEYSTATUS", "COHERENT"); println(gettext("NAME"), gettext("BACK-END"), gettext("KEYSTATUS"), gettext("COHERENT"));
for(auto cur = lines; cur != lines + lines_len; ++cur) for(auto cur = lines; cur != lines + lines_len; ++cur)
if(cur->included(print_nontzpfms, backend_restrixion, key_loadedness_restrixion)) if(cur->included(print_nontzpfms, backend_restrixion, key_loadedness_restrixion))
println(cur->name, cur->backend_display(), output_line::key_available_display[cur->key_available], output_line::coherent_display[cur->coherent]); println(cur->name, cur->backend_display(), output_line::key_available_display[cur->key_available], output_line::coherent_display[cur->coherent]);

View File

@ -1,10 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <libzfs.h> #include <algorithm>
// #include <sys/zio_crypt.h>
#define WRAPPING_KEY_LEN 32
#include <stdio.h> #include <stdio.h>
#include "../fd.hpp" #include "../fd.hpp"
@ -23,8 +20,20 @@
int main(int argc, char ** argv) { int main(int argc, char ** argv) {
const char * backup{}; const char * backup{};
uint32_t * pcrs{};
size_t pcrs_len{};
return do_main( return do_main(
argc, argv, "b:", "[-b backup-file]", [&](auto) { backup = optarg; }, argc, argv, "b:P:", gettext_noop("[-b backup-file] [-P PCR[,PCR]…]"),
[&](auto o) {
switch(o) {
case 'b':
return backup = optarg, 0;
case 'P':
return tpm1x_parse_pcrs(optarg, pcrs, pcrs_len);
default:
__builtin_unreachable();
}
},
[&](auto dataset) { [&](auto dataset) {
REQUIRE_KEY_LOADED(dataset); REQUIRE_KEY_LOADED(dataset);
@ -37,10 +46,45 @@ int main(int argc, char ** argv) {
TRY_TPM1X("extract TPM from context", Tspi_Context_GetTpmObject(ctx, &tpm_h)); TRY_TPM1X("extract TPM from context", Tspi_Context_GetTpmObject(ctx, &tpm_h));
uint8_t * wrap_key{}; /// Do it early because it's a cmdline argument and to not ask for password if it fails
TRY_TPM1X("get random data from TPM", Tspi_TPM_GetRandom(tpm_h, WRAPPING_KEY_LEN, &wrap_key)); TSS_HOBJECT bound_pcrs{};
quickscope_wrapper bound_pcrs_deleter{[&] {
if(bound_pcrs)
Tspi_Context_CloseObject(ctx, bound_pcrs);
}};
if(pcrs_len) {
auto has_big = std::find_if(pcrs, pcrs + pcrs_len, [](auto p) { return p > 15; }) != pcrs + pcrs_len;
TRY_TPM1X("create PCR list",
Tspi_Context_CreateObject(ctx, TSS_OBJECT_TYPE_PCRS, has_big ? TSS_PCRS_STRUCT_INFO_LONG : TSS_PCRS_STRUCT_DEFAULT, &bound_pcrs));
for(size_t i = 0; i < pcrs_len; i++) {
char buf[15 + 10 + 1]; // 4294967296
snprintf(buf, sizeof(buf), "read PCR %" PRIu32 "", pcrs[i]);
BYTE * val{};
uint32_t val_len{};
TRY_TPM1X(buf, Tspi_TPM_PcrRead(tpm_h, pcrs[i], &val_len, &val));
quickscope_wrapper bound_pcrs_deleter{[&] { Tspi_Context_FreeMemory(ctx, val); }};
snprintf(buf, sizeof(buf), "save PCR %" PRIu32 " value", pcrs[i]);
TRY_TPM1X(buf, Tspi_PcrComposite_SetPcrValue(bound_pcrs, pcrs[i], val_len, val));
}
if(has_big)
TRY_TPM1X("set PCR locality", Tspi_PcrComposite_SetPcrLocality(bound_pcrs, TSS_LOCALITY_ALL));
}
uint8_t wrap_key[WRAPPING_KEY_LEN];
{
BYTE * rand{};
TRY_TPM1X("get random data from TPM", Tspi_TPM_GetRandom(tpm_h, sizeof(wrap_key), &rand));
memcpy(wrap_key, rand, sizeof(wrap_key));
Tspi_Context_FreeMemory(tpm_h, rand);
}
if(backup) if(backup)
TRY_MAIN(write_exact(backup, wrap_key, WRAPPING_KEY_LEN, 0400)); TRY_MAIN(write_exact(backup, wrap_key, sizeof(wrap_key), 0400));
TSS_HOBJECT parent_key{}; TSS_HOBJECT parent_key{};
@ -60,9 +104,13 @@ int main(int argc, char ** argv) {
}}; }};
{ {
char what_for[ZFS_MAX_DATASET_NAME_LEN + 512 + 1];
// %s=dataset, then TPM1.X. noun for "Enter passphrase for" prompt
snprintf(what_for, sizeof(what_for), gettext("%s %s wrapping key (or empty for none)"), zfs_get_name(dataset), THIS_BACKEND);
uint8_t * parent_key_passphrase{}; uint8_t * parent_key_passphrase{};
size_t parent_key_passphrase_len{}; size_t parent_key_passphrase_len{};
TRY_MAIN(read_new_passphrase("wrapping key (or empty for none)", parent_key_passphrase, parent_key_passphrase_len)); TRY_MAIN(read_new_passphrase(what_for, parent_key_passphrase, parent_key_passphrase_len));
quickscope_wrapper parent_key_passphrase_deleter{[&] { free(parent_key_passphrase); }}; quickscope_wrapper parent_key_passphrase_deleter{[&] { free(parent_key_passphrase); }};
if(parent_key_passphrase_len) if(parent_key_passphrase_len)
@ -88,12 +136,8 @@ int main(int argc, char ** argv) {
Tspi_Context_CloseObject(ctx, sealed_object); Tspi_Context_CloseObject(ctx, sealed_object);
}}; }};
// This would need to replace the 0 below to handle PCRs
// TSS_HOBJECT bound_pcrs{}; // See tpm_sealdata.c from src:tpm-tools for more on flags here
// TRY_TPM1X("create PCR list", Tspi_Context_CreateObject(ctx, TSS_OBJECT_TYPE_PCRS, 0, &bound_pcrs));
// quickscope_wrapper bound_pcrs_deleter{[&] { Tspi_Context_CloseObject(ctx, bound_pcrs); }};
TRY_TPM1X("seal wrapping key data", Tspi_Data_Seal(sealed_object, parent_key, WRAPPING_KEY_LEN, wrap_key, 0)); TRY_TPM1X("seal wrapping key data", Tspi_Data_Seal(sealed_object, parent_key, sizeof(wrap_key), wrap_key, bound_pcrs));
uint8_t * parent_key_blob{}; uint8_t * parent_key_blob{};
@ -115,10 +159,10 @@ int main(int argc, char ** argv) {
{ {
auto cur = handle; auto cur = handle;
for(auto i = 0u; i < parent_key_blob_len; ++i, cur += 2) for(auto i = 0u; i < parent_key_blob_len; ++i, cur += 2)
sprintf(cur, "%02X", parent_key_blob[i]); sprintf(cur, "%02hhX", parent_key_blob[i]);
*cur++ = ':'; *cur++ = ':';
for(auto i = 0u; i < sealed_object_blob_len; ++i, cur += 2) for(auto i = 0u; i < sealed_object_blob_len; ++i, cur += 2)
sprintf(cur, "%02X", sealed_object_blob[i]); sprintf(cur, "%02hhX", sealed_object_blob[i]);
*cur++ = '\0'; *cur++ = '\0';
} }

View File

@ -2,14 +2,11 @@
#include "../main_clear.hpp" #include "../main_clear.hpp"
#include "../tpm1x.hpp"
#define THIS_BACKEND "TPM1.X" #define THIS_BACKEND "TPM1.X"
int main(int argc, char ** argv) { int main(int argc, char ** argv) {
tpm1x_handle handle{}; // Not like we use this, but for symmetry with the other -clear-keys return do_clear_main(argc, argv, THIS_BACKEND, [](auto, auto) { return 0; }, [] { return 0; });
return do_clear_main(
argc, argv, THIS_BACKEND, [&](auto dataset, auto handle_s) { return parse_key_props(dataset, THIS_BACKEND, handle_s); }, [&] { return 0; });
} }

View File

@ -1,10 +1,6 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <libzfs.h>
// #include <sys/zio_crypt.h>
#define WRAPPING_KEY_LEN 32
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -35,7 +31,7 @@ int main(int argc, char ** argv) {
uint8_t wrap_key[WRAPPING_KEY_LEN]{}; uint8_t wrap_key[WRAPPING_KEY_LEN]{};
TRY_MAIN(with_tpm1x_session([&](auto ctx, auto srk, auto srk_policy) { TRY_MAIN(with_tpm1x_session([&](auto ctx, auto srk, auto srk_policy) {
TSS_HOBJECT parent_key{}; TSS_HOBJECT parent_key{};
TRY_MAIN(try_policy_or_passphrase("load sealant key from blob (did you take ownership?)", "SRK", srk_policy, [&] { TRY_MAIN(try_policy_or_passphrase("load sealant key from blob (did you take ownership?)", "TPM1.X SRK", srk_policy, [&] {
return Tspi_Context_LoadKeyByBlob(ctx, srk, handle.parent_key_blob_len, handle.parent_key_blob, &parent_key); return Tspi_Context_LoadKeyByBlob(ctx, srk, handle.parent_key_blob_len, handle.parent_key_blob, &parent_key);
})); }));
quickscope_wrapper parent_key_deleter{[&] { Tspi_Key_UnloadKey(parent_key); }}; quickscope_wrapper parent_key_deleter{[&] { Tspi_Key_UnloadKey(parent_key); }};
@ -58,15 +54,19 @@ int main(int argc, char ** argv) {
TRY_TPM1X("load sealed object from blob", Tspi_SetAttribData(sealed_object, TSS_TSPATTRIB_ENCDATA_BLOB, TSS_TSPATTRIB_ENCDATABLOB_BLOB, TRY_TPM1X("load sealed object from blob", Tspi_SetAttribData(sealed_object, TSS_TSPATTRIB_ENCDATA_BLOB, TSS_TSPATTRIB_ENCDATABLOB_BLOB,
handle.sealed_object_blob_len, handle.sealed_object_blob)); handle.sealed_object_blob_len, handle.sealed_object_blob));
char what_for[ZFS_MAX_DATASET_NAME_LEN + 512 + 1];
// %s=dataset name, then TPM1.x. noun for "Enter passphrase for" prompt
snprintf(what_for, sizeof(what_for), gettext("%s %s wrapping key"), zfs_get_name(dataset), THIS_BACKEND);
uint8_t * loaded_wrap_key{}; uint8_t * loaded_wrap_key{};
uint32_t loaded_wrap_key_len{}; uint32_t loaded_wrap_key_len{};
TRY_MAIN(try_policy_or_passphrase("unseal wrapping key", "wrapping key", parent_key_policy, TRY_MAIN(try_policy_or_passphrase("unseal wrapping key", what_for, parent_key_policy,
[&] { return Tspi_Data_Unseal(sealed_object, parent_key, &loaded_wrap_key_len, &loaded_wrap_key); })); [&] { return Tspi_Data_Unseal(sealed_object, parent_key, &loaded_wrap_key_len, &loaded_wrap_key); }));
if(loaded_wrap_key_len != sizeof(wrap_key)) { if(loaded_wrap_key_len != sizeof(wrap_key)) {
fprintf(stderr, "Wrong sealed data length (%u != %zu):", loaded_wrap_key_len, sizeof(wrap_key)); // 0A1234... follows
fprintf(stderr, gettext("Wrong sealed data length (%u != %zu): "), loaded_wrap_key_len, sizeof(wrap_key));
for(auto i = 0u; i < loaded_wrap_key_len; ++i) for(auto i = 0u; i < loaded_wrap_key_len; ++i)
fprintf(stderr, "%02X", loaded_wrap_key[i]); fprintf(stderr, "%02hhX", loaded_wrap_key[i]);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
return __LINE__; return __LINE__;
} }

View File

@ -1,10 +1,6 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <libzfs.h>
// #include <sys/zio_crypt.h>
#define WRAPPING_KEY_LEN 32
#include <stdio.h> #include <stdio.h>
#include "../fd.hpp" #include "../fd.hpp"
@ -19,13 +15,28 @@
int main(int argc, char ** argv) { int main(int argc, char ** argv) {
const char * backup{}; const char * backup{};
TPML_PCR_SELECTION pcrs{};
bool allow_PCR_or_pass{};
return do_main( return do_main(
argc, argv, "b:", "[-b backup-file]", [&](auto) { backup = optarg; }, argc, argv, "b:P:A", gettext_noop("[-b backup-file] [-P algorithm:PCR[,PCR]…[+algorithm:PCR[,PCR]…]… [-A]]"),
[&](auto o) {
switch(o) {
case 'b':
return backup = optarg, 0;
case 'P':
return tpm2_parse_pcrs(optarg, pcrs);
case 'A':
return allow_PCR_or_pass = true, 0;
default:
__builtin_unreachable();
}
},
[&](auto dataset) { [&](auto dataset) {
REQUIRE_KEY_LOADED(dataset); REQUIRE_KEY_LOADED(dataset);
// https://software.intel.com/content/www/us/en/develop/articles/code-sample-protecting-secret-data-and-keys-using-intel-platform-trust-technology.html // https://software.intel.com/content/www/us/en/develop/articles/code-sample-protecting-secret-data-and-keys-using-intel-platform-trust-technology.html
// https://tpm2-software.github.io/2020/04/13/Disk-Encryption.html#pcr-policy-authentication---access-control-of-sealed-pass-phrase-on-tpm2-with-pcr-sealing
// tssstartup // tssstartup
// tpm2_createprimary -Q --hierarchy=o --key-context=prim.ctx // tpm2_createprimary -Q --hierarchy=o --key-context=prim.ctx
// cat /tmp/sk | tpm2_create --hash-algorithm=sha256 --public=seal.pub --private=seal.priv --sealing-input=- --parent-context=prim.ctx // cat /tmp/sk | tpm2_create --hash-algorithm=sha256 --public=seal.pub --private=seal.priv --sealing-input=- --parent-context=prim.ctx
@ -35,44 +46,57 @@ int main(int argc, char ** argv) {
// persistent-handle: 0x81000001 // persistent-handle: 0x81000001
// //
// tpm2_unseal -Q --object-context=0x81000000 // tpm2_unseal -Q --object-context=0x81000000
//
// For PCRs:
// tpm2_startauthsession --session=session.ctx
// tpm2_policypcr -S session.ctx -l 'sha512:7+sha256:10' -L 5-10.policy3
// tpm2_flushcontext session.ctx; rm session.ctx
// + tpm2_create{,primary} gain -l 'sha512:7+sha256:10', tpm2_create gains -L 5-10.policy3
//
// tpm2_unseal -p pcr:'sha512:7+sha256:10' --object-context=0x81000000
// or, longhand:
// tpm2_startauthsession --policy-session --session=session3.ctx
// tpm2_policypcr --session=session3.ctx --pcr-list='sha512:7+sha256:10'
// tpm2_unseal -p session:session3.ctx --object-context=0x81000000
// tpm2_flushcontext session3.ctx; rm session3.ctx
return with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { return with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) {
TRY_MAIN(verify_backend(dataset, THIS_BACKEND, [&](auto previous_handle_s) { TRY_MAIN(verify_backend(dataset, THIS_BACKEND, [&](auto previous_handle_s) {
TPMI_DH_PERSISTENT previous_handle{}; TPMI_DH_PERSISTENT previous_handle{};
if(parse_int(previous_handle_s, previous_handle)) if(tpm2_parse_prop(zfs_get_name(dataset), previous_handle_s, previous_handle, nullptr))
fprintf(stderr, "Couldn't parse previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c %s\" or equivalent!\n", fprintf(stderr,
gettext("Couldn't parse previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c %s\" or equivalent!\n"),
zfs_get_name(dataset), previous_handle_s); zfs_get_name(dataset), previous_handle_s);
else { else {
if(tpm2_free_persistent(tpm2_ctx, tpm2_session, previous_handle)) if(tpm2_free_persistent(tpm2_ctx, tpm2_session, previous_handle))
fprintf(stderr, "Couldn't free previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c 0x%X\" or equivalent!\n", fprintf(
zfs_get_name(dataset), previous_handle); stderr,
gettext("Couldn't free previous persistent handle for dataset %s. You might need to run \"tpm2_evictcontrol -c 0x%X\" or equivalent!\n"),
zfs_get_name(dataset), previous_handle);
} }
})); }));
uint8_t wrap_key[WRAPPING_KEY_LEN]; uint8_t wrap_key[WRAPPING_KEY_LEN];
TPMI_DH_PERSISTENT persistent_handle{};
TRY_MAIN(tpm2_generate_rand(tpm2_ctx, wrap_key, sizeof(wrap_key))); TRY_MAIN(tpm2_generate_rand(tpm2_ctx, wrap_key, sizeof(wrap_key)));
if(backup) if(backup)
TRY_MAIN(write_exact(backup, wrap_key, sizeof(wrap_key), 0400)); TRY_MAIN(write_exact(backup, wrap_key, sizeof(wrap_key), 0400));
TRY_MAIN(tpm2_seal(tpm2_ctx, tpm2_session, persistent_handle, tpm2_creation_metadata(zfs_get_name(dataset)), wrap_key, sizeof(wrap_key))); TPMI_DH_PERSISTENT persistent_handle{};
TRY_MAIN(tpm2_seal(zfs_get_name(dataset), tpm2_ctx, tpm2_session, persistent_handle, pcrs, allow_PCR_or_pass, wrap_key, sizeof(wrap_key)));
bool ok = false; // Try to free the persistent handle if we're unsuccessful in actually using it later on bool ok = false; // Try to free the persistent handle if we're unsuccessful in actually using it later on
quickscope_wrapper persistent_clearer{[&] { quickscope_wrapper persistent_clearer{[&] {
if(!ok && tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle)) if(!ok && tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle))
fprintf(stderr, "Couldn't free persistent handle. You might need to run \"tpm2_evictcontrol -c 0x%X\" or equivalent!\n", persistent_handle); fprintf(stderr, gettext("Couldn't free persistent handle. You might need to run \"tpm2_evictcontrol -c 0x%X\" or equivalent!\n"),
persistent_handle);
if(!ok) if(!ok)
clear_key_props(dataset); clear_key_props(dataset);
}}; }};
{ {
char persistent_handle_s[2 + sizeof(persistent_handle) * 2 + 1]; char * prop{};
if(auto written = snprintf(persistent_handle_s, sizeof(persistent_handle_s), "0x%X", persistent_handle); TRY_MAIN(tpm2_unparse_prop(persistent_handle, pcrs, &prop));
written < 0 || written >= static_cast<int>(sizeof(persistent_handle_s))) { quickscope_wrapper prop_deleter{[&] { free(prop); }};
fprintf(stderr, "Truncated persistent_handle name? %d/%zu\n", written, sizeof(persistent_handle_s)); TRY_MAIN(set_key_props(dataset, THIS_BACKEND, prop));
return __LINE__;
}
TRY_MAIN(set_key_props(dataset, THIS_BACKEND, persistent_handle_s));
} }
TRY_MAIN(change_key(dataset, wrap_key)); TRY_MAIN(change_key(dataset, wrap_key));
@ -80,5 +104,10 @@ int main(int argc, char ** argv) {
ok = true; ok = true;
return 0; return 0;
}); });
},
[&]() {
if(allow_PCR_or_pass && !pcrs.count)
return __LINE__;
return 0;
}); });
} }

View File

@ -12,6 +12,6 @@ int main(int argc, char ** argv) {
TPMI_DH_PERSISTENT persistent_handle{}; TPMI_DH_PERSISTENT persistent_handle{};
return do_clear_main( return do_clear_main(
argc, argv, THIS_BACKEND, argc, argv, THIS_BACKEND,
[&](auto dataset, auto persistent_handle_s) { return tpm2_parse_handle(zfs_get_name(dataset), persistent_handle_s, persistent_handle); }, [&](auto dataset, auto persistent_handle_s) { return tpm2_parse_prop(zfs_get_name(dataset), persistent_handle_s, persistent_handle, nullptr); },
[&] { return with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { return tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle); }); }); [&] { return with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { return tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle); }); });
} }

View File

@ -1,10 +1,6 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <libzfs.h>
// #include <sys/zio_crypt.h>
#define WRAPPING_KEY_LEN 32
#include <stdio.h> #include <stdio.h>
#include "../fd.hpp" #include "../fd.hpp"
@ -25,12 +21,13 @@ int main(int argc, char ** argv) {
TRY_MAIN(parse_key_props(dataset, THIS_BACKEND, handle_s)); TRY_MAIN(parse_key_props(dataset, THIS_BACKEND, handle_s));
TPMI_DH_PERSISTENT handle{}; TPMI_DH_PERSISTENT handle{};
TRY_MAIN(tpm2_parse_handle(zfs_get_name(dataset), handle_s, handle)); TPML_PCR_SELECTION pcrs{};
TRY_MAIN(tpm2_parse_prop(zfs_get_name(dataset), handle_s, handle, &pcrs));
uint8_t wrap_key[WRAPPING_KEY_LEN]; uint8_t wrap_key[WRAPPING_KEY_LEN];
TRY_MAIN(with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { TRY_MAIN(with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) {
TRY_MAIN(tpm2_unseal(tpm2_ctx, tpm2_session, handle, wrap_key, sizeof(wrap_key))); TRY_MAIN(tpm2_unseal(zfs_get_name(dataset), tpm2_ctx, tpm2_session, handle, pcrs, wrap_key, sizeof(wrap_key)));
return 0; return 0;
})); }));

View File

@ -13,7 +13,7 @@
({ \ ({ \
auto _try_ret = (__VA_ARGS__); \ auto _try_ret = (__VA_ARGS__); \
if(cond_pre _try_ret cond_post) { \ if(cond_pre _try_ret cond_post) { \
if constexpr(what != nullptr) \ if(what != nullptr) \
fprintf(stderr, "Couldn't %s: %s\n", static_cast<const char *>(what), strerr(err_src)); \ fprintf(stderr, "Couldn't %s: %s\n", static_cast<const char *>(what), strerr(err_src)); \
return err_ret; \ return err_ret; \
} \ } \
@ -30,4 +30,4 @@ struct quickscope_wrapper {
}; };
template <class F> template <class F>
quickscope_wrapper(F)->quickscope_wrapper<F>; quickscope_wrapper(F) -> quickscope_wrapper<F>;

View File

@ -6,10 +6,10 @@
#include "main.hpp" #include "main.hpp"
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <utility>
/// Matches libzfs /// Matches libzfs
@ -18,7 +18,7 @@
int filled_fd(int & fd, const void * with, size_t with_len) { int filled_fd(int & fd, const void * with, size_t with_len) {
int pipes[2]; int pipes[2];
TRY("create buffer pipe", pipe(pipes)); TRY("create buffer pipe", pipe2(pipes, O_CLOEXEC));
quickscope_wrapper pipes_w_deleter{[=] { close(pipes[1]); }}; quickscope_wrapper pipes_w_deleter{[=] { close(pipes[1]); }};
fd = pipes[0]; fd = pipes[0];
@ -33,26 +33,9 @@ int filled_fd(int & fd, const void * with, size_t with_len) {
} }
int read_exact(const char * path, void * data, size_t len) {
auto infd = TRY("open input file", open(path, O_RDONLY));
quickscope_wrapper infd_deleter{[=] { close(infd); }};
while(len)
if(const auto rd = TRY("read input file", read(infd, data, len))) {
len -= rd;
data = static_cast<char *>(data) + rd;
} else {
fprintf(stderr, "Couldn't read %zu bytes from input file: too short\n", len);
return __LINE__;
}
return 0;
}
int write_exact(const char * path, const void * data, size_t len, mode_t mode) { int write_exact(const char * path, const void * data, size_t len, mode_t mode) {
auto outfd = TRY("create output file", open(path, O_WRONLY | O_CREAT | O_EXCL, mode)); auto outfd = TRY("create output file", open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
quickscope_wrapper infd_deleter{[=] { close(outfd); }}; quickscope_wrapper outfd_deleter{[=] { close(outfd); }};
while(len) { while(len) {
const auto rd = TRY("write to output file", write(outfd, data, len)); const auto rd = TRY("write to output file", write(outfd, data, len));
@ -64,6 +47,79 @@ int write_exact(const char * path, const void * data, size_t len, mode_t mode) {
} }
#define TRY_HELPER(what, ...) TRY_GENERIC(what, , == -1, errno, -1, strerror, __VA_ARGS__)
/// TRY_MAIN rules, plus -1 for ENOENT
static int get_key_material_helper(const char * helper, const char * whom, bool again, bool newkey, uint8_t *& buf, size_t & len_out) {
int pipes[2];
TRY("create IPC pipe", pipe2(pipes, O_CLOEXEC));
quickscope_wrapper pipes_r_deleter{[=] { close(pipes[0]); }};
if(auto pid = TRY_HELPER("create child", fork()); pid == 0) { // child
dup2(pipes[1], 1);
const char * fmt;
if(!newkey && !again)
fmt = gettext("Passphrase for %s");
else if(!newkey && again)
fmt = gettext("Passphrase for %s (again)");
else if(newkey && !again)
fmt = gettext("New passphrase for %s");
else // newkey && again
fmt = gettext("New passphrase for %s (again)");
char * msg;
if(asprintf(&msg, fmt, whom) == -1)
msg = const_cast<char *>(whom);
execl("/bin/sh", "sh", "-c", helper, helper, msg, whom, newkey ? "new" : "", again ? "again" : "", nullptr);
int exec_err = errno;
fprintf(stderr, "exec(/bin/sh): %s\n", strerror(errno));
_exit(exec_err == ENOENT ? 127 : 126);
}
close(pipes[1]);
buf = nullptr;
len_out = 0;
for(size_t len_cap = 0;;) { // 99% of cases this is 1 allocation and 1 read
if(len_out == len_cap) {
len_cap += 120;
buf = TRY_PTR("allocate passphrase", static_cast<uint8_t *>(realloc(buf, len_cap)));
}
ssize_t rd;
while((rd = read(pipes[0], buf + len_out, len_cap - len_out)) == -1 && errno == EINTR)
;
TRY("read passphrase from helper", rd);
if(rd == 0)
break;
len_out += rd;
}
if(len_out && buf[len_out - 1] == '\n')
--len_out;
int err, ret;
while((ret = wait(&err)) == -1 && errno == EINTR)
;
TRY("wait for helper", ret);
if(WIFEXITED(err))
switch(WEXITSTATUS(err)) {
case 0:
return 0;
case 127: // ENOENT, error already written by shell or child
return -1;
default:
// exit status
return fprintf(stderr, gettext("Helper '%s' failed with %d.\n"), helper, WEXITSTATUS(err)), __LINE__;
}
else
return fprintf(stderr, gettext("Helper '%s' died to signal %d: %s.\n"), helper, WTERMSIG(err), strsignal(WTERMSIG(err))), __LINE__;
}
/// Adapted from src:zfs's lib/libzfs/libzfs_crypto.c#get_key_material_raw() /// Adapted from src:zfs's lib/libzfs/libzfs_crypto.c#get_key_material_raw()
static int get_key_material_raw(const char * whom, bool again, bool newkey, uint8_t *& buf, size_t & len_out) { static int get_key_material_raw(const char * whom, bool again, bool newkey, uint8_t *& buf, size_t & len_out) {
static int caught_interrupt; static int caught_interrupt;
@ -77,18 +133,33 @@ static int get_key_material_raw(const char * whom, bool again, bool newkey, uint
if(from_tty) { if(from_tty) {
// Handle SIGINT and ignore SIGSTP. // Handle SIGINT and ignore SIGSTP.
// This is necessary to restore the state of the terminal. // This is necessary to restore the state of the terminal.
struct sigaction act {}; struct sigaction act{};
sigemptyset(&act.sa_mask); sigemptyset(&act.sa_mask);
caught_interrupt = 0; caught_interrupt = 0;
act.sa_handler = [](auto sig) { caught_interrupt = sig; }; act.sa_handler = [](auto sig) {
caught_interrupt = sig;
struct termios cur;
tcgetattr(0, &cur);
fprintf(stderr, "%s^%c\n", (cur.c_cc[VINTR] & 0x80) ? "M-" : "", ((cur.c_cc[VINTR] & 0x7F) == 0x7F) ? '?' : ('@' + (cur.c_cc[VINTR] & 0x7F)));
};
sigaction(SIGINT, &act, &osigint); sigaction(SIGINT, &act, &osigint);
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
sigaction(SIGTSTP, &act, &osigtstp); sigaction(SIGTSTP, &act, &osigtstp);
// Prompt for the key // Prompt for the key
printf("%s %spassphrase for %s: ", again ? "Re-enter" : "Enter", newkey ? "new " : "", whom); const char * fmt;
if(!again && !newkey)
fmt = gettext("Enter passphrase for %s: ");
else if(!again && newkey)
fmt = gettext("Enter passphrase for new %s: ");
else if(again && !newkey)
fmt = gettext("Re-enter passphrase for %s: ");
else // again && newkey
fmt = gettext("Re-enter passphrase for new %s: ");
printf(fmt, whom);
fflush(stdout); fflush(stdout);
// Disable the terminal echo for key input // Disable the terminal echo for key input
@ -107,7 +178,7 @@ static int get_key_material_raw(const char * whom, bool again, bool newkey, uint
// If we caught a signal, re-throw it now // If we caught a signal, re-throw it now
if(caught_interrupt != 0) if(caught_interrupt != 0)
kill(getpid(), caught_interrupt); raise(caught_interrupt);
// Print the newline that was not echoed // Print the newline that was not echoed
putchar('\n'); putchar('\n');
@ -121,7 +192,7 @@ static int get_key_material_raw(const char * whom, bool again, bool newkey, uint
auto bytes = getline((char **)&buf, &buflen, stdin); auto bytes = getline((char **)&buf, &buflen, stdin);
switch(bytes) { switch(bytes) {
case -1: case -1:
if(errno != 0) if(errno)
TRY("read in passphrase", bytes); TRY("read in passphrase", bytes);
else // EOF else // EOF
bytes = 0; bytes = 0;
@ -130,10 +201,8 @@ static int get_key_material_raw(const char * whom, bool again, bool newkey, uint
break; break;
default: default:
// Trim ending newline, if any // Trim ending newline, if any
if(buf[bytes - 1] == '\n') { if(buf[bytes - 1] == '\n')
buf[bytes - 1] = '\0'; buf[--bytes] = '\0';
--bytes;
}
break; break;
} }
@ -141,12 +210,32 @@ static int get_key_material_raw(const char * whom, bool again, bool newkey, uint
return 0; return 0;
} }
#ifndef TZPFMS_PASSPHRASE_HELPER
#define TZPFMS_PASSPHRASE_HELPER
#endif
#define STRINGIFY_(...) #__VA_ARGS__
#define STRINGIFY(...) STRINGIFY_(__VA_ARGS__)
static int get_key_material_dispatch(const char * whom, bool again, bool newkey, uint8_t *& buf, size_t & len_out) {
static const char * helper{};
if(!helper)
helper = getenv("TZPFMS_PASSPHRASE_HELPER") ?: STRINGIFY(TZPFMS_PASSPHRASE_HELPER);
if(*helper) {
if(auto err = get_key_material_helper(helper, whom, again, newkey, buf, len_out); err != -1)
return err;
else
helper = "";
}
return get_key_material_raw(whom, again, newkey, buf, len_out);
}
int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len) { int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len) {
TRY_MAIN(get_key_material_raw(whom, false, false, buf, len_out)); TRY_MAIN(get_key_material_dispatch(whom, false, false, buf, len_out));
if(len_out <= max_len) if(len_out <= max_len)
return 0; return 0;
fprintf(stderr, "Passphrase too long (max %zu)\n", max_len); fprintf(stderr, gettext("Passphrase too long (max %zu)\n"), max_len);
free(buf); free(buf);
buf = nullptr; buf = nullptr;
len_out = 0; len_out = 0;
@ -156,32 +245,25 @@ int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, s
int read_new_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len) { int read_new_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len) {
uint8_t * first_passphrase{}; uint8_t * first_passphrase{};
size_t first_passphrase_len{}; size_t first_passphrase_len{};
TRY_MAIN(get_key_material_raw(whom, false, true, first_passphrase, first_passphrase_len)); TRY_MAIN(get_key_material_dispatch(whom, false, true, first_passphrase, first_passphrase_len));
quickscope_wrapper first_passphrase_deleter{[&] { free(first_passphrase); }}; quickscope_wrapper first_passphrase_deleter{[&] { free(first_passphrase); }};
if(first_passphrase_len != 0 && first_passphrase_len < MIN_PASSPHRASE_LEN) { if(first_passphrase_len != 0 && first_passphrase_len < MIN_PASSPHRASE_LEN)
fprintf(stderr, "Passphrase too short (min %u)\n", MIN_PASSPHRASE_LEN); return fprintf(stderr, gettext("Passphrase too short (min %u)\n"), MIN_PASSPHRASE_LEN), __LINE__;
return __LINE__; if(first_passphrase_len > max_len)
} return fprintf(stderr, gettext("Passphrase too long (max %zu)\n"), max_len), __LINE__;
if(first_passphrase_len > max_len) {
fprintf(stderr, "Passphrase too long (max %zu)\n", max_len);
return __LINE__;
}
uint8_t * second_passphrase{}; uint8_t * second_passphrase{};
size_t second_passphrase_len{}; size_t second_passphrase_len{};
TRY_MAIN(get_key_material_raw(whom, true, true, second_passphrase, second_passphrase_len)); TRY_MAIN(get_key_material_dispatch(whom, true, true, second_passphrase, second_passphrase_len));
quickscope_wrapper second_passphrase_deleter{[&] { free(second_passphrase); }}; quickscope_wrapper second_passphrase_deleter{[&] { free(second_passphrase); }};
if(second_passphrase_len != first_passphrase_len || memcmp(first_passphrase, second_passphrase, first_passphrase_len)) { if(second_passphrase_len != first_passphrase_len || memcmp(first_passphrase, second_passphrase, first_passphrase_len))
fprintf(stderr, "Provided keys do not match.\n"); return fprintf(stderr, gettext("Provided passphrases do not match.\n")), __LINE__;
return __LINE__;
}
if(second_passphrase_len) { if(second_passphrase_len)
buf = second_passphrase; buf = std::exchange(second_passphrase, nullptr);
second_passphrase = nullptr; else
} else
buf = nullptr; buf = nullptr;
len_out = second_passphrase_len; len_out = second_passphrase_len;

View File

@ -4,38 +4,32 @@
#pragma once #pragma once
#include "common.hpp"
#include <unistd.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <unistd.h>
template <class F> template <class F>
int with_stdin_at(int fd, F && what) { int with_stdin_at(int fd, F && what) {
auto stdin_saved = TRY("dup() stdin", dup(0)); auto stdin_saved = dup(0);
quickscope_wrapper stdin_saved_deleter{[=] { close(stdin_saved); }};
TRY("dup2() onto stdin", dup2(fd, 0)); dup2(fd, 0);
clearerr(stdin);
int ret = what();
if(int ret = what()) { dup2(stdin_saved, 0);
dup2(stdin_saved, 0); close(stdin_saved);
return ret; return ret;
}
TRY("dup2() stdin back onto stdin", dup2(stdin_saved, 0));
return 0;
} }
/// with_len may not exceed pipe capacity (64k by default) /// with_len may not exceed pipe capacity (64k by default)
extern int filled_fd(int & fd, const void * with, size_t with_len); extern int filled_fd(int & fd, const void * with, size_t with_len);
/// Read exactly len bytes from path into data, or error
extern int read_exact(const char * path, void * data, size_t len);
/// Write exactly len bytes from data into path, or error /// Write exactly len bytes from data into path, or error
extern int write_exact(const char * path, const void * data, size_t len, mode_t mode); extern int write_exact(const char * path, const void * data, size_t len, mode_t mode);
/// Prompt for passphrase for whom the user knows, up to max_len bytes /// Prompt for passphrase for whom the user knows, up to max_len bytes
extern int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len = SIZE_MAX); extern int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len = SIZE_MAX);
/// Prompt twive for passphrase for whom the user is setting /// Prompt twice for passphrase for whom the user is setting
extern int read_new_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len = SIZE_MAX); extern int read_new_passphrase(const char * whom, uint8_t *& buf, size_t & len_out, size_t max_len = SIZE_MAX);

View File

@ -5,10 +5,13 @@
#include "common.hpp" #include "common.hpp"
#include <libintl.h>
#include <libzfs.h> #include <libzfs.h>
#include <locale.h>
#include <stdlib.h> #include <stdlib.h>
#include <type_traits> #include <type_traits>
#include <unistd.h> #include <unistd.h>
#define gettext_noop(s) s
#define TRY_PTR(what, ...) TRY_GENERIC(what, !, , errno, __LINE__, strerror, __VA_ARGS__) #define TRY_PTR(what, ...) TRY_GENERIC(what, !, , errno, __LINE__, strerror, __VA_ARGS__)
@ -19,70 +22,80 @@
} while(0) } while(0)
template <class G, class M> template <class G, class M, class V = int (*)()>
int do_bare_main(int argc, char ** argv, const char * getoptions, const char * usage, const char * dataset_usage, G && getoptfn, M && main) { static int do_bare_main(
const auto libz = TRY_PTR("initialise libzfs", libzfs_init()); int argc, char ** argv, const char * getoptions, const char * usage, const char * dataset_usage, G && getoptfn, M && main,
quickscope_wrapper libz_deleter{[=] { libzfs_fini(libz); }}; V && validate = [] { return 0; }) {
setlocale(LC_ALL, "");
bindtextdomain("tzpfms", "/usr/share/locale");
// bindtextdomain("tzpfms", "out/locale");
textdomain("tzpfms");
const auto libz = TRY_PTR("initialise libzfs", libzfs_init());
libzfs_print_on_error(libz, B_TRUE); libzfs_print_on_error(libz, B_TRUE);
#if __GLIBC__ auto gopts = reinterpret_cast<char *>(alloca(strlen(getoptions) + 2 + 1));
setenv("POSIXLY_CORRECT", "1", true); gopts[0] = 'h', gopts[1] = 'V';
#endif strcpy(gopts + 2, getoptions);
auto gopts = reinterpret_cast<char *>(TRY_PTR("allocate options string", alloca(strlen(getoptions) + 2 + 1)));
snprintf(gopts, strlen(getoptions) + 2 + 1, "%shV", getoptions);
for(int opt; (opt = getopt(argc, argv, gopts)) != -1;) for(int opt; (opt = getopt(argc, argv, gopts)) != -1;)
switch(opt) { switch(opt) {
case '?': case '?':
case 'h': case 'h':
fprintf(opt == 'h' ? stdout : stderr, "Usage: %s [-hV] %s%s%s\n", argv[0], usage, strlen(usage) ? " " : "", dataset_usage); fprintf(opt == 'h' ? stdout : stderr, gettext("usage: %s [-hV] %s%s%s\n"), argv[0], *usage ? gettext(usage) : "", *usage ? " " : "",
gettext(dataset_usage));
return opt == 'h' ? 0 : __LINE__; return opt == 'h' ? 0 : __LINE__;
case 'V': case 'V':
printf("tzpfms version %s\n", TZPFMS_VERSION); puts("tzpfms " TZPFMS_VERSION);
return 0; return 0;
default: default:
if constexpr(std::is_same_v<std::invoke_result_t<G, decltype(opt)>, void>) if constexpr(std::is_same_v<std::invoke_result_t<G, decltype(opt)>, void>)
getoptfn(opt); getoptfn(opt);
else if constexpr(std::is_arithmetic_v<std::invoke_result_t<G, decltype(opt)>>)
TRY_MAIN(getoptfn(opt));
else { else {
if(auto err = getoptfn(opt)) { if(auto err = getoptfn(opt))
fprintf(stderr, "Usage: %s [-hV] %s%s%s\n", argv[0], usage, strlen(usage) ? " " : "", dataset_usage); return fprintf(stderr, gettext("usage: %s [-hV] %s%s%s\n"), argv[0], *usage ? gettext(usage) : "", *usage ? " " : "", gettext(dataset_usage)), err;
return err;
}
} }
} }
if(auto err = validate())
return fprintf(stderr, gettext("usage: %s [-hV] %s%s%s\n"), argv[0], *usage ? gettext(usage) : "", *usage ? " " : "", gettext(dataset_usage)), err;
return main(libz); return main(libz);
} }
template <class G, class M> template <class G, class M, class V = int (*)()>
int do_main(int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main) { static int do_main(int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main, V && validate = [] { return 0; }) {
return do_bare_main(argc, argv, getoptions, usage, "<dataset>", getoptfn, [&](auto libz) { return do_bare_main(
if(optind >= argc) { // as-in argument in a usage string
fprintf(stderr, argc, argv, getoptions, usage, gettext_noop("dataset"), getoptfn,
"No dataset to act on?\n" [&](auto libz) {
"Usage: %s [-hV] %s%s<dataset>\n", if(!*(argv + optind))
argv[0], usage, strlen(usage) ? " " : ""); return fprintf(stderr,
return __LINE__; gettext("No dataset to act on?\n"
} "usage: %s [-hV] %s%sdataset\n"),
auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)); argv[0], usage, strlen(usage) ? " " : ""),
quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }}; __LINE__;
if(*(argv + optind + 1))
return fprintf(stderr, gettext("usage: %s [-hV] %s%sdataset\n"), argv[0], usage, strlen(usage) ? " " : ""), __LINE__;
auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME));
quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }};
{ {
char encryption_root[MAXNAMELEN]; char encryption_root[MAXNAMELEN];
boolean_t dataset_is_root; boolean_t dataset_is_root;
TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root)); TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root));
if(!dataset_is_root && !strlen(encryption_root)) { if(!dataset_is_root && !strlen(encryption_root))
fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)); return fprintf(stderr, gettext("Dataset %s not encrypted?\n"), zfs_get_name(dataset)), __LINE__;
return __LINE__; else if(!dataset_is_root) {
} else if(!dataset_is_root) { fprintf(stderr, gettext("Using dataset %s's encryption root %s instead.\n"), zfs_get_name(dataset), encryption_root);
fprintf(stderr, "Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root); zfs_close(dataset);
zfs_close(dataset); dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME));
dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)); }
} }
}
return main(dataset); return main(dataset);
}); },
validate);
} }

View File

@ -13,7 +13,7 @@ int do_clear_main(int argc, char ** argv, const char * this_backend, H && handle
return do_main( return do_main(
argc, argv, "", "", [&](auto) {}, argc, argv, "", "", [&](auto) {},
[&](auto dataset) { [&](auto dataset) {
REQUIRE_KEY_LOADED(dataset); REQUIRE_KEY_LOADED(dataset);
char * handle_s{}; char * handle_s{};
TRY_MAIN(parse_key_props(dataset, this_backend, handle_s)); TRY_MAIN(parse_key_props(dataset, this_backend, handle_s));

View File

@ -1,24 +1,33 @@
/* SPDX-License-Identifier: MIT */ // SPDX-License-Identifier: 0BSD
#pragma once #pragma once
#include <charconv> #include <charconv>
#include <errno.h>
#include <limits>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
template <class T> template <class T>
int parse_int(const char * what, T & out) { bool parse_uint(const char * val, T & out) {
int base = 10; if(val[0] == '\0')
if(!strncmp(what, "0x", 2) || !strncmp(what, "0X", 2)) { return errno = EINVAL, false;
base = 16; if(val[0] == '-')
what += 2; return errno = ERANGE, false;
}
if(std::from_chars(what, what + strlen(what), out, base).ptr == what) char * end{};
return __LINE__; errno = 0;
else auto res = strtoull(val, &end, 0);
return 0; out = res;
if(errno)
return false;
if(res > std::numeric_limits<T>::max())
return errno = ERANGE, false;
if(*end != '\0')
return errno = EINVAL, false;
return true;
} }

View File

@ -4,7 +4,9 @@
#include "tpm1x.hpp" #include "tpm1x.hpp"
#include "main.hpp" #include "main.hpp"
#include "parse.hpp"
#include <algorithm>
#include <stdlib.h> #include <stdlib.h>
@ -23,10 +25,8 @@ tpm1x_handle::~tpm1x_handle() {
int tpm1x_parse_handle(const char * dataset_name, char * handle_s, tpm1x_handle & handle) { int tpm1x_parse_handle(const char * dataset_name, char * handle_s, tpm1x_handle & handle) {
auto midpoint = strchr(handle_s, ':'); auto midpoint = strchr(handle_s, ':');
if(!midpoint) { if(!midpoint)
fprintf(stderr, "Dataset %s's handle %s not valid.\n", dataset_name, handle_s); return fprintf(stderr, "Dataset %s's handle %s not valid.\n", dataset_name, handle_s), __LINE__;
return __LINE__;
}
*midpoint = '\0'; *midpoint = '\0';
auto parent_key_wide_blob = handle_s; auto parent_key_wide_blob = handle_s;
@ -82,6 +82,41 @@ int tpm1x_prep_sealed_object(TSS_HCONTEXT ctx, TSS_HOBJECT & sealed_object, TSS_
return 0; return 0;
} }
/// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-1-Design-Principles_v1.2_rev116_01032011.pdf sections 4.4.7, .8 (L1228-1236):
/// > 7. A TPM implementation MUST provide 16 or more independent PCRs. These PCRs areidentified by index and MUST be numbered from 0 (that is, PCR0 through
/// > PCR15 are required for TCG compliance). Vendors MAY implement more registers for general-purpose use. Extra registers MUST be numbered contiguously
/// > from16 up to max 1,where max is the maximum offered by the TPM
/// > 8. The TCG-protected capabilities that expose and modify the PCRs use a 32-bit index,indicating the maximum usable PCR index. However, TCG reserves
/// > register indices 230and higher for later versions of the specification. A TPM implementation MUST NOTprovide registers with indices greater than or
/// > equal to 230.
int tpm1x_parse_pcrs(char * arg, uint32_t *& pcrs, size_t & pcrs_len) {
size_t out_cap = 16;
pcrs = reinterpret_cast<uint32_t *>(TRY_PTR("allocate PCR list", calloc(out_cap, sizeof(uint32_t))));
char * sv{};
for(arg = strtok_r(arg, ", ", &sv); arg; arg = strtok_r(nullptr, ", ", &sv)) {
uint32_t pcr;
if(!parse_uint(arg, pcr))
// user-supplied parameter, errno
return fprintf(stderr, gettext("PCR %s: %s\n"), arg, strerror(errno)), __LINE__;
if(pcr >= 230)
// user-supplied parameter
return fprintf(stderr, gettext("PCR %s: too large (max 229).\n"), arg), __LINE__;
auto idx = std::upper_bound(pcrs, pcrs + pcrs_len, pcr) - pcrs;
if(!idx || pcrs[idx - 1] != pcr) {
if(pcrs_len >= out_cap)
pcrs = reinterpret_cast<uint32_t *>(TRY_PTR("allocate PCR list", reallocarray(pcrs, out_cap *= 2, sizeof(uint32_t))));
memmove(pcrs + idx + 1, pcrs + idx, (pcrs_len - idx) * sizeof(uint32_t));
pcrs[idx] = pcr;
++pcrs_len;
}
}
return 0;
}
/// This feels suboptimal somehow, and yet /// This feels suboptimal somehow, and yet
static int fromxchar(uint8_t & out, char c) { static int fromxchar(uint8_t & out, char c) {

View File

@ -9,6 +9,7 @@
#include "main.hpp" #include "main.hpp"
#include <stdlib.h> #include <stdlib.h>
#include <sys/mman.h>
#include <tss/platform.h> #include <tss/platform.h>
#include <tss/tspi.h> #include <tss/tspi.h>
@ -22,13 +23,15 @@
/// Used as default secret if passphrase wasn't provided for wrapping key for the sealed object /// Used as default secret if passphrase wasn't provided for wrapping key for the sealed object
// I just got this out of /dev/random // I just got this out of /dev/random, for greppers: CE4CF677875B5EB8993591D5A9AF1ED24A3A8736
static const constexpr uint8_t parent_key_secret[TPM_SHA1_160_HASH_LEN]{0xCE, 0x4C, 0xF6, 0x77, 0x87, 0x5B, 0x5E, 0xB8, 0x99, 0x35, static const constexpr uint8_t parent_key_secret[TPM_SHA1_160_HASH_LEN]{0xCE, 0x4C, 0xF6, 0x77, 0x87, 0x5B, 0x5E, 0xB8, 0x99, 0x35,
0x91, 0xD5, 0xA9, 0xAF, 0x1E, 0xD2, 0x4A, 0x3A, 0x87, 0x36}; 0x91, 0xD5, 0xA9, 0xAF, 0x1E, 0xD2, 0x4A, 0x3A, 0x87, 0x36};
template <class F> template <class F>
int with_tpm1x_session(F && func) { int with_tpm1x_session(F && func) {
mlockall(MCL_CURRENT | MCL_FUTURE);
TSS_HCONTEXT ctx{}; // All memory lives as long as this does TSS_HCONTEXT ctx{}; // All memory lives as long as this does
TRY_TPM1X("create TPM context", Tspi_Context_Create(&ctx)); TRY_TPM1X("create TPM context", Tspi_Context_Create(&ctx));
@ -80,11 +83,8 @@ int try_policy_or_passphrase(const char * what, const char * what_for, TSS_HPOLI
} }
// TRY_TPM1X() unrolled because no constexpr/string-literal-template arguments until C++20, which is not supported by GCC 8, which we need for Buster // TRY_TPM1X() unrolled because no constexpr/string-literal-template arguments until C++20, which is not supported by GCC 8, which we need for Buster
if(err != TPM_SUCCESS) { if(err != TPM_SUCCESS)
fprintf(stderr, "Couldn't %s: %s\n", what, Trspi_Error_String(err)); return fprintf(stderr, "Couldn't %s: %s\n", what, Trspi_Error_String(err)), __LINE__;
return __LINE__;
}
return 0; return 0;
} }
@ -105,3 +105,6 @@ extern int tpm1x_parse_handle(const char * dataset_name, char * handle_s, tpm1x_
/// Create sealed object, assign a policy and a known secret to it. /// Create sealed object, assign a policy and a known secret to it.
extern int tpm1x_prep_sealed_object(TSS_HCONTEXT ctx, TSS_HOBJECT & sealed_object, TSS_HPOLICY & sealed_object_policy); extern int tpm1x_prep_sealed_object(TSS_HCONTEXT ctx, TSS_HOBJECT & sealed_object, TSS_HPOLICY & sealed_object_policy);
/// Parse a comma- or space-separated number list.
extern int tpm1x_parse_pcrs(char * arg, uint32_t *& pcrs, size_t & pcrs_len);

View File

@ -7,7 +7,13 @@
#include "parse.hpp" #include "parse.hpp"
#include <algorithm> #include <algorithm>
#include <time.h> #include <inttypes.h>
#include <iterator>
#define OPENSSL_SUPPRESS_DEPRECATED // SHA256_*(); supposedly replaced with EVP* but that's horseshit. we'll see how she turns out, given there's no reason
// given
#include <openssl/sha.h>
#include <optional>
#include <utility>
template <class F> template <class F>
@ -15,7 +21,7 @@ static int try_or_passphrase(const char * what, const char * what_for, ESYS_CONT
auto err = func(); auto err = func();
for(int i = 0; err == TPM2_RC_9 + valid_error && i < 3; ++i) { for(int i = 0; err == TPM2_RC_9 + valid_error && i < 3; ++i) {
if(i) if(i)
fprintf(stderr, "Couldn't %s: %s\n", what, Tss2_RC_Decode(err)); fprintf(stderr, "Couldn't %s: %s\n", gettext(what), Tss2_RC_Decode(err));
uint8_t * pass{}; uint8_t * pass{};
size_t pass_len{}; size_t pass_len{};
@ -31,37 +37,168 @@ static int try_or_passphrase(const char * what, const char * what_for, ESYS_CONT
} }
// TRY_TPM2() unrolled because no constexpr/string-literal-template arguments until C++20, which is not supported by GCC 8, which we need for Buster // TRY_TPM2() unrolled because no constexpr/string-literal-template arguments until C++20, which is not supported by GCC 8, which we need for Buster
if(err != TPM2_RC_SUCCESS) { if(err != TPM2_RC_SUCCESS)
fprintf(stderr, "Couldn't %s: %s\n", what, Tss2_RC_Decode(err)); return fprintf(stderr, "Couldn't %s: %s\n", gettext(what), Tss2_RC_Decode(err)), __LINE__;
return __LINE__; return 0;
} }
int tpm2_parse_prop(const char * dataset_name, char * handle_s, TPMI_DH_PERSISTENT & handle, TPML_PCR_SELECTION * pcrs) {
char * sv{};
if(!parse_uint(handle_s = strtok_r(handle_s, ";", &sv), handle))
return fprintf(stderr, "Dataset %s's handle %s: %s.\n", dataset_name, handle_s, strerror(errno)), __LINE__;
if(auto p = strtok_r(nullptr, ";", &sv); p && pcrs)
TRY_MAIN(tpm2_parse_pcrs(p, *pcrs));
return 0; return 0;
} }
TPM2B_DATA tpm2_creation_metadata(const char * dataset_name) { /// Extension of the table used by tpm2-tools (tpm2_create et al.), which only has "s{m,ha}3_XXX", not "s{m,ha}3-XXX", and does case-sentitive comparisons
TPM2B_DATA metadata{}; #define TPM2_HASH_ALGS_MAX_NAME_LEN 8 // sha3_512
static const constexpr struct tpm2_hash_algs_t {
TPM2_ALG_ID alg;
const char * names[2];
} tpm2_hash_algs[] = {{TPM2_ALG_SHA1, {"sha1"}},
{TPM2_ALG_SHA256, {"sha256"}},
{TPM2_ALG_SHA384, {"sha384"}},
{TPM2_ALG_SHA512, {"sha512"}},
{TPM2_ALG_SM3_256, {"sm3_256", "sm3-256"}},
{TPM2_ALG_SHA3_256, {"sha3_256", "sha3-256"}},
{TPM2_ALG_SHA3_384, {"sha3_384", "sha3-384"}},
{TPM2_ALG_SHA3_512, {"sha3_512", "sha3-512"}}};
const auto now = time(nullptr); static constexpr bool is_tpm2_hash_algs_sorted() {
const auto now_tm = localtime(&now); for(auto itr = std::begin(tpm2_hash_algs); itr != std::end(tpm2_hash_algs) - 1; ++itr)
metadata.size = snprintf((char *)metadata.buffer, sizeof(metadata.buffer), "%s %d-%02d-%02dT%02d:%02d:%02d %s", dataset_name, // if((itr + 1)->alg < itr->alg)
now_tm->tm_year + 1900, now_tm->tm_mon + 1, now_tm->tm_mday, now_tm->tm_hour, now_tm->tm_min, now_tm->tm_sec, // return false;
TZPFMS_VERSION) + return true;
1; }
metadata.size = metadata.size > sizeof(metadata.buffer) ? sizeof(metadata.buffer) : metadata.size; static_assert(is_tpm2_hash_algs_sorted()); // for the binary_search() below
// fprintf(stderr, "%d/%zu: \"%s\"\n", metadata.size, sizeof(metadata.buffer), metadata.buffer); /// Assuming always != end: we always parse first
return metadata; static const char * tpm2_hash_alg_name(TPM2_ALG_ID id) {
return std::lower_bound(std::begin(tpm2_hash_algs), std::end(tpm2_hash_algs), tpm2_hash_algs_t{id, {}},
[&](auto && lhs, auto && rhs) { return lhs.alg < rhs.alg; })
->names[0];
} }
int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_PERSISTENT & handle) { /// Nominally:
if(parse_int(handle_s, handle)) { /// #define TPM2_MAX_PCRS 32
fprintf(stderr, "Dataset %s's handle %s not valid.\n", dataset_name, handle_s); /// #define TPM2_PCR_SELECT_MAX ((TPM2_MAX_PCRS + 7) / 8)
return __LINE__; /// and
/// struct TPMS_PCR_SELECT {
/// UINT8 sizeofSelect; /* the size in octets of the pcrSelect array */
/// BYTE pcrSelect[TPM2_PCR_SELECT_MAX]; /* the bit map of selected PCR */
/// };
///
/// This works out to TPM2_PCR_SELECT_MAX=4, but most (all?) TPM2s have only 24 PCRs, meaning *any* request with sizeofSelect=sizeof(pcrSelect)=4 fails with
/// WARNING:esys:src/tss2-esys/api/Esys_CreatePrimary.c:393:Esys_CreatePrimary_Finish() Received TPM Error
/// ERROR:esys:src/tss2-esys/api/Esys_CreatePrimary.c:135:Esys_CreatePrimary() Esys Finish ErrorCode (0x000004c4)
/// Couldn't create primary encryption key: tpm:parameter(4):value is out of range or is not correct for the context
///
/// Follow tpm2-tools and pretend TPM2_MAX_PCRS=24 => TPM2_PCR_SELECT_MAX=3 => sizeofSelect=3.
#define TPM2_MAX_PCRS_BUT_STRONGER 24
#define TPM2_PCR_SELECT_MAX_BUT_STRONGER ((TPM2_MAX_PCRS_BUT_STRONGER + 7) / 8)
static_assert(TPM2_PCR_SELECT_MAX_BUT_STRONGER <= sizeof(TPMS_PCR_SELECT::pcrSelect));
int tpm2_parse_pcrs(char * arg, TPML_PCR_SELECTION & pcrs) {
TPMS_PCR_SELECTION * bank = pcrs.pcrSelections;
char * ph_sv{};
for(auto per_hash = strtok_r(arg, "+", &ph_sv); per_hash; per_hash = strtok_r(nullptr, "+", &ph_sv), ++bank) {
while(*per_hash == ' ')
++per_hash;
if(bank == pcrs.pcrSelections + (sizeof(pcrs.pcrSelections) / sizeof(*pcrs.pcrSelections))) // == TPM2_NUM_PCR_BANKS
return fprintf(stderr, gettext("Too many PCR banks specified! Can only have up to %zu\n"), sizeof(pcrs.pcrSelections) / sizeof(*pcrs.pcrSelections)),
__LINE__;
if(auto sep = strchr(per_hash, ':')) {
*sep = '\0';
auto values = sep + 1;
if(auto alg = std::find_if(
std::begin(tpm2_hash_algs), std::end(tpm2_hash_algs),
[&](auto && alg) { return std::any_of(std::begin(alg.names), std::end(alg.names), [&](auto && nm) { return nm && !strcasecmp(per_hash, nm); }); });
alg != std::end(tpm2_hash_algs))
bank->hash = alg->alg;
else {
if(!parse_uint(per_hash, bank->hash) || !std::binary_search(std::begin(tpm2_hash_algs), std::end(tpm2_hash_algs), tpm2_hash_algs_t{bank->hash, {}},
[&](auto && lhs, auto && rhs) { return lhs.alg < rhs.alg; })) {
fprintf(stderr,
// comma-separated list follows
gettext("Unknown hash algorithm %s.\n"
"Can be any of case-insensitive "),
per_hash);
auto first = true;
for(auto && alg : tpm2_hash_algs)
for(auto && nm : alg.names)
if(nm)
fprintf(stderr, "%s%s", first ? "" : ", ", nm), first = false;
return fputs(".\n", stderr), __LINE__;
}
}
bank->sizeofSelect = TPM2_PCR_SELECT_MAX_BUT_STRONGER;
if(!strcasecmp(values, "all"))
memset(bank->pcrSelect, 0xFF, bank->sizeofSelect);
else if(!strcasecmp(values, "none"))
; // already 0
else {
char * sv{};
for(values = strtok_r(values, ", ", &sv); values; values = strtok_r(nullptr, ", ", &sv)) {
uint8_t pcr;
if(!parse_uint(values, pcr))
return fprintf(stderr, "PCR %s: %s\n", values, strerror(errno)), __LINE__;
if(pcr > TPM2_MAX_PCRS_BUT_STRONGER - 1)
return fprintf(stderr, "PCR %s: %s, max %u\n", values, strerror(ERANGE), TPM2_MAX_PCRS_BUT_STRONGER - 1), __LINE__;
bank->pcrSelect[pcr / 8] |= 1 << (pcr % 8);
}
}
} else
// user-passed parameter
return fprintf(stderr, gettext("PCR bank \"%s\": no algorithm; need alg:PCR[,PCR]…\n"), per_hash), __LINE__;
} }
pcrs.count = bank - pcrs.pcrSelections;
return 0;
}
int tpm2_unparse_prop(TPMI_DH_PERSISTENT persistent_handle, const TPML_PCR_SELECTION & pcrs, char ** prop) {
// 0xFFFFFFFF;sha3_512:00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22+sha3_...
*prop = TRY_PTR("allocate property value",
reinterpret_cast<char *>(malloc(2 + 8 + pcrs.count * (1 + TPM2_HASH_ALGS_MAX_NAME_LEN + (TPM2_MAX_PCRS_BUT_STRONGER - 1) * 3) + 1)));
auto cur = *prop;
cur += sprintf(cur, "0x%" PRIX32 "", persistent_handle);
auto pre = ';';
for(size_t i = 0; i < pcrs.count; ++i) {
auto && sel = pcrs.pcrSelections[i];
*cur++ = std::exchange(pre, '+');
auto nm = tpm2_hash_alg_name(sel.hash);
auto nm_len = strlen(nm);
memcpy(cur, nm, nm_len), cur += nm_len;
if(std::all_of(sel.pcrSelect, sel.pcrSelect + sel.sizeofSelect, [](auto b) { return b == 0x00; }))
memcpy(cur, ":none", strlen(":none")), cur += strlen(":none");
else if(std::all_of(sel.pcrSelect, sel.pcrSelect + sel.sizeofSelect, [](auto b) { return b == 0xFF; }))
memcpy(cur, ":all", strlen(":all")), cur += strlen(":all");
else {
bool first = true;
for(size_t j = 0; j < sel.sizeofSelect; ++j)
for(uint8_t b = 0; b < 8; ++b)
if(sel.pcrSelect[j] & (1 << b))
cur += sprintf(cur, "%c%zu", std::exchange(first, false) ? ':' : ',', j * 8 + b);
}
}
*cur = '\0';
return 0; return 0;
} }
@ -69,12 +206,10 @@ int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_
int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length) { int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length) {
TPM2B_DIGEST * rand{}; TPM2B_DIGEST * rand{};
TRY_TPM2("get random data from TPM", Esys_GetRandom(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, length, &rand)); TRY_TPM2("get random data from TPM", Esys_GetRandom(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, length, &rand));
quickscope_wrapper rand_deleter{[=] { Esys_Free(rand); }}; quickscope_wrapper rand_deleter{[&] { Esys_Free(rand); }};
if(rand->size != length) { if(rand->size != length)
fprintf(stderr, "Wrong random size: wanted %zu, got %u bytes.\n", length, rand->size); return fprintf(stderr, "Wrong random size: wanted %zu, got %" PRIu16 " bytes.\n", length, rand->size), __LINE__;
return __LINE__;
}
memcpy(into, rand->buffer, length); memcpy(into, rand->buffer, length);
return 0; return 0;
@ -82,10 +217,10 @@ int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length) {
static int tpm2_find_unused_persistent_non_platform(ESYS_CONTEXT * tpm2_ctx, TPMI_DH_PERSISTENT & persistent_handle) { static int tpm2_find_unused_persistent_non_platform(ESYS_CONTEXT * tpm2_ctx, TPMI_DH_PERSISTENT & persistent_handle) {
TPMS_CAPABILITY_DATA * cap; // TODO: check for more data? TPMS_CAPABILITY_DATA * cap{}; // TODO: check for more data?
TRY_TPM2("Read used persistent TPM handles", Esys_GetCapability(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_HANDLES, TPM2_PERSISTENT_FIRST, TRY_TPM2("Read used persistent TPM handles", Esys_GetCapability(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_HANDLES, TPM2_PERSISTENT_FIRST,
TPM2_MAX_CAP_HANDLES, nullptr, &cap)); TPM2_MAX_CAP_HANDLES, nullptr, &cap));
quickscope_wrapper cap_deleter{[=] { Esys_Free(cap); }}; quickscope_wrapper cap_deleter{[&] { Esys_Free(cap); }};
persistent_handle = 0; persistent_handle = 0;
switch(cap->data.handles.count) { switch(cap->data.handles.count) {
@ -102,25 +237,85 @@ static int tpm2_find_unused_persistent_non_platform(ESYS_CONTEXT * tpm2_ctx, TPM
} }
} }
if(!persistent_handle) { if(!persistent_handle)
fprintf(stderr, "All %zu persistent handles allocated! We're fucked!\n", TPM2_MAX_CAP_HANDLES); return fprintf(stderr, "All %zu persistent handles allocated! We're fucked!\n", TPM2_MAX_CAP_HANDLES), __LINE__;
return __LINE__;
}
return 0; return 0;
} }
int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPM2B_DATA & metadata, void * data, template <class F>
size_t data_len) { static int tpm2_police_pcrs(ESYS_CONTEXT * tpm2_ctx, const TPML_PCR_SELECTION & pcrs, TPM2_SE session_type, F && with_session) {
if(!pcrs.count)
return with_session(ESYS_TR_NONE);
TPM2B_DIGEST digested_pcrs{};
digested_pcrs.size = SHA256_DIGEST_LENGTH;
static_assert(sizeof(TPM2B_DIGEST::buffer) >= SHA256_DIGEST_LENGTH);
{
SHA256_CTX ctx;
new_pcrs:
std::optional<uint32_t> update_count;
SHA256_Init(&ctx);
auto pcrs_left = pcrs;
while(std::any_of(pcrs_left.pcrSelections, pcrs_left.pcrSelections + pcrs_left.count,
[](auto && sel) { return std::any_of(sel.pcrSelect, sel.pcrSelect + sel.sizeofSelect, [](auto b) { return b; }); })) {
uint32_t out_upcnt{};
TPML_PCR_SELECTION * out_sel{};
TPML_DIGEST * out_val{};
TRY_TPM2("read PCRs", Esys_PCR_Read(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pcrs_left, &out_upcnt, &out_sel, &out_val));
quickscope_wrapper out_deleter{[&] { Esys_Free(out_val), Esys_Free(out_sel); }};
if(update_count && update_count != out_upcnt)
goto new_pcrs;
update_count = out_upcnt;
if(!out_val->count) { // this can happen with SHA1 disabled, for example
auto first = true;
fputs("No PCRs when asking for ", stderr);
for(size_t i = 0; i < pcrs_left.count; ++i)
if(std::any_of(pcrs_left.pcrSelections[i].pcrSelect, pcrs_left.pcrSelections[i].pcrSelect + pcrs_left.pcrSelections[i].sizeofSelect,
[](auto b) { return b; }))
fprintf(stderr, "%s%s", std::exchange(first, false) ? "" : ", ", tpm2_hash_alg_name(pcrs_left.pcrSelections[i].hash));
return fputs(": does the TPM support the algorithm?\n", stderr), __LINE__;
}
for(size_t i = 0; i < out_val->count; ++i)
SHA256_Update(&ctx, out_val->digests[i].buffer, out_val->digests[i].size);
for(size_t i = 0; i < out_sel->count; ++i)
for(size_t j = 0u; j < out_sel->pcrSelections[i].sizeofSelect; ++j)
pcrs_left.pcrSelections[i].pcrSelect[j] &= ~out_sel->pcrSelections[i].pcrSelect[j];
}
SHA256_Final(digested_pcrs.buffer, &ctx);
}
ESYS_TR pcr_session = ESYS_TR_NONE;
quickscope_wrapper tpm2_session_deleter{[&] { Esys_FlushContext(tpm2_ctx, pcr_session); }};
TRY_TPM2("start PCR session", Esys_StartAuthSession(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, nullptr, session_type,
&tpm2_session_key, TPM2_ALG_SHA256, &pcr_session));
TRY_TPM2("create PCR policy", Esys_PolicyPCR(tpm2_ctx, pcr_session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &digested_pcrs, &pcrs));
return with_session(pcr_session);
}
int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPML_PCR_SELECTION & pcrs,
bool allow_PCR_or_pass, void * data, size_t data_len) {
ESYS_TR primary_handle = ESYS_TR_NONE; ESYS_TR primary_handle = ESYS_TR_NONE;
quickscope_wrapper primary_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, primary_handle); }}; quickscope_wrapper primary_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, primary_handle); }};
const TPM2B_DATA metadata{};
{ {
const TPM2B_SENSITIVE_CREATE primary_sens{}; const TPM2B_SENSITIVE_CREATE primary_sens{};
// Adapted from tpm2-tss-3.0.1/test/integration/esys-create-primary-hmac.int.c // Adapted from tpm2-tss-3.0.1/test/integration/esys-create-primary-hmac.int.c
TPM2B_PUBLIC pub{}; TPM2B_PUBLIC pub{};
pub.publicArea.type = TPM2_ALG_RSA; pub.publicArea.type = TPM2_ALG_RSA;
pub.publicArea.nameAlg = TPM2_ALG_SHA1; pub.publicArea.nameAlg = TPM2_ALG_SHA256;
pub.publicArea.objectAttributes = TPMA_OBJECT_USERWITHAUTH | TPMA_OBJECT_RESTRICTED | TPMA_OBJECT_DECRYPT | TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | pub.publicArea.objectAttributes = TPMA_OBJECT_USERWITHAUTH | TPMA_OBJECT_RESTRICTED | TPMA_OBJECT_DECRYPT | TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN; TPMA_OBJECT_SENSITIVEDATAORIGIN;
pub.publicArea.parameters.rsaDetail.symmetric.algorithm = TPM2_ALG_AES; pub.publicArea.parameters.rsaDetail.symmetric.algorithm = TPM2_ALG_AES;
@ -129,21 +324,14 @@ int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT
pub.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG_NULL; pub.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG_NULL;
pub.publicArea.parameters.rsaDetail.keyBits = 2048; pub.publicArea.parameters.rsaDetail.keyBits = 2048;
pub.publicArea.parameters.rsaDetail.exponent = 0; pub.publicArea.parameters.rsaDetail.exponent = 0;
pub.publicArea.parameters.asymDetail.scheme.scheme = TPM2_ALG_NULL;
const TPML_PCR_SELECTION pcrs{}; pub.publicArea.parameters.symDetail.sym.algorithm = TPM2_ALG_AES;
pub.publicArea.parameters.symDetail.sym.keyBits.sym = 128;
TPM2B_PUBLIC * public_ret{}; pub.publicArea.parameters.symDetail.sym.mode.sym = TPM2_ALG_CFB;
TPM2B_CREATION_DATA * creation_data{}; TRY_MAIN(try_or_passphrase("create primary encryption key", "TPM2 owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER, [&] {
TPM2B_DIGEST * creation_hash{};
TPMT_TK_CREATION * creation_ticket{};
TRY_MAIN(try_or_passphrase("create primary encryption key", "owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER, [&] {
return Esys_CreatePrimary(tpm2_ctx, ESYS_TR_RH_OWNER, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &primary_sens, &pub, &metadata, &pcrs, &primary_handle, return Esys_CreatePrimary(tpm2_ctx, ESYS_TR_RH_OWNER, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &primary_sens, &pub, &metadata, &pcrs, &primary_handle,
&public_ret, &creation_data, &creation_hash, &creation_ticket); nullptr, nullptr, nullptr, nullptr);
})); }));
quickscope_wrapper creation_ticket_deleter{[=] { Esys_Free(creation_ticket); }};
quickscope_wrapper creation_hash_deleter{[=] { Esys_Free(creation_hash); }};
quickscope_wrapper creation_data_deleter{[=] { Esys_Free(creation_data); }};
quickscope_wrapper public_ret_deleter{[=] { Esys_Free(public_ret); }};
// TSS2_RC Esys_CertifyCreation ( ESYS_CONTEXT * esysContext, // TSS2_RC Esys_CertifyCreation ( ESYS_CONTEXT * esysContext,
// ESYS_TR signHandle, // ESYS_TR signHandle,
@ -160,11 +348,19 @@ int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT
// ) // )
} }
TPM2B_DIGEST policy_digest{};
if(pcrs.count)
TRY_MAIN(tpm2_police_pcrs(tpm2_ctx, pcrs, TPM2_SE_TRIAL, [&](auto pcr_session) {
TPM2B_DIGEST * dgst{};
TRY_TPM2("get PCR policy digest", Esys_PolicyGetDigest(tpm2_ctx, pcr_session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &dgst));
quickscope_wrapper dgst_deleter{[&] { Esys_Free(dgst); }};
policy_digest = *dgst;
return 0;
}));
TPM2B_PRIVATE * sealant_private{}; TPM2B_PRIVATE * sealant_private{};
TPM2B_PUBLIC * sealant_public{}; TPM2B_PUBLIC * sealant_public{};
quickscope_wrapper sealant_public_deleter{[=] { Esys_Free(sealant_public); }}; quickscope_wrapper sealant_deleter{[&] { Esys_Free(sealant_public), Esys_Free(sealant_private); }};
quickscope_wrapper sealant_private_deleter{[=] { Esys_Free(sealant_private); }};
/// This is the object with the actual sealed data in it /// This is the object with the actual sealed data in it
{ {
@ -172,35 +368,35 @@ int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT
secret_sens.sensitive.data.size = data_len; secret_sens.sensitive.data.size = data_len;
memcpy(secret_sens.sensitive.data.buffer, data, secret_sens.sensitive.data.size); memcpy(secret_sens.sensitive.data.buffer, data, secret_sens.sensitive.data.size);
{ if(!pcrs.count || allow_PCR_or_pass) {
char what_for[ZFS_MAX_DATASET_NAME_LEN + 512 + 1];
// %s=dataset name, then TPM2. noun for "Enter passphrase for" prompt
snprintf(what_for, sizeof(what_for), gettext("%s %s wrapping key (or empty for none)"), dataset, "TPM2");
uint8_t * passphrase{}; uint8_t * passphrase{};
size_t passphrase_len{}; size_t passphrase_len{};
TRY_MAIN(read_new_passphrase("wrapping key (or empty for none)", passphrase, passphrase_len, sizeof(TPM2B_SENSITIVE_CREATE::sensitive.userAuth.buffer))); TRY_MAIN(read_new_passphrase(what_for, passphrase, passphrase_len, sizeof(TPM2B_SENSITIVE_CREATE::sensitive.userAuth.buffer)));
quickscope_wrapper passphrase_deleter{[&] { free(passphrase); }}; quickscope_wrapper passphrase_deleter{[&] { free(passphrase); }};
secret_sens.sensitive.userAuth.size = passphrase_len; secret_sens.sensitive.userAuth.size = passphrase_len;
memcpy(secret_sens.sensitive.userAuth.buffer, passphrase, secret_sens.sensitive.userAuth.size); memcpy(secret_sens.sensitive.userAuth.buffer, passphrase, secret_sens.sensitive.userAuth.size);
} }
// Same args as tpm2-tools' tpm2_create(1) // Same args as tpm2-tools' tpm2_create(1)
TPM2B_PUBLIC pub{}; TPM2B_PUBLIC pub{};
pub.publicArea.type = TPM2_ALG_KEYEDHASH; pub.publicArea.type = TPM2_ALG_KEYEDHASH;
pub.publicArea.nameAlg = TPM2_ALG_SHA256; pub.publicArea.nameAlg = TPM2_ALG_SHA256;
pub.publicArea.objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | TPMA_OBJECT_USERWITHAUTH; pub.publicArea.objectAttributes =
TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | ((pcrs.count && !secret_sens.sensitive.userAuth.size) ? 0 : TPMA_OBJECT_USERWITHAUTH);
pub.publicArea.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL; pub.publicArea.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL;
pub.publicArea.authPolicy = policy_digest;
const TPML_PCR_SELECTION pcrs{};
TPM2B_CREATION_DATA * creation_data{};
TPM2B_DIGEST * creation_hash{};
TPMT_TK_CREATION * creation_ticket{};
TRY_TPM2("create key seal", Esys_Create(tpm2_ctx, primary_handle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &secret_sens, &pub, &metadata, &pcrs, TRY_TPM2("create key seal", Esys_Create(tpm2_ctx, primary_handle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &secret_sens, &pub, &metadata, &pcrs,
&sealant_private, &sealant_public, &creation_data, &creation_hash, &creation_ticket)); &sealant_private, &sealant_public, nullptr, nullptr, nullptr));
quickscope_wrapper creation_ticket_deleter{[=] { Esys_Free(creation_ticket); }};
quickscope_wrapper creation_hash_deleter{[=] { Esys_Free(creation_hash); }};
quickscope_wrapper creation_data_deleter{[=] { Esys_Free(creation_data); }};
} }
ESYS_TR sealed_handle = ESYS_TR_NONE; ESYS_TR sealed_handle = ESYS_TR_NONE;
quickscope_wrapper sealed_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, sealed_handle); }}; quickscope_wrapper sealed_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, sealed_handle); }};
@ -221,20 +417,36 @@ int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT
return 0; return 0;
} }
int tpm2_unseal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, void * data, size_t data_len) { int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, const TPML_PCR_SELECTION & pcrs,
void * data, size_t data_len) {
// Esys_FlushContext(tpm2_ctx, tpm2_session);
char what_for[ZFS_MAX_DATASET_NAME_LEN + 512 + 1];
// %s=dataset name, then TPM2. noun for "Enter passphrase for" prompt
snprintf(what_for, sizeof(what_for), gettext("%s %s wrapping key"), dataset, "TPM2");
// Entirely fake and not flushable (tpm:parameter(1):value is out of range or is not correct for the context) // Entirely fake and not flushable (tpm:parameter(1):value is out of range or is not correct for the context)
ESYS_TR pandle; ESYS_TR pandle;
TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle)); TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle));
TPM2B_SENSITIVE_DATA * unsealed{};
quickscope_wrapper unsealed_deleter{[=] { Esys_Free(unsealed); }};
TRY_MAIN(try_or_passphrase("unseal wrapping key", "wrapping key", tpm2_ctx, TPM2_RC_AUTH_FAIL, pandle,
[&] { return Esys_Unseal(tpm2_ctx, pandle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &unsealed); }));
if(unsealed->size != data_len) { TPM2B_SENSITIVE_DATA * unsealed{};
fprintf(stderr, "Unsealed data has wrong length %u, expected %zu!\n", unsealed->size, data_len); quickscope_wrapper unsealed_deleter{[&] { Esys_Free(unsealed); }};
return __LINE__; auto unseal = [&](auto sess) { return Esys_Unseal(tpm2_ctx, pandle, sess, ESYS_TR_NONE, ESYS_TR_NONE, &unsealed); };
} TRY_MAIN(tpm2_police_pcrs(tpm2_ctx, pcrs, TPM2_SE_POLICY, [&](auto pcr_session) {
// In case there's (PCR policy || passphrase): try PCR once; if it fails, fall back to passphrase
if(pcr_session != ESYS_TR_NONE) {
if(auto err = unseal(pcr_session); err != TPM2_RC_SUCCESS)
fprintf(stderr, gettext("Couldn't unseal wrapping key with PCR policy: %s\n"), Tss2_RC_Decode(err));
else
return 0;
}
return try_or_passphrase("unseal wrapping key", what_for, tpm2_ctx, TPM2_RC_AUTH_FAIL, pandle, [&] { return unseal(tpm2_session); });
}));
if(unsealed->size != data_len)
return fprintf(stderr, "Unsealed data has wrong length %" PRIu16 ", expected %zu!\n", unsealed->size, data_len), __LINE__;
memcpy(data, unsealed->buffer, data_len); memcpy(data, unsealed->buffer, data_len);
return 0; return 0;
} }
@ -245,7 +457,7 @@ int tpm2_free_persistent(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_
TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle)); TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle));
ESYS_TR new_handle; ESYS_TR new_handle;
TRY_MAIN(try_or_passphrase("unpersist object", "owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER, TRY_MAIN(try_or_passphrase("unpersist object", "TPM2 owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER,
[&] { return Esys_EvictControl(tpm2_ctx, ESYS_TR_RH_OWNER, pandle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, 0, &new_handle); })); [&] { return Esys_EvictControl(tpm2_ctx, ESYS_TR_RH_OWNER, pandle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, 0, &new_handle); }));
return 0; return 0;

View File

@ -6,6 +6,7 @@
#include "common.hpp" #include "common.hpp"
#include <sys/mman.h>
#include <tss2/tss2_common.h> #include <tss2/tss2_common.h>
#include <tss2/tss2_esys.h> #include <tss2/tss2_esys.h>
#include <tss2/tss2_rc.h> #include <tss2/tss2_rc.h>
@ -14,8 +15,14 @@
#define TRY_TPM2(what, ...) TRY_GENERIC(what, , != TPM2_RC_SUCCESS, _try_ret, __LINE__, Tss2_RC_Decode, __VA_ARGS__) #define TRY_TPM2(what, ...) TRY_GENERIC(what, , != TPM2_RC_SUCCESS, _try_ret, __LINE__, Tss2_RC_Decode, __VA_ARGS__)
// https://github.com/tpm2-software/tpm2-tss/blob/49146d926ccb0fd3c3ee064455eb02356e0cdf90/test/integration/esys-create-session-auth.int.c#L218
static const constexpr TPMT_SYM_DEF tpm2_session_key{.algorithm = TPM2_ALG_AES, .keyBits = {.aes = 128}, .mode = {.aes = TPM2_ALG_CFB}};
template <class F> template <class F>
int with_tpm2_session(F && func) { int with_tpm2_session(F && func) {
mlockall(MCL_CURRENT | MCL_FUTURE);
// https://trustedcomputinggroup.org/wp-content/uploads/TSS_ESAPI_v1p00_r05_pubrev.pdf // https://trustedcomputinggroup.org/wp-content/uploads/TSS_ESAPI_v1p00_r05_pubrev.pdf
// mainly "3.4. The ESAPI Session" and "3.5. ESAPI Use Model" // mainly "3.4. The ESAPI Session" and "3.5. ESAPI Use Model"
// https://tpm2-tss.readthedocs.io/en/latest/group___e_s_y_s___c_o_n_t_e_x_t.html // https://tpm2-tss.readthedocs.io/en/latest/group___e_s_y_s___c_o_n_t_e_x_t.html
@ -29,26 +36,22 @@ int with_tpm2_session(F && func) {
ESYS_TR tpm2_session = ESYS_TR_NONE; ESYS_TR tpm2_session = ESYS_TR_NONE;
quickscope_wrapper tpm2_session_deleter{[&] { Esys_FlushContext(tpm2_ctx, tpm2_session); }}; quickscope_wrapper tpm2_session_deleter{[&] { Esys_FlushContext(tpm2_ctx, tpm2_session); }};
{ TRY_TPM2("authenticate with TPM", Esys_StartAuthSession(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, nullptr, TPM2_SE_HMAC,
// https://github.com/tpm2-software/tpm2-tss/blob/master/test/integration/esys-create-session-auth.int.c#L218 &tpm2_session_key, TPM2_ALG_SHA256, &tpm2_session));
TPMT_SYM_DEF session_key{};
session_key.algorithm = TPM2_ALG_AES;
session_key.keyBits.aes = 128;
session_key.mode.aes = TPM2_ALG_CFB;
TRY_TPM2("authenticate with TPM", Esys_StartAuthSession(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, nullptr,
TPM2_SE_HMAC, &session_key, TPM2_ALG_SHA256, &tpm2_session));
}
return func(tpm2_ctx, tpm2_session); return func(tpm2_ctx, tpm2_session);
} }
extern TPM2B_DATA tpm2_creation_metadata(const char * dataset_name);
/// Parse a persistent handle name as stored in a ZFS property /// Parse a persistent handle name as stored in a ZFS property
extern int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_PERSISTENT & handle); extern int tpm2_parse_prop(const char * dataset_name, char * handle_s, TPMI_DH_PERSISTENT & handle, TPML_PCR_SELECTION * pcrs);
extern int tpm2_unparse_prop(TPMI_DH_PERSISTENT persistent_handle, const TPML_PCR_SELECTION & pcrs, char ** prop);
/// `alg:PCR[,PCR]...[+alg:PCR[,PCR]...]...`; all separators can have spaces
extern int tpm2_parse_pcrs(char * arg, TPML_PCR_SELECTION & pcrs);
extern int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length); extern int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length);
extern int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPM2B_DATA & metadata, void * data, extern int tpm2_seal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle,
size_t data_len); const TPML_PCR_SELECTION & pcrs, bool allow_PCR_or_pass, void * data, size_t data_len);
extern int tpm2_unseal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, void * data, size_t data_len); extern int tpm2_unseal(const char * dataset, ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle,
const TPML_PCR_SELECTION & pcrs, void * data, size_t data_len);
extern int tpm2_free_persistent(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle); extern int tpm2_free_persistent(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle);

View File

@ -10,10 +10,8 @@
#include <string.h> #include <string.h>
// Funxion statics pull in libc++'s __cxa_guard_acquire()
static nvlist_t * rrargs{};
static quickscope_wrapper rrargs_deleter{[] { nvlist_free(rrargs); }};
nvlist_t * rewrap_args() { nvlist_t * rewrap_args() {
static nvlist_t * rrargs;
if(!rrargs) if(!rrargs)
if(auto err = if(auto err =
[&] { [&] {
@ -33,9 +31,8 @@ nvlist_t * rewrap_args() {
} }
static nvlist_t * crrargs{};
static quickscope_wrapper crrargs_deleter{[] { nvlist_free(crrargs); }};
nvlist_t * clear_rewrap_args() { nvlist_t * clear_rewrap_args() {
static nvlist_t * crrargs;
if(!crrargs) if(!crrargs)
if(auto err = if(auto err =
[&] { [&] {
@ -62,6 +59,18 @@ nvlist_t * clear_rewrap_args() {
TRY_NVL(what, _try_retl); \ TRY_NVL(what, _try_retl); \
}) })
namespace {
// nvlist_lookup_nvlist takes nvlist_t *, char ** on bookworm and an const nvlist_t *, const char ** on sid!
template <class>
struct third_arg;
template <class F, class A, class B, class C>
struct third_arg<F(A, B, C)> {
using third = C;
};
}
int lookup_userprop(zfs_handle_t * in, const char * name, char *& out) { int lookup_userprop(zfs_handle_t * in, const char * name, char *& out) {
// xyz.nabijaczleweli:tzpfms.key: // xyz.nabijaczleweli:tzpfms.key:
// value: '76B0286BEB3FAF57536C47D9A2BAD38157FD522A75A59E72867BBFD6AF167395' // value: '76B0286BEB3FAF57536C47D9A2BAD38157FD522A75A59E72867BBFD6AF167395'
@ -71,11 +80,11 @@ int lookup_userprop(zfs_handle_t * in, const char * name, char *& out) {
TRY_LOOKUP("look up user property", nvlist_lookup_nvlist(zfs_get_user_props(in), name, &vs)); TRY_LOOKUP("look up user property", nvlist_lookup_nvlist(zfs_get_user_props(in), name, &vs));
char * source{}; char * source{};
TRY_LOOKUP("look up user property source", nvlist_lookup_string(vs, "source", &source)); TRY_LOOKUP("look up user property source", nvlist_lookup_string(vs, "source", const_cast<third_arg<decltype(nvlist_lookup_string)>::third>(&source)));
if(!source || strcmp(source, zfs_get_name(in))) if(!source || strcmp(source, zfs_get_name(in)))
return 0; return 0;
TRY_LOOKUP("look up user property value", nvlist_lookup_string(vs, "value", &out)); TRY_LOOKUP("look up user property value", nvlist_lookup_string(vs, "value", const_cast<third_arg<decltype(nvlist_lookup_string)>::third>(&out)));
return 0; return 0;
} }
@ -98,15 +107,13 @@ int clear_key_props(zfs_handle_t * from) {
bool ok = false; bool ok = false;
quickscope_wrapper props_deleter{[&] { quickscope_wrapper props_deleter{[&] {
if(!ok) if(!ok)
fprintf(stderr, "You might need to run \"zfs inherit %s %s\" and \"zfs inherit %s %s\"!\n", PROPNAME_BACKEND, zfs_get_name(from), PROPNAME_KEY, fprintf(stderr, gettext("You might need to run \"zfs inherit %s %s\" and \"zfs inherit %s %s\" to fully clear metadata!\n"), PROPNAME_BACKEND,
zfs_get_name(from)); zfs_get_name(from), PROPNAME_KEY, zfs_get_name(from));
}}; }};
TRY("delete tzpfms.backend", zfs_prop_inherit(from, PROPNAME_BACKEND, B_FALSE)); TRY("delete tzpfms.backend", zfs_prop_inherit(from, PROPNAME_BACKEND, B_FALSE));
TRY("delete tzpfms.key", zfs_prop_inherit(from, PROPNAME_KEY, B_FALSE)); TRY("delete tzpfms.key", zfs_prop_inherit(from, PROPNAME_KEY, B_FALSE));
return ok = true, 0;
ok = true;
return 0;
} }
@ -114,20 +121,14 @@ int parse_key_props(zfs_handle_t * in, const char * our_backend, char *& handle)
char * backend{}; char * backend{};
TRY_MAIN(lookup_userprop(in, PROPNAME_BACKEND, backend)); TRY_MAIN(lookup_userprop(in, PROPNAME_BACKEND, backend));
if(!backend) { if(!backend)
fprintf(stderr, "Dataset %s not encrypted with tzpfms!\n", zfs_get_name(in)); return fprintf(stderr, gettext("Dataset %s not encrypted with tzpfms!\n"), zfs_get_name(in)), __LINE__;
return __LINE__; if(strcmp(backend, our_backend))
} return fprintf(stderr, gettext("Dataset %s encrypted with tzpfms back-end %s, but we are %s.\n"), zfs_get_name(in), backend, our_backend), __LINE__;
if(strcmp(backend, our_backend)) {
fprintf(stderr, "Dataset %s encrypted with tzpfms back-end %s, but we are %s.\n", zfs_get_name(in), backend, our_backend);
return __LINE__;
}
TRY_MAIN(lookup_userprop(in, PROPNAME_KEY, handle)); TRY_MAIN(lookup_userprop(in, PROPNAME_KEY, handle));
if(!handle) { if(!handle)
fprintf(stderr, "Dataset %s missing key data.\n", zfs_get_name(in)); return fprintf(stderr, gettext("Dataset %s missing key data.\n"), zfs_get_name(in)), __LINE__;
return __LINE__;
}
return 0; return 0;
} }

View File

@ -6,6 +6,8 @@
#include <libzfs.h> #include <libzfs.h>
#include <sys/nvpair.h> #include <sys/nvpair.h>
// #include <sys/zio_crypt.h>
#define WRAPPING_KEY_LEN 32
#include "main.hpp" #include "main.hpp"
@ -20,12 +22,10 @@
/// Mimic libzfs error output /// Mimic libzfs error output
#define REQUIRE_KEY_LOADED(dataset) \ #define REQUIRE_KEY_LOADED(dataset) \
do { \ do { \
if(zfs_prop_get_int(dataset, ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE) { \ if(zfs_prop_get_int(dataset, ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE) \
fprintf(stderr, "Key change error: Key must be loaded.\n"); \ return fprintf(stderr, gettext("Key change error: Key must be loaded.\n")), __LINE__; \
return __LINE__; \
} \
} while(0) } while(0)
@ -50,14 +50,10 @@ extern int parse_key_props(zfs_handle_t * in, const char * our_backend, char *&
/// Rewrap key on on to wrap_key. /// Rewrap key on on to wrap_key.
/// extern int change_key(zfs_handle_t * on, const uint8_t (&wrap_key)[WRAPPING_KEY_LEN]);
/// wrap_key must be WRAPPING_KEY_LEN long.
extern int change_key(zfs_handle_t * on, const uint8_t * wrap_key);
/// (Try to) load key wrap_key for for_d. /// (Try to) load key wrap_key for for_d.
/// extern int load_key(zfs_handle_t * for_d, const uint8_t (&wrap_key)[WRAPPING_KEY_LEN], bool noop);
/// wrap_key must be WRAPPING_KEY_LEN long.
extern int load_key(zfs_handle_t * for_d, const uint8_t * wrap_key, bool noop);
/// Check back-end integrity; if the previous backend matches this_backend, run func(); otherwise warn. /// Check back-end integrity; if the previous backend matches this_backend, run func(); otherwise warn.
template <class F> template <class F>
@ -67,10 +63,14 @@ int verify_backend(zfs_handle_t * on, const char * this_backend, F && func) {
TRY_MAIN(lookup_userprop(on, PROPNAME_KEY, previous_handle)); TRY_MAIN(lookup_userprop(on, PROPNAME_KEY, previous_handle));
if(!!previous_backend ^ !!previous_handle) if(!!previous_backend ^ !!previous_handle)
fprintf(stderr, "Inconsistent tzpfms metadata for %s: back-end is %s, but handle is %s?\n", zfs_get_name(on), previous_backend, previous_handle); // dataset name: (null), 0A123...
// dataset name: TPM1.X, (null)
fprintf(stderr, gettext("Inconsistent tzpfms metadata for %s: back-end is %s, but handle is %s?\n"), zfs_get_name(on), previous_backend, previous_handle);
else if(previous_backend && previous_handle) { else if(previous_backend && previous_handle) {
if(strcmp(previous_backend, this_backend)) if(strcmp(previous_backend, this_backend))
fprintf(stderr, "Dataset %s was encrypted with tzpfms back-end %s before, but we are %s. You will have to free handle %s for back-end %s manually!\n", // dataset name: TPM1.X, TPM2. 0A1234..., TPM1.X
fprintf(stderr,
gettext("Dataset %s was encrypted with tzpfms back-end %s before, but we are %s. You will have to free handle %s for back-end %s manually!\n"),
zfs_get_name(on), previous_backend, this_backend, previous_handle, previous_backend); zfs_get_name(on), previous_backend, this_backend, previous_handle, previous_backend);
else else
func(previous_handle); func(previous_handle);

View File

@ -5,10 +5,6 @@
#include "main.hpp" #include "main.hpp"
#include "zfs.hpp" #include "zfs.hpp"
#include <libzfs.h>
// #include <sys/zio_crypt.h>
#define WRAPPING_KEY_LEN 32
template <class F> template <class F>
static int with_stdin_at_buffer(const void * buf, size_t buf_len, F && func) { static int with_stdin_at_buffer(const void * buf, size_t buf_len, F && func) {
@ -20,29 +16,32 @@ static int with_stdin_at_buffer(const void * buf, size_t buf_len, F && func) {
} }
int change_key(zfs_handle_t * on, const uint8_t * wrap_key) { int change_key(zfs_handle_t * on, const uint8_t (&wrap_key)[WRAPPING_KEY_LEN]) {
/// zfs_crypto_rewrap() with "prompt" reads from stdin, but not if it's a TTY; /// zfs_crypto_rewrap() with "prompt" reads from stdin, but not if it's a TTY;
/// this user-proofs the set-up, and means we don't have to touch the filesysten: /// this user-proofs the set-up, and means we don't have to touch the filesysten:
/// instead, get an FD, write the raw key data there, dup() it onto stdin, /// instead, get an FD, write the raw key data there, dup() it onto stdin,
/// let libzfs read it, then restore stdin /// let libzfs read it, then restore stdin
return with_stdin_at_buffer(wrap_key, WRAPPING_KEY_LEN, [&] { return with_stdin_at_buffer(wrap_key, sizeof(wrap_key), [&] {
if(zfs_crypto_rewrap(on, TRY_PTR("get rewrap args", rewrap_args()), B_FALSE)) if(zfs_crypto_rewrap(on, TRY_PTR("get rewrap args", rewrap_args()), B_FALSE))
return __LINE__; // Error printed by libzfs return __LINE__; // Error printed by libzfs
else else
printf("Key for %s changed\n", zfs_get_name(on)); printf(gettext("Key for %s changed\n"), zfs_get_name(on));
return 0; return 0;
}); });
} }
int load_key(zfs_handle_t * for_d, const uint8_t * wrap_key, bool noop) { int load_key(zfs_handle_t * for_d, const uint8_t (&wrap_key)[WRAPPING_KEY_LEN], bool noop) {
return with_stdin_at_buffer(wrap_key, WRAPPING_KEY_LEN, [&] { return with_stdin_at_buffer(wrap_key, sizeof(wrap_key), [&] {
if(zfs_crypto_load_key(for_d, noop ? B_TRUE : B_FALSE, nullptr)) if(zfs_crypto_load_key(for_d, noop ? B_TRUE : B_FALSE, nullptr))
return __LINE__; // Error printed by libzfs return __LINE__; // Error printed by libzfs
else else //
printf("Key for %s %s\n", zfs_get_name(for_d), noop ? "OK" : "loaded"); if(noop)
printf(gettext("Key for %s OK\n"), zfs_get_name(for_d));
else
printf(gettext("Key for %s loaded\n"), zfs_get_name(for_d));
return 0; return 0;
}); });

View File

@ -25,21 +25,36 @@
"name": "Source", "name": "Source",
"path": "src" "path": "src"
}, },
{
"follow_symlinks": true,
"name": "Misc source",
"path": "contrib"
},
{ {
"follow_symlinks": true, "follow_symlinks": true,
"name": "Initrd plug-ins", "name": "Initrd plug-ins",
"path": "initrd" "path": "initrd"
}, },
{
"follow_symlinks": true,
"name": "Init system plug-ins",
"path": "init.d"
},
{ {
"follow_symlinks": true, "follow_symlinks": true,
"name": "Manpages", "name": "Manpages",
"path": "man" "path": "man"
}, },
{
"follow_symlinks": true,
"name": "Localisation",
"path": "po"
},
{ {
"follow_symlinks": true, "follow_symlinks": true,
"name": "Build scripts", "name": "Build scripts",
"path": ".", "path": ".",
"file_include_patterns": [".build.yml", "*Makefile", "*.awk"], "file_include_patterns": [".build.yml", "*Makefile*", "*.awk"],
"folder_exclude_patterns": ["*"] "folder_exclude_patterns": ["*"]
}, },
] ]