diff --git a/Makefile b/Makefile index e98eb68..b33df40 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ VERAR := $(foreach l,TZPFMS,-D$(l)_VERSION='$($(l)_VERSION)') BINARY_SOURCES := $(sort $(wildcard $(SRCDIR)bin/*.cpp $(SRCDIR)bin/**/*.cpp)) COMMON_SOURCES := $(filter-out $(BINARY_SOURCES),$(sort $(wildcard $(SRCDIR)*.cpp $(SRCDIR)**/*.cpp $(SRCDIR)**/**/*.cpp $(SRCDIR)**/**/**/*.cpp))) # TEST_SOURCES := $(sort $(wildcard $(TSTDIR)*.cpp $(TSTDIR)**/*.cpp $(TSTDIR)**/**/*.cpp $(TSTDIR)**/**/**/*.cpp)) -MANPAGE_SOURCES := $(sort $(wildcard $(MANDIR)*.md $(MANDIR)**/*.md)) +MANPAGE_SOURCES := $(sort $(wildcard $(MANDIR)*.md.pp)) .PHONY : all clean build build-test man @@ -48,17 +48,17 @@ clean : build : $(subst $(SRCDIR)bin/,$(OUTDIR),$(subst .cpp,$(EXE),$(BINARY_SOURCES))) #build-test : $(OUTDIR)tzpfms-test$(EXE) -man : $(subst $(MANDIR),$(OUTDIR)man/,$(MANPAGE_SOURCES)) +man : $(OUTDIR)man/index.txt #$(OUTDIR)tzpfms-test$(EXE) : $(subst $(TSTDIR),$(BLDDIR)test/,$(subst .cpp,$(OBJ),$(TEST_SOURCES))) $(subst $(SRCDIR),$(OBJDIR),$(subst .cpp,$(OBJ),$(filter-out $(SRCDIR)main.cpp,$(SOURCES)))) $(patsubst ext/fmt/src/%.cc,$(BLDDIR)fmt/obj/%$(OBJ),$(wildcard ext/fmt/src/*.cc)) # $(CXX) $(CXXAR) -o$@ $^ $(PIC) $(LDAR) -$(subst $(MANDIR),$(OUTDIR)man/,$(MANPAGE_SOURCES)) : $(MANDIR)index.txt $(MANPAGE_SOURCES) - @rm -rf $(dir $@) && mkdir -p $(dir $@) - cp $^ $(dir $@) - $(RONN) $@ - $(RONN) -f $@ +$(OUTDIR)man/index.txt : $(MANDIR)index.txt $(patsubst $(MANDIR)%.pp,$(OUTDIR)man/%,$(MANPAGE_SOURCES)) + @mkdir -p $(dir $@) + cp $< $(dir $@) + $(RONN) --organization="tzpfms developers" $(filter-out $<,$^) + $(RONN) --organization="tzpfms developers" -f $(filter-out $<,$^) $(OUTDIR)%$(EXE) : $(subst $(SRCDIR),$(OBJDIR),$(subst .cpp,$(OBJ),$(SRCDIR)bin/%.cpp $(COMMON_SOURCES))) @@ -73,3 +73,7 @@ $(OBJDIR)%$(OBJ) : $(SRCDIR)%.cpp $(BLDDIR)test/%$(OBJ) : $(TSTDIR)%.cpp @mkdir -p $(dir $@) $(CXX) $(CXXAR) $(INCAR) -I$(SRCDIR) $(VERAR) -c -o$@ $^ + +$(OUTDIR)man/%.md : $(MANDIR)%.md.pp $(sort $(wildcard $(MANDIR)*.h)) + @mkdir -p $(dir $@) + $(AWK) '/^#include/ {gsub("\"", "", $$2); while((getline inc < ("$(dir $<)" $$2)) == 1) print inc; next} {print}' $< > $@ diff --git a/configMakefile b/configMakefile index 5aaeda9..831ded7 100644 --- a/configMakefile +++ b/configMakefile @@ -52,7 +52,6 @@ TZPFMS_VERSION := "0.0.0-$(shell git rev-list HEAD --count)" INCCMAKEAR := CXXFLAGS="$(INCCXXAR)" LNCMAKEAR := LDFLAGS="$(LNCXXAR)" -LDD ?= ldd AWK ?= awk RONN ?= ronn OBJ := .o diff --git a/man/backend-tpm2.h b/man/backend-tpm2.h new file mode 100644 index 0000000..4f83b01 --- /dev/null +++ b/man/backend-tpm2.h @@ -0,0 +1,17 @@ +## TPM2 back-end configuration + +### Environment variables + + * `TSS2_LOG`=: + Any of: *NONE*, *ERROR*, *WARNING*, *INFO*, *DEBUG*, *TRACE*. Default: *WARNING*. + +### TPM selection + +The library `libtss2-tcti-default.so` can be linked to any of the `libtss2-tcti-*.so` libraries to select the default, +otherwise `/dev/tpmrm0`, then `/dev/tpm0`, then `localhost:2321` will be tried, in order (see ESYS_CONTEXT(3)). + +### See also + +The tpm2-tss git repository at <https://github.com/tpm2-software/tpm2-tss> and the documentation at <https://tpm2-tss.readthedocs.io>. + +The TPM 2.0 specifications, mainly at <<https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf>> and related pages. diff --git a/man/common.h b/man/common.h new file mode 100644 index 0000000..f7bd4c8 --- /dev/null +++ b/man/common.h @@ -0,0 +1,16 @@ +## AUTHOR + +Written by наб <<nabijaczleweli@nabijaczleweli.xyz>> + +## SPECIAL THANKS + +To all who support further development, in particular: + + * ThePhD + * Embark Studios + +## REPORTING BUGS + +<<https://todo.sr.ht/~nabijaczleweli/tzpfms>> + +<<mailto:~nabijaczleweli/tzpfms@lists.sr.ht>>, archived at <<https://lists.sr.ht/~nabijaczleweli/tzpfms>> diff --git a/man/index.txt b/man/index.txt new file mode 100644 index 0000000..f83ce9f --- /dev/null +++ b/man/index.txt @@ -0,0 +1,8 @@ +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(8) https://manpages.debian.org/bullseye/zfsutils-linux/zfs.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 diff --git a/man/zfs-tpm2-change-key.md.pp b/man/zfs-tpm2-change-key.md.pp new file mode 100644 index 0000000..06a7f15 --- /dev/null +++ b/man/zfs-tpm2-change-key.md.pp @@ -0,0 +1,56 @@ +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. + +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}** 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 + +<<https://git.sr.ht/~nabijaczleweli/tzpfms>> diff --git a/man/zfs-tpm2-clear-key.md.pp b/man/zfs-tpm2-clear-key.md.pp new file mode 100644 index 0000000..3fb864d --- /dev/null +++ b/man/zfs-tpm2-clear-key.md.pp @@ -0,0 +1,24 @@ +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 + +<<https://git.sr.ht/~nabijaczleweli/tzpfms>> diff --git a/man/zfs-tpm2-load-key.md.pp b/man/zfs-tpm2-load-key.md.pp new file mode 100644 index 0000000..93f92ae --- /dev/null +++ b/man/zfs-tpm2-load-key.md.pp @@ -0,0 +1,25 @@ +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 + +<<https://git.sr.ht/~nabijaczleweli/tzpfms>> diff --git a/src/bin/zfs-tpm2-change-key.cpp b/src/bin/zfs-tpm2-change-key.cpp index e69fa4f..cbd0003 100644 --- a/src/bin/zfs-tpm2-change-key.cpp +++ b/src/bin/zfs-tpm2-change-key.cpp @@ -62,8 +62,8 @@ int main(int argc, char ** argv) { TRY_MAIN(with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { char *previous_backend{}, *previous_handle_s{}; - TRY_MAIN(lookup_userprop(zfs_get_user_props(dataset), PROPNAME_BACKEND, previous_backend)); - TRY_MAIN(lookup_userprop(zfs_get_user_props(dataset), PROPNAME_KEY, previous_handle_s)); + TRY_MAIN(lookup_userprop(dataset, PROPNAME_BACKEND, previous_backend)); + TRY_MAIN(lookup_userprop(dataset, PROPNAME_KEY, previous_handle_s)); if(!!previous_backend ^ !!previous_handle_s) fprintf(stderr, "Inconsistent tzpfms metadata for %s: back-end is %s, but handle is %s?\n", zfs_get_name(dataset), previous_backend, previous_handle_s); @@ -98,6 +98,9 @@ int main(int argc, char ** argv) { quickscope_wrapper persistent_clearer{[&] { 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); + if(!ok && clear_key_props(dataset)) // Sync with zfs-tpm2-clear-key + fprintf(stderr, "You might need to run \"zfs inherit %s %s\" and \"zfs inherit %s %s\"!\n", PROPNAME_BACKEND, zfs_get_name(dataset), PROPNAME_KEY, + zfs_get_name(dataset)); }}; TRY_MAIN(set_key_props(dataset, THIS_BACKEND, persistent_handle)); diff --git a/src/bin/zfs-tpm-clear-key.cpp b/src/bin/zfs-tpm2-clear-key.cpp similarity index 61% rename from src/bin/zfs-tpm-clear-key.cpp rename to src/bin/zfs-tpm2-clear-key.cpp index 9b23197..a85a9d3 100644 --- a/src/bin/zfs-tpm-clear-key.cpp +++ b/src/bin/zfs-tpm2-clear-key.cpp @@ -6,19 +6,32 @@ #include <stdio.h> #include "../main.hpp" +#include "../tpm2.hpp" #include "../zfs.hpp" +#define THIS_BACKEND "TPM2" + + int main(int argc, char ** argv) { return do_main( argc, argv, "", [&](auto) {}, [&](auto dataset) { REQUIRE_KEY_LOADED(dataset); + TPMI_DH_PERSISTENT persistent_handle{}; + TRY_MAIN(parse_key_props(dataset, THIS_BACKEND, persistent_handle)); + if(zfs_crypto_rewrap(dataset, TRY_PTR("get clear rewrap args", clear_rewrap_args()), B_FALSE)) return __LINE__; // Error printed by libzfs - if(clear_key_props(dataset)) { + + TRY_MAIN(with_tpm2_session([&](auto tpm2_ctx, auto tpm2_session) { + TRY_MAIN(tpm2_free_persistent(tpm2_ctx, tpm2_session, persistent_handle)); + return 0; + })); + + if(clear_key_props(dataset)) { // Sync with zfs-tpm2-change-key fprintf(stderr, "You might need to run \"zfs inherit %s %s\" and \"zfs inherit %s %s\"!\n", PROPNAME_BACKEND, zfs_get_name(dataset), PROPNAME_KEY, zfs_get_name(dataset)); return __LINE__; diff --git a/src/bin/zfs-tpm2-load-key.cpp b/src/bin/zfs-tpm2-load-key.cpp index dfcd547..415fb80 100644 --- a/src/bin/zfs-tpm2-load-key.cpp +++ b/src/bin/zfs-tpm2-load-key.cpp @@ -9,7 +9,6 @@ #include "../fd.hpp" #include "../main.hpp" -#include "../parse.hpp" #include "../tpm2.hpp" #include "../zfs.hpp" @@ -22,29 +21,8 @@ int main(int argc, char ** argv) { return do_main( argc, argv, "n", [&](auto) { noop = B_TRUE; }, [&](auto dataset) { - char *backend{}, *handle_s{}; - TRY_MAIN(lookup_userprop(zfs_get_user_props(dataset), PROPNAME_BACKEND, backend)); - - if(!backend) { - fprintf(stderr, "Dataset %s not encrypted with tzpfms!\n", zfs_get_name(dataset)); - return __LINE__; - } - if(strcmp(backend, THIS_BACKEND)) { - fprintf(stderr, "Dataset %s encrypted with tzpfms back-end %s, but we are %s.\n", zfs_get_name(dataset), backend, THIS_BACKEND); - return __LINE__; - } - - TRY_MAIN(lookup_userprop(zfs_get_user_props(dataset), PROPNAME_KEY, handle_s)); - if(!handle_s) { - fprintf(stderr, "Dataset %s missing key data.\n", zfs_get_name(dataset)); - return __LINE__; - } - TPMI_DH_PERSISTENT handle{}; - if(parse_int(handle_s, handle)) { - fprintf(stderr, "Dataset %s's handle %s not valid.\n", zfs_get_name(dataset), handle_s); - return __LINE__; - } + TRY_MAIN(parse_key_props(dataset, THIS_BACKEND, handle)); uint8_t wrap_key[WRAPPING_KEY_LEN]; diff --git a/src/parse.hpp b/src/parse.hpp index fb5e675..7eee9e1 100644 --- a/src/parse.hpp +++ b/src/parse.hpp @@ -6,6 +6,7 @@ #include <charconv> #include <stdio.h> +#include <string.h> template <class T> diff --git a/src/tpm2.hpp b/src/tpm2.hpp index 9fa7b47..6f97f35 100644 --- a/src/tpm2.hpp +++ b/src/tpm2.hpp @@ -6,6 +6,8 @@ #include "common.hpp" +#include <libzfs.h> + #include <tss2/tss2_common.h> #include <tss2/tss2_esys.h> #include <tss2/tss2_rc.h> diff --git a/src/zfs.cpp b/src/zfs.cpp index 4fa9858..58e6ab5 100644 --- a/src/zfs.cpp +++ b/src/zfs.cpp @@ -3,9 +3,13 @@ #include "zfs.hpp" #include "common.hpp" +#include "main.hpp" +#include "parse.hpp" #include <libzfs.h> +#include <string.h> + // Funxion statics pull in libc++'s __cxa_guard_acquire() static nvlist_t * rrargs{}; @@ -59,14 +63,19 @@ nvlist_t * clear_rewrap_args() { TRY_NVL(what, _try_retl); \ }) -// TODO: how does this interact with nested datasets? -int lookup_userprop(nvlist_t * from, const char * name, char *& out) { +int lookup_userprop(zfs_handle_t * in, const char * name, char *& out) { // xyz.nabijaczleweli:tzpfms.key: // value: '76B0286BEB3FAF57536C47D9A2BAD38157FD522A75A59E72867BBFD6AF167395' // source: 'owo/enc' nvlist_t * vs{}; - TRY_LOOKUP("look up user property", nvlist_lookup_nvlist(from, name, &vs)); + TRY_LOOKUP("look up user property", nvlist_lookup_nvlist(zfs_get_user_props(in), name, &vs)); + + char * source{}; + TRY_LOOKUP("look up user property source", nvlist_lookup_string(vs, "source", &source)); + if(!source || strcmp(source, zfs_get_name(in))) + return 0; + TRY_LOOKUP("look up user property value", nvlist_lookup_string(vs, "value", &out)); return 0; } @@ -98,3 +107,31 @@ int clear_key_props(zfs_handle_t * from) { TRY("delete tzpfms.key", zfs_prop_inherit(from, PROPNAME_KEY, B_FALSE)); return 0; } + + +int parse_key_props(zfs_handle_t * in, const char * our_backend, uint32_t & handle) { + char *backend{}, *handle_s{}; + TRY_MAIN(lookup_userprop(in, PROPNAME_BACKEND, backend)); + + if(!backend) { + fprintf(stderr, "Dataset %s not encrypted with tzpfms!\n", zfs_get_name(in)); + return __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_s)); + if(!handle_s) { + fprintf(stderr, "Dataset %s missing key data.\n", zfs_get_name(in)); + return __LINE__; + } + + if(parse_int(handle_s, handle)) { + fprintf(stderr, "Dataset %s's handle %s not valid.\n", zfs_get_name(in), handle_s); + return __LINE__; + } + + return 0; +} diff --git a/src/zfs.hpp b/src/zfs.hpp index 8324569..74f7d4b 100644 --- a/src/zfs.hpp +++ b/src/zfs.hpp @@ -6,9 +6,6 @@ #include <libzfs.h> #include <sys/nvpair.h> -// #include <sys/fs/zfs.h> -//// #include <sys/zio_crypt.h> -// #define WRAPPING_KEY_LEN 32 #define TRY_NVL(what, ...) TRY_GENERIC(what, , , _try_ret, _try_ret, strerror, __VA_ARGS__) @@ -36,10 +33,13 @@ extern nvlist_t * clear_rewrap_args(); /// Extract user property name from ZFS property list from to out. /// /// Returns success but does not touch out on not found. -extern int lookup_userprop(nvlist_t * from, const char * name, char *& out); +extern int lookup_userprop(zfs_handle_t * from, const char * name, char *& out); /// Set required decoding props on the dataset extern int set_key_props(zfs_handle_t * on, const char * backend, uint32_t handle); /// Remove decoding props from the dataset extern int clear_key_props(zfs_handle_t * from); + +/// Read in decoding props from the dataset +extern int parse_key_props(zfs_handle_t * in, const char * our_backend, uint32_t & handle);