diff --git a/configMakefile b/configMakefile index be5b7fa..368dcf0 100644 --- a/configMakefile +++ b/configMakefile @@ -56,8 +56,8 @@ LDD ?= ldd AWK ?= awk PATCHELF ?= patchelf OBJ := .o -CXXAR := -O3 -std=c++17 -fno-exceptions -Wall -Wextra $(CXXSPECIFIC) -pipe $(INCCXXAR) $(PIC) -STRIP ?= strip +CXXAR := -g -O3 -std=c++17 -fno-exceptions -Wall -Wextra $(CXXSPECIFIC) -pipe $(INCCXXAR) $(PIC) +STRIP ?= @echo strip STRIPAR := --strip-all --remove-section=.comment --remove-section=.note OUTDIR := out/ diff --git a/src/bin/rewrap.cpp b/src/bin/rewrap.cpp index 348b0bd..d1818fd 100644 --- a/src/bin/rewrap.cpp +++ b/src/bin/rewrap.cpp @@ -12,23 +12,42 @@ #include "../zfs.hpp" -static const constexpr uint8_t our_test_key[WRAPPING_KEY_LEN] = { - 0xe2, 0xac, 0xf7, 0x89, 0x32, 0x37, 0xcb, 0x94, 0x67, 0xeb, 0x2b, 0xe9, 0xa3, 0x48, 0x83, 0x72, - 0xd5, 0x4c, 0xc5, 0x1c, 0x99, 0x65, 0xb0, 0x8d, 0x05, 0xa6, 0xd5, 0xff, 0x7a, 0xf7, 0xeb, 0xfc, -}; - - int main(int argc, char ** argv) { + const char * backup{}; return do_main( - argc, argv, "", [](auto) {}, - [](auto dataset) { + argc, argv, "b:", [&](auto) { backup = optarg; }, + [&](auto dataset) { + if(zfs_prop_get_int(dataset, ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE) { + fprintf(stderr, "Key change error: Key must be loaded.\n"); // mimic libzfs error output + return __LINE__; + } + + + uint8_t wrap_key[WRAPPING_KEY_LEN]; + TRY_MAIN(read_exact("/dev/random", wrap_key, sizeof(wrap_key))); + if(backup) + TRY_MAIN(write_exact(backup, wrap_key, sizeof(wrap_key), 0400)); + + + auto wrap_key_s = static_cast(TRY_PTR("wrap_key_s", alloca(WRAPPING_KEY_LEN * 2 + 1))); + { + auto cur = wrap_key_s; + for(auto kb : wrap_key) { + *cur++ = "0123456789ABCDEF"[(kb >> 4) & 0x0F]; + *cur++ = "0123456789ABCDEF"[(kb >> 0) & 0x0F]; + } + *cur = '\0'; + } + TRY_MAIN(zfs_prop_set(dataset, "xyz.nabijaczleweli:tzpfms.key", wrap_key_s)); + + /// 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: /// instead, get an FD, write the raw key data there, dup() it onto stdin, /// let libzfs read it, then restore stdin int key_fd; - TRY_MAIN(filled_fd(key_fd, (void *)our_test_key, WRAPPING_KEY_LEN)); + TRY_MAIN(filled_fd(key_fd, wrap_key, WRAPPING_KEY_LEN)); quickscope_wrapper key_fd_deleter{[=] { close(key_fd); }}; diff --git a/src/bin/unlock.cpp b/src/bin/unlock.cpp index 6dc19a1..6f06e66 100644 --- a/src/bin/unlock.cpp +++ b/src/bin/unlock.cpp @@ -2,28 +2,83 @@ #include -// #include -#define WRAPPING_KEY_LEN 32 #include #include "../fd.hpp" #include "../main.hpp" +#include "../zfs.hpp" -static const constexpr uint8_t our_test_key[WRAPPING_KEY_LEN] = { - 0xe2, 0xac, 0xf7, 0x89, 0x32, 0x37, 0xcb, 0x94, 0x67, 0xeb, 0x2b, 0xe9, 0xa3, 0x48, 0x83, 0x72, - 0xd5, 0x4c, 0xc5, 0x1c, 0x99, 0x65, 0xb0, 0x8d, 0x05, 0xa6, 0xd5, 0xff, 0x7a, 0xf7, 0xeb, 0xfc, -}; +static int hex_nibble(char c) { + switch(c) { + case '0': + return 0x0; + case '1': + return 0x1; + case '2': + return 0x2; + case '3': + return 0x3; + case '4': + return 0x4; + case '5': + return 0x5; + case '6': + return 0x6; + case '7': + return 0x7; + case '8': + return 0x8; + case '9': + return 0x9; + case 'A': + case 'a': + return 0xA; + case 'B': + case 'b': + return 0xB; + case 'C': + case 'c': + return 0xC; + case 'D': + case 'd': + return 0xD; + case 'E': + case 'e': + return 0xE; + case 'F': + case 'f': + return 0xF; + default: + fprintf(stderr, "Character %c (0x%02X) not hex?\n", c, static_cast(c)); + return 0; + } +} int main(int argc, char ** argv) { auto noop = B_FALSE; return do_main( - argc, argv, "n", [&](char) { noop = B_TRUE; }, + argc, argv, "n", [&](auto) { noop = B_TRUE; }, [&](auto dataset) { + char * stored_key_s{}; + TRY_MAIN(lookup_userprop(zfs_get_user_props(dataset), "xyz.nabijaczleweli:tzpfms.key", stored_key_s)); + errno = EEXIST; + TRY_PTR("find encryption key prop", stored_key_s); + + auto stored_key_len = strlen(stored_key_s) / 2; + auto stored_key = static_cast(TRY_PTR("stored_key", alloca(stored_key_len))); + { + auto cur = stored_key_s; + for(auto kcur = stored_key; kcur < stored_key + stored_key_len; ++kcur) { + *kcur = (hex_nibble(cur[0]) << 4) | hex_nibble(cur[1]); + cur += 2; + } + } + int key_fd; - TRY_MAIN(filled_fd(key_fd, (void *)our_test_key, WRAPPING_KEY_LEN)); + TRY_MAIN(filled_fd(key_fd, (void *)stored_key, stored_key_len)); quickscope_wrapper key_fd_deleter{[=] { close(key_fd); }}; diff --git a/src/fd.cpp b/src/fd.cpp index 297f21f..e83560d 100644 --- a/src/fd.cpp +++ b/src/fd.cpp @@ -1,8 +1,13 @@ - /* SPDX-License-Identifier: MIT */ +/* SPDX-License-Identifier: MIT */ #include "fd.hpp" +#include +#include +#include +#include + int filled_fd(int & fd, const void * with, size_t with_len) { int pipes[2]; @@ -19,3 +24,34 @@ int filled_fd(int & fd, const void * with, size_t with_len) { return 0; } + + +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(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) { + auto outfd = TRY("open output file", open(path, O_WRONLY | O_CREAT | O_EXCL, mode)); + quickscope_wrapper infd_deleter{[=] { close(outfd); }}; + + while(len) { + const auto rd = TRY("write to output file", write(outfd, data, len)); + len -= rd; + data = static_cast(data) + rd; + } + + return 0; +} diff --git a/src/fd.hpp b/src/fd.hpp index 654c8d8..ac4aa85 100644 --- a/src/fd.hpp +++ b/src/fd.hpp @@ -26,3 +26,9 @@ int with_stdin_at(int fd, F && what) { /// with_len may not exceed pipe capacity (64k by default) 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 +extern int write_exact(const char * path, const void * data, size_t len, mode_t mode); diff --git a/src/main.hpp b/src/main.hpp index 3720f04..a80ee4d 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -5,6 +5,7 @@ #include "common.hpp" +#include #include #include @@ -51,7 +52,6 @@ int do_main(int argc, char ** argv, const char * getoptions, G && getoptfn, M && } else if(!dataset_is_root) { printf("Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root); // TODO: disallow maybe? or require force option? - strcpy(argv[1], encryption_root); zfs_close(dataset); dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM)); } diff --git a/src/zfs.cpp b/src/zfs.cpp index a7c86c2..6da224d 100644 --- a/src/zfs.cpp +++ b/src/zfs.cpp @@ -7,27 +7,44 @@ #include -#define TRY_NVL(what, ...) TRY_GENERIC(what, , , _try_ret, _try_ret, __VA_ARGS__) - - +// Funxion statics pull in libc++'s __cxa_guard_acquire() +static nvlist_t * rrargs{}; +static quickscope_wrapper ret_deleter{[] { nvlist_free(rrargs); }}; nvlist_t * rewrap_args() { - static nvlist_t * ret{}; - static quickscope_wrapper ret_deleter{[&] { nvlist_free(ret); }}; - - if(!ret) + if(!rrargs) if(auto err = [&] { - TRY_NVL("allocate rewrap nvlist", nvlist_alloc(&ret, NV_UNIQUE_NAME, 0)); + TRY_NVL("allocate rewrap nvlist", nvlist_alloc(&rrargs, NV_UNIQUE_NAME, 0)); TRY_NVL("add keyformat to rewrap nvlist", - nvlist_add_string(ret, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), "raw")); // Why can't this be uint64 and ZFS_KEYFORMAT_RAW? - TRY_NVL("add keylocation to rewrap nvlist", nvlist_add_string(ret, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), "prompt")); + nvlist_add_string(rrargs, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), "raw")); // Why can't this be uint64 and ZFS_KEYFORMAT_RAW? + TRY_NVL("add keylocation to rewrap nvlist", nvlist_add_string(rrargs, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), "prompt")); return 0; }(); - err && ret) { - nvlist_free(ret); - ret = nullptr; + err && rrargs) { + nvlist_free(rrargs); + rrargs = nullptr; errno = err; } - return ret; + return rrargs; +} + + +#define TRY_LOOKUP(what, ...) \ + ({ \ + const auto _try_retl = (__VA_ARGS__); \ + if(_try_retl == ENOENT) \ + return 0; \ + TRY_NVL(what, _try_retl); \ + }) + +int lookup_userprop(nvlist_t * from, 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 value", nvlist_lookup_string(vs, "value", &out)); + return 0; } diff --git a/src/zfs.hpp b/src/zfs.hpp index ffb38cb..56fca26 100644 --- a/src/zfs.hpp +++ b/src/zfs.hpp @@ -4,12 +4,20 @@ #pragma once -#include +#include // #include // #include //// #include // #define WRAPPING_KEY_LEN 32 +#define TRY_NVL(what, ...) TRY_GENERIC(what, , , _try_ret, _try_ret, __VA_ARGS__) + + /// Static nvlist with {keyformat=raw, keylocation=prompt} extern nvlist_t * 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);