diff --git a/src/bin/zfs-tpm1x-change-key.cpp b/src/bin/zfs-tpm1x-change-key.cpp index 554f74d..d2de3b1 100644 --- a/src/bin/zfs-tpm1x-change-key.cpp +++ b/src/bin/zfs-tpm1x-change-key.cpp @@ -58,13 +58,23 @@ int main(int argc, char ** argv) { Tspi_Policy_FlushSecret(parent_key_policy); Tspi_Context_CloseObject(ctx, parent_key_policy); }}; - // TODO: this is where we'd prompt for a password if we supported it yet - fprintf(stderr, "Tspi_Policy_SetSecret(\"adenozynotrójfosforan\") = %s\n", - Trspi_Error_String( - Tspi_Policy_SetSecret(parent_key_policy, TSS_SECRET_MODE_PLAIN, strlen("adenozynotrójfosforan"), (BYTE *)"adenozynotrójfosforan"))); + { + uint8_t * parent_key_passphrase{}; + size_t parent_key_passphrase_len{}; + TRY_MAIN(read_new_passphrase("wrapping key (or empty for none)", parent_key_passphrase, parent_key_passphrase_len)); + quickscope_wrapper parent_key_passphrase_deleter{[&] { free(parent_key_passphrase); }}; - TRY_MAIN(try_srk("create sealant key (did you take ownership?)", srk_policy, [&] { return Tspi_Key_CreateKey(parent_key, srk, 0); })); + if(parent_key_passphrase_len) + TRY_TPM1X("assign passphrase to parent_key key", + Tspi_Policy_SetSecret(parent_key_policy, TSS_SECRET_MODE_PLAIN, parent_key_passphrase_len, parent_key_passphrase)); + else + TRY_TPM1X("assign default sealant key secret", + Tspi_Policy_SetSecret(parent_key_policy, TSS_SECRET_MODE_SHA1, sizeof(parent_key_secret), (BYTE *)parent_key_secret)); + } + + TRY_MAIN(try_policy_or_passphrase("create sealant key (did you take ownership?)", "SRK", srk_policy, + [&] { return Tspi_Key_CreateKey(parent_key, srk, 0); })); TRY_TPM1X("load sealant key", Tspi_Key_LoadKey(parent_key, srk)); diff --git a/src/bin/zfs-tpm1x-load-key.cpp b/src/bin/zfs-tpm1x-load-key.cpp index ad7c552..23f6451 100644 --- a/src/bin/zfs-tpm1x-load-key.cpp +++ b/src/bin/zfs-tpm1x-load-key.cpp @@ -35,8 +35,9 @@ int main(int argc, char ** argv) { uint8_t wrap_key[WRAPPING_KEY_LEN]{}; TRY_MAIN(with_tpm1x_session([&](auto ctx, auto srk, auto srk_policy) { TSS_HOBJECT parent_key{}; - TRY_MAIN(try_srk("load sealant key from blob (did you take ownership?)", srk_policy, - [&] { return Tspi_Context_LoadKeyByBlob(ctx, srk, handle.parent_key_blob_len, handle.parent_key_blob, &parent_key); })); + TRY_MAIN(try_policy_or_passphrase("load sealant key from blob (did you take ownership?)", "SRK", srk_policy, [&] { + 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); }}; TSS_HPOLICY parent_key_policy{}; @@ -46,9 +47,8 @@ int main(int argc, char ** argv) { Tspi_Policy_FlushSecret(parent_key_policy); Tspi_Context_CloseObject(ctx, parent_key_policy); }}; - fprintf(stderr, "Tspi_Policy_SetSecret(\"adenozynotrójfosforan\") = %s\n", - Trspi_Error_String( - Tspi_Policy_SetSecret(parent_key_policy, TSS_SECRET_MODE_PLAIN, strlen("adenozynotrójfosforan"), (BYTE *)"adenozynotrójfosforan"))); + TRY_TPM1X("assign default sealant key secret", + Tspi_Policy_SetSecret(parent_key_policy, TSS_SECRET_MODE_SHA1, sizeof(parent_key_secret), (BYTE *)parent_key_secret)); TSS_HOBJECT sealed_object{}; @@ -61,7 +61,8 @@ int main(int argc, char ** argv) { uint8_t * loaded_wrap_key{}; uint32_t loaded_wrap_key_len{}; - TRY_TPM1X("unseal wrapping key", Tspi_Data_Unseal(sealed_object, parent_key, &loaded_wrap_key_len, &loaded_wrap_key)); + TRY_MAIN(try_policy_or_passphrase("unseal wrapping key", "wrapping key", parent_key_policy, + [&] { return Tspi_Data_Unseal(sealed_object, parent_key, &loaded_wrap_key_len, &loaded_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)); for(auto i = 0u; i < loaded_wrap_key_len; ++i) diff --git a/src/fd.cpp b/src/fd.cpp index 5ce1095..30d77c7 100644 --- a/src/fd.cpp +++ b/src/fd.cpp @@ -3,9 +3,12 @@ #include "fd.hpp" +#include "main.hpp" + #include #include #include +#include #include @@ -55,3 +58,111 @@ int write_exact(const char * path, const void * data, size_t len, mode_t mode) { return 0; } + + +/// 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 caught_interrupt; + + struct termios old_term; + struct sigaction osigint, osigtstp; + + len_out = 0; + + auto from_tty = isatty(0); + if(from_tty) { + // Handle SIGINT and ignore SIGSTP. + // This is necessary to restore the state of the terminal. + struct sigaction act {}; + sigemptyset(&act.sa_mask); + + caught_interrupt = 0; + act.sa_handler = [](auto sig) { caught_interrupt = sig; }; + sigaction(SIGINT, &act, &osigint); + + act.sa_handler = SIG_IGN; + sigaction(SIGTSTP, &act, &osigtstp); + + // Prompt for the key + printf("%s %spassphrase for %s: ", again ? "Re-enter" : "Enter", newkey ? "new " : "", whom); + fflush(stdout); + + // Disable the terminal echo for key input + tcgetattr(0, &old_term); + + auto new_term = old_term; + new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + TRY("disable echo", tcsetattr(0, TCSAFLUSH, &new_term)); + } + quickscope_wrapper stdin_restorer{[&] { + if(from_tty) { + // Reset the terminal + tcsetattr(0, TCSAFLUSH, &old_term); + sigaction(SIGINT, &osigint, nullptr); + sigaction(SIGTSTP, &osigtstp, nullptr); + + // If we caught a signal, re-throw it now + if(caught_interrupt != 0) + kill(getpid(), caught_interrupt); + + // Print the newline that was not echoed + putchar('\n'); + } + }}; + + + // Read the key material + size_t buflen{}; + errno = 0; + auto bytes = getline((char **)&buf, &buflen, stdin); + switch(bytes) { + case -1: + if(errno != 0) + TRY("read in passphrase", bytes); + else // EOF + bytes = 0; + break; + case 0: + break; + default: + // Trim ending newline, if any + if(buf[bytes - 1] == '\n') { + buf[bytes - 1] = '\0'; + --bytes; + } + break; + } + + len_out = bytes; + return 0; +} + +int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out) { + return get_key_material_raw(whom, false, false, buf, len_out); +} + +int read_new_passphrase(const char * whom, uint8_t *& buf, size_t & len_out) { + uint8_t * first_passphrase{}; + size_t first_passphrase_len{}; + TRY_MAIN(get_key_material_raw(whom, false, true, first_passphrase, first_passphrase_len)); + quickscope_wrapper first_passphrase_deleter{[&] { free(first_passphrase); }}; + + uint8_t * second_passphrase{}; + size_t second_passphrase_len{}; + TRY_MAIN(get_key_material_raw(whom, true, true, second_passphrase, second_passphrase_len)); + quickscope_wrapper second_passphrase_deleter{[&] { free(second_passphrase); }}; + + if(second_passphrase_len != first_passphrase_len || memcmp(first_passphrase, second_passphrase, first_passphrase_len)) { + fprintf(stderr, "Provided keys do not match.\n"); + return __LINE__; + } + + if(second_passphrase_len) { + buf = second_passphrase; + second_passphrase = nullptr; + } else + buf = nullptr; + + len_out = second_passphrase_len; + return 0; +} diff --git a/src/fd.hpp b/src/fd.hpp index ac4aa85..c6554bb 100644 --- a/src/fd.hpp +++ b/src/fd.hpp @@ -32,3 +32,6 @@ 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); + +extern int read_known_passphrase(const char * whom, uint8_t *& buf, size_t & len_out); +extern int read_new_passphrase(const char * whom, uint8_t *& buf, size_t & len_out); diff --git a/src/tpm1x.cpp b/src/tpm1x.cpp index 6e6d919..1defbc0 100644 --- a/src/tpm1x.cpp +++ b/src/tpm1x.cpp @@ -8,6 +8,7 @@ #include +/// Used as secret for the sealed object itself // I just got this out of /dev/random static const constexpr uint8_t sealing_secret[TPM_SHA1_160_HASH_LEN]{0xB9, 0xEE, 0x71, 0x5D, 0xBE, 0x4B, 0x24, 0x3F, 0xAA, 0x81, 0xEA, 0x04, 0x30, 0x6E, 0x06, 0x37, 0x10, 0x38, 0x3E, 0x35}; diff --git a/src/tpm1x.hpp b/src/tpm1x.hpp index c71f572..e1e6f9d 100644 --- a/src/tpm1x.hpp +++ b/src/tpm1x.hpp @@ -5,6 +5,10 @@ #include "common.hpp" +#include "fd.hpp" +#include "main.hpp" + +#include #include #include @@ -17,6 +21,12 @@ #define TRY_TPM1X(what, ...) TRY_GENERIC(what, , != TPM_SUCCESS, _try_ret, __LINE__, Trspi_Error_String, __VA_ARGS__) +/// Used as default secret if passphrase wasn't provided for wrapping key for the sealed object +// I just got this out of /dev/random +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}; + + template int with_tpm1x_session(F && func) { TSS_HCONTEXT ctx{}; // All memory lives as long as this does @@ -44,15 +54,26 @@ int with_tpm1x_session(F && func) { return func(ctx, srk, srk_policy); } -/// Try to run func() with the current SRK policy (well-known by default); if it fails, prompt for password and reattempt. +/// Try to run func() with the current policy; if it fails, prompt for passphrase and reattempt up to three total times. template -int try_srk(const char * what, TSS_HPOLICY srk_policy, F && func) { +int try_policy_or_passphrase(const char * what, const char * what_for, TSS_HPOLICY policy, F && func) { + auto get_passphrase = [&] { + BYTE * pass{}; + size_t pass_len{}; + TRY_MAIN(read_known_passphrase(what_for, pass, pass_len)); + quickscope_wrapper pass_deleter{[&] { free(pass); }}; + + TRY_TPM1X("set passphrase secret on policy", Tspi_Policy_SetSecret(policy, TSS_SECRET_MODE_PLAIN, pass_len, pass)); + return 0; + }; + auto err = func(); // Equivalent to TSS_ERROR_LAYER(err) == TSS_LAYER_TPM && TSS_ERROR_CODE(err) == TPM_E_AUTHFAIL - if((err & TSS_LAYER_TSP) == TSS_LAYER_TPM && (err & TSS_MAX_ERROR) == TPM_E_AUTHFAIL) { - // TODO: read SRK password from stdin here - TRY_TPM1X("set password secret on SRK policy", Tspi_Policy_SetSecret(srk_policy, TSS_SECRET_MODE_PLAIN, strlen("dupanina"), (BYTE *)"dupanina")); + for(int i = 0; ((err & TSS_LAYER_TSP) == TSS_LAYER_TPM && (err & TSS_MAX_ERROR) == TPM_E_AUTHFAIL) && i < 3; ++i) { + if(i) + fprintf(stderr, "Couldn't %s: %s\n", what, Trspi_Error_String(err)); + TRY_MAIN(get_passphrase()); err = func(); } @@ -80,9 +101,5 @@ struct tpm1x_handle { /// The stored handle is in the form [%X:%X] where the first blob is the parent key and the second is the sealed data. extern int tpm1x_parse_handle(const char * dataset_name, char * handle_s, tpm1x_handle & handle); - +/// 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 tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPM2B_DATA & metadata, 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_free_persistent(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle);