From d0cdc08ace86b0ffb476a1ff25cfe535b15a4b75 Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Mon, 24 Jun 2019 09:26:33 +0200 Subject: [PATCH 01/14] Add tokens directory to source tree. --- Makefile.am | 1 + tokens/Makemodule.am | 5 +++++ tokens/libcryptsetup-token.sym | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 tokens/Makemodule.am create mode 100644 tokens/libcryptsetup-token.sym diff --git a/Makefile.am b/Makefile.am index 43f81786..0c9f325a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -40,6 +40,7 @@ include lib/Makemodule.am include lib/cli/Makemodule.am include src/Makemodule.am +include tokens/Makemodule.am ACLOCAL_AMFLAGS = -I m4 diff --git a/tokens/Makemodule.am b/tokens/Makemodule.am new file mode 100644 index 00000000..61645b23 --- /dev/null +++ b/tokens/Makemodule.am @@ -0,0 +1,5 @@ +EXTRA_DIST += tokens/libcryptsetup-token.sym + +TOKENS_LDFLAGS = $(AM_LDFLAGS) -no-undefined \ + -Wl,--version-script=$(top_srcdir)/tokens/libcryptsetup-token.sym \ + -version-info 0:0:0 diff --git a/tokens/libcryptsetup-token.sym b/tokens/libcryptsetup-token.sym new file mode 100644 index 00000000..c4456ddd --- /dev/null +++ b/tokens/libcryptsetup-token.sym @@ -0,0 +1,4 @@ +CRYPTSETUP_TOKEN_1.0 { + global: cryptsetup_token_handler; + local: *; +}; From 61c99ff4d754f4f3fd03ff32936a3530ae673d3f Mon Sep 17 00:00:00 2001 From: Ondrej Kozina Date: Mon, 23 Nov 2020 11:24:29 +0100 Subject: [PATCH 02/14] Allow checking token arguments in dry run. --- src/cryptsetup.c | 61 +++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 3bc0cd26..f3836da7 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -2644,33 +2644,7 @@ static int _token_export(struct crypt_device *cd) static int action_token(void) { int r; - struct crypt_device *cd = NULL; - enum { ADD = 0, REMOVE, IMPORT, EXPORT } action; - - if (!strcmp(action_argv[0], "add")) { - if (!ARG_SET(OPT_KEY_DESCRIPTION_ID)) { - log_err(_("--key-description parameter is mandatory for token add action.")); - return -EINVAL; - } - action = ADD; - } else if (!strcmp(action_argv[0], "remove")) { - if (ARG_INT32(OPT_TOKEN_ID_ID) == CRYPT_ANY_TOKEN) { - log_err(_("Action requires specific token. Use --token-id parameter.")); - return -EINVAL; - } - action = REMOVE; - } else if (!strcmp(action_argv[0], "import")) { - action = IMPORT; - } else if (!strcmp(action_argv[0], "export")) { - if (ARG_INT32(OPT_TOKEN_ID_ID)== CRYPT_ANY_TOKEN) { - log_err(_("Action requires specific token. Use --token-id parameter.")); - return -EINVAL; - } - action = EXPORT; - } else { - log_err(_("Invalid token operation %s."), action_argv[0]); - return -EINVAL; - } + struct crypt_device *cd; if ((r = crypt_init(&cd, uuid_or_device(ARG_STR(OPT_HEADER_ID) ?: action_argv[1])))) return r; @@ -2682,18 +2656,16 @@ static int action_token(void) return r; } - if (action == ADD) + r = -EINVAL; + + if (!strcmp(action_argv[0], "add")) r = _token_add(cd); /* adds only luks2-keyring type */ - else if (action == REMOVE) + else if (!strcmp(action_argv[0], "remove")) r = _token_remove(cd); - else if (action == IMPORT) + else if (!strcmp(action_argv[0], "import")) r = _token_import(cd); - else if (action == EXPORT) + else if (!strcmp(action_argv[0], "export")) r = _token_export(cd); - else { - log_dbg("Internal token action error."); - r = -EINVAL; - } crypt_free(cd); @@ -3949,6 +3921,25 @@ int main(int argc, const char **argv) return 0; } + /* token action specific check */ + if (!strcmp(aname, TOKEN_ACTION)) { + if (strcmp(action_argv[0], "add") && + strcmp(action_argv[0], "remove") && + strcmp(action_argv[0], "import") && + strcmp(action_argv[0], "export")) + usage(popt_context, EXIT_FAILURE, _("Unknown token action."), + poptGetInvocationName(popt_context)); + + if (!ARG_SET(OPT_KEY_DESCRIPTION_ID) && !strcmp(action_argv[0], "add")) + usage(popt_context, EXIT_FAILURE, _("Option --key-description is required."), + poptGetInvocationName(popt_context)); + + if (ARG_INT32(OPT_TOKEN_ID_ID) == CRYPT_ANY_TOKEN && + (!strcmp(action_argv[0], "remove") || !strcmp(action_argv[0], "export"))) + usage(popt_context, EXIT_FAILURE, _("Option --token-id is required."), + poptGetInvocationName(popt_context)); + } + if (ARG_SET(OPT_DISABLE_KEYRING_ID)) (void) crypt_volume_key_keyring(NULL, 0); From deb18fc1228eb624ab9b9f08e49af2c1f33e80d4 Mon Sep 17 00:00:00 2001 From: Ondrej Kozina Date: Fri, 13 Nov 2020 14:33:57 +0100 Subject: [PATCH 03/14] Refactor verbose token action reporting. also avoids unlikely possibility of reporting succcessful token add before failing assigning the token to keyslot. --- src/cryptsetup.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/cryptsetup.c b/src/cryptsetup.c index f3836da7..d4ba383f 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -2553,21 +2553,20 @@ static int _token_add(struct crypt_device *cd) } token = r; - tools_token_msg(token, CREATED); r = crypt_token_assign_keyslot(cd, token, ARG_INT32(OPT_KEY_SLOT_ID)); if (r < 0) { log_err(_("Failed to assign token %d to keyslot %d."), token, ARG_INT32(OPT_KEY_SLOT_ID)); (void) crypt_token_json_set(cd, token, NULL); + return r; } - return r; + return token; } static int _token_remove(struct crypt_device *cd) { crypt_token_info token_info; - int r; token_info = crypt_token_status(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); if (token_info < CRYPT_TOKEN_INACTIVE) { @@ -2578,10 +2577,7 @@ static int _token_remove(struct crypt_device *cd) return -EINVAL; } - r = crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); - tools_token_msg(r, REMOVED); - - return r; + return crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); } static int _token_import(struct crypt_device *cd) @@ -2614,17 +2610,17 @@ static int _token_import(struct crypt_device *cd) } token = r; - tools_token_msg(token, CREATED); if (ARG_INT32(OPT_KEY_SLOT_ID) != CRYPT_ANY_SLOT) { r = crypt_token_assign_keyslot(cd, token, ARG_INT32(OPT_KEY_SLOT_ID)); if (r < 0) { log_err(_("Failed to assign token %d to keyslot %d."), token, ARG_INT32(OPT_KEY_SLOT_ID)); (void) crypt_token_json_set(cd, token, NULL); + return r; } } - return r; + return token; } static int _token_export(struct crypt_device *cd) @@ -2658,13 +2654,16 @@ static int action_token(void) r = -EINVAL; - if (!strcmp(action_argv[0], "add")) + if (!strcmp(action_argv[0], "add")) { r = _token_add(cd); /* adds only luks2-keyring type */ - else if (!strcmp(action_argv[0], "remove")) + tools_token_msg(r, CREATED); + } else if (!strcmp(action_argv[0], "remove")) { r = _token_remove(cd); - else if (!strcmp(action_argv[0], "import")) + tools_token_msg(r, REMOVED); + } else if (!strcmp(action_argv[0], "import")) { r = _token_import(cd); - else if (!strcmp(action_argv[0], "export")) + tools_token_msg(r, CREATED); + } else if (!strcmp(action_argv[0], "export")) r = _token_export(cd); crypt_free(cd); From 2eba7accfeb2b16be8d8a310331480bc2b74f313 Mon Sep 17 00:00:00 2001 From: Ondrej Kozina Date: Wed, 8 Jul 2020 18:50:48 +0200 Subject: [PATCH 04/14] WIP: Add support for loadable plugin in tools. - cryptsetup only TODO: - add version reporting --- configure.ac | 13 + lib/cli/cli.c | 18 +- lib/cli/cli_internal.h | 7 + src/Makemodule.am | 10 +- src/cryptsetup.c | 450 +++++++++++++++++++++------ src/cryptsetup.h | 50 +++ src/cryptsetup_arg_list.h | 2 + src/cryptsetup_args.h | 1 + src/plugin.h | 52 ++++ src/utils_plugins.c | 535 +++++++++++++++++++++++++++++++++ tokens/libcryptsetup-token.sym | 13 + 11 files changed, 1058 insertions(+), 93 deletions(-) create mode 100644 src/plugin.h create mode 100644 src/utils_plugins.c diff --git a/configure.ac b/configure.ac index b7a46e6a..0e76e7da 100644 --- a/configure.ac +++ b/configure.ac @@ -126,6 +126,17 @@ if test "x$enable_external_tokens" = "xyes"; then AC_DEFINE(USE_EXTERNAL_TOKENS, 1, [Use external tokens]) fi +dnl ========================================================================== +dnl enable support cli token plugins (currently affects only cryptsetup) + +AC_ARG_ENABLE([external-cli-tokens], + AS_HELP_STRING([--enable-external-cli-tokens], [enable external LUKS2 tokens plugins in cryptsetup])) +if test "x$enable_external_cli_tokens" = "xyes"; then + AC_DEFINE(USE_EXTERNAL_CLI_TOKENS, 1, [Use cli external tokens]) + TOKENSCLI_LIBS="-ldl" +fi +AM_CONDITIONAL(CLI_TOKENS, test "x$enable_external_cli_tokens" = "xyes") + dnl ========================================================================== AM_GNU_GETTEXT([external],[need-ngettext]) @@ -542,6 +553,8 @@ AC_SUBST([BLKID_LIBS]) AC_SUBST([LIBCRYPTSETUP_VERSION]) AC_SUBST([LIBCRYPTSETUP_VERSION_INFO]) +AC_SUBST([TOKENSCLI_LIBS]) + AC_SUBST([LIBCRYPTSETUP_CLI_VERSION]) AC_SUBST([LIBCRYPTSETUP_CLI_VERSION_INFO]) diff --git a/lib/cli/cli.c b/lib/cli/cli.c index 466dfda6..3bbb4ab9 100644 --- a/lib/cli/cli.c +++ b/lib/cli/cli.c @@ -344,9 +344,25 @@ static const struct tools_arg *find_arg_in_args(const char *name, const struct t return NULL; } +static const struct tools_arg *find_arg_by_name_plugin(struct crypt_cli *ctx, const char *name) +{ + const char *p = strstr(name, "-"); + + if (!p || p == name || !ctx) + return NULL; + + if (!strncmp(name, ctx->plugin_args.name, p - name)) + return find_arg_in_args(p + 1, ctx->plugin_args.args, ctx->plugin_args.count); + + return NULL; +} + static const struct tools_arg *find_arg_by_name(struct crypt_cli *ctx, const char *name) { - return find_arg_in_args(name, ctx->core_args, ctx->core_args_count); + if (!strncmp(name, "plugin-", 7)) + return find_arg_by_name_plugin(ctx, name + 7); + else + return find_arg_in_args(name, ctx->core_args, ctx->core_args_count); } int crypt_cli_arg_value(struct crypt_cli *ctx, const char *name, void *value) diff --git a/lib/cli/cli_internal.h b/lib/cli/cli_internal.h index 38be83c8..21ec1249 100644 --- a/lib/cli/cli_internal.h +++ b/lib/cli/cli_internal.h @@ -42,9 +42,16 @@ struct tools_arg { const char *actions_array[MAX_ACTIONS]; }; +struct plugin_args { + const char *name; + const struct tools_arg *args; + size_t count; +}; + struct crypt_cli { const struct tools_arg *core_args; size_t core_args_count; + struct plugin_args plugin_args; }; #endif diff --git a/src/Makemodule.am b/src/Makemodule.am index beb136f0..2b301301 100644 --- a/src/Makemodule.am +++ b/src/Makemodule.am @@ -1,9 +1,6 @@ # cryptsetup if CRYPTSETUP -# cryptsetup_CPPFLAGS = $(AM_CPPFLAGS) \ -# -I $(top_srcdir)/lib/cli - cryptsetup_SOURCES = \ lib/utils_crypt.c \ lib/utils_loop.c \ @@ -16,17 +13,20 @@ cryptsetup_SOURCES = \ src/utils_blockdev.c \ src/utils_arg_names.h \ src/utils_arg_macros.h \ + src/utils_plugins.c \ src/cryptsetup.c \ src/cryptsetup.h \ src/cryptsetup_args.h \ - src/cryptsetup_arg_list.h + src/cryptsetup_arg_list.h \ + src/plugin.h cryptsetup_LDADD = $(LDADD) \ libcryptsetup.la \ libcryptsetup_cli.la \ @POPT_LIBS@ \ @UUID_LIBS@ \ - @BLKID_LIBS@ + @BLKID_LIBS@ \ + @TOKENSCLI_LIBS@ sbin_PROGRAMS += cryptsetup diff --git a/src/cryptsetup.c b/src/cryptsetup.c index d4ba383f..9efbe37f 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -26,6 +26,9 @@ #include "cryptsetup.h" #include "cryptsetup_args.h" +static bool processing_help = false; +static bool popt_first_run = true; + static char *keyfiles[MAX_KEYFILES]; static char *keyfile_stdin = NULL; @@ -40,6 +43,13 @@ static int action_argc; static const char *null_action_argv[] = {NULL, NULL}; static int total_keyfiles = 0; +static struct crypt_cli tool_ctx = { + .core_args = tool_core_args, + .core_args_count = ARRAY_SIZE(tool_core_args) +}; + +static struct tools_token_handler token_handler; + static struct tools_log_params log_parms; void tools_cleanup(void) @@ -2527,13 +2537,12 @@ static int action_luksConfig(void) return r; } -static int _token_add(struct crypt_device *cd) +static int _token_import(struct crypt_device *cd) { - int r, token; + char *json; + size_t json_length; crypt_token_info token_info; - const struct crypt_token_params_luks2_keyring params = { - .key_description = ARG_STR(OPT_KEY_DESCRIPTION_ID) - }; + int r, token; if (ARG_INT32(OPT_TOKEN_ID_ID) != CRYPT_ANY_TOKEN) { token_info = crypt_token_status(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); @@ -2546,46 +2555,164 @@ static int _token_add(struct crypt_device *cd) } } - r = crypt_token_luks2_keyring_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), ¶ms); + r = tools_read_json_file(cd, ARG_STR(OPT_JSON_FILE_ID), &json, &json_length, ARG_SET(OPT_BATCH_MODE_ID)); + if (r) + return r; + + r = crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), json); + free(json); if (r < 0) { - log_err(_("Failed to add luks2-keyring token %d."), ARG_INT32(OPT_TOKEN_ID_ID)); + log_err(_("Failed to import token from file.")); return r; } token = r; - r = crypt_token_assign_keyslot(cd, token, ARG_INT32(OPT_KEY_SLOT_ID)); + if (ARG_INT32(OPT_KEY_SLOT_ID) != CRYPT_ANY_SLOT) { + r = crypt_token_assign_keyslot(cd, token, ARG_INT32(OPT_KEY_SLOT_ID)); + if (r < 0) { + log_err(_("Failed to assign token %d to keyslot %d."), token, ARG_INT32(OPT_KEY_SLOT_ID)); + (void) crypt_token_json_set(cd, token, NULL); + return r; + } + } + + return token; +} + +static int _token_export(struct crypt_device *cd) +{ + const char *json; + int r; + + r = crypt_token_json_get(cd, ARG_INT32(OPT_TOKEN_ID_ID), &json); if (r < 0) { - log_err(_("Failed to assign token %d to keyslot %d."), token, ARG_INT32(OPT_KEY_SLOT_ID)); - (void) crypt_token_json_set(cd, token, NULL); + log_err(_("Failed to get token %d for export."), ARG_INT32(OPT_TOKEN_ID_ID)); return r; } - return token; + return tools_write_json_file(cd, ARG_STR(OPT_JSON_FILE_ID), json); } -static int _token_remove(struct crypt_device *cd) +static int token_plugin_validate_add_args(struct tools_token_handler *token_handler, void *handle, struct crypt_device *cd) { - crypt_token_info token_info; + if (!token_handler->create) { + log_err(_("Plugin %s does not support token add action."), token_handler->type); + return -ENOTSUP; + } - token_info = crypt_token_status(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); - if (token_info < CRYPT_TOKEN_INACTIVE) { - log_err(_("Token %d is invalid."), ARG_INT32(OPT_TOKEN_ID_ID)); + if (!token_handler->validate_create_params) { + log_dbg("Plugin does not export validation method for token add action."); + return 0; + } + + return token_handler->validate_create_params(cd, handle); +} + +static int token_plugin_validate_remove_args(struct tools_token_handler *token_handler, void *handle, struct crypt_device *cd) +{ + if (!token_handler->remove) { + log_err(_("Plugin %s does not support token remove action."), token_handler->type); + return -ENOTSUP; + } + + if (!token_handler->validate_remove_params) { + log_dbg("Plugin does not export validation method for token remove action."); + return 0; + } + + return token_handler->validate_remove_params(cd, handle); +} + +static int token_plugin_add(struct crypt_device *cd, struct tools_token_handler *token_handler) +{ + int r; + void *phandle = NULL; + + if (!token_handler || !token_handler->init || !token_handler->free) return -EINVAL; - } else if (token_info == CRYPT_TOKEN_INACTIVE) { - log_err(_("Token %d is not in use."), ARG_INT32(OPT_TOKEN_ID_ID)); + + r = token_handler->init(&tool_ctx, &phandle); + if (r) { + log_verbose(_("Failed to initialize plugin (%s) handle."), token_handler->type); + goto err; + } + + r = token_plugin_validate_add_args(token_handler, phandle, cd); + if (r) + goto err; + + r = token_handler->create(cd, phandle); + if (r < 0) + log_verbose(_("Plugin (%s) token add action failed."), token_handler->type); +err: + token_handler->free(phandle); + + return r; +} + +static int token_plugin_remove(struct crypt_device *cd, struct tools_token_handler *token_handler) +{ + int r; + void *phandle = NULL; + + if (!token_handler || !token_handler->init || !token_handler->free) return -EINVAL; + + r = token_handler->init(&tool_ctx, &phandle); + if (r) { + log_verbose(_("Failed to initialize plugin (%s) handle."), token_handler->type); + goto err; } - return crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); + r = token_plugin_validate_remove_args(token_handler, phandle, cd); + if (r) + goto err; + + r = token_handler->remove(cd, phandle); + if (r < 0) + log_verbose(_("Plugin (%s) token remove action failed."), token_handler->type); +err: + token_handler->free(phandle); + + return r; } -static int _token_import(struct crypt_device *cd) +static int token_plugin_validate_args(const char *token_action, struct tools_token_handler *token_handler) +{ + int r; + void *phandle; + + if (!token_handler || !token_handler->init || !token_handler->free) + return -EINVAL; + + if (token_handler->init(&tool_ctx, &phandle)) { + log_err(_("Failed to initialize plugin (%s) handle."), token_handler->type); + return -EINVAL; + } + + if (!strcmp(token_action, "add")) + r = token_plugin_validate_add_args(token_handler, phandle, NULL); + else if (!strcmp(token_action, "remove")) + r = token_plugin_validate_remove_args(token_handler, phandle, NULL); + else + r = -ENOTSUP; + + token_handler->free(phandle); + + return r; +} + +static int _token_add(struct crypt_device *cd) { - char *json; - size_t json_length; - crypt_token_info token_info; int r, token; + crypt_token_info token_info; + const struct crypt_token_params_luks2_keyring params = { + .key_description = ARG_STR(OPT_KEY_DESCRIPTION_ID) + }; + + if (token_handler.type) + return token_plugin_add(cd, &token_handler); if (ARG_INT32(OPT_TOKEN_ID_ID) != CRYPT_ANY_TOKEN) { token_info = crypt_token_status(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); @@ -2598,43 +2725,41 @@ static int _token_import(struct crypt_device *cd) } } - r = tools_read_json_file(cd, ARG_STR(OPT_JSON_FILE_ID), &json, &json_length, ARG_SET(OPT_BATCH_MODE_ID)); - if (r) - return r; - - r = crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), json); - free(json); + r = crypt_token_luks2_keyring_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), ¶ms); if (r < 0) { - log_err(_("Failed to import token from file.")); + log_err(_("Failed to add luks2-keyring token %d."), ARG_INT32(OPT_TOKEN_ID_ID)); return r; } token = r; - if (ARG_INT32(OPT_KEY_SLOT_ID) != CRYPT_ANY_SLOT) { - r = crypt_token_assign_keyslot(cd, token, ARG_INT32(OPT_KEY_SLOT_ID)); - if (r < 0) { - log_err(_("Failed to assign token %d to keyslot %d."), token, ARG_INT32(OPT_KEY_SLOT_ID)); - (void) crypt_token_json_set(cd, token, NULL); - return r; - } + r = crypt_token_assign_keyslot(cd, token, ARG_INT32(OPT_KEY_SLOT_ID)); + if (r < 0) { + log_err(_("Failed to assign token %d to keyslot %d."), token, ARG_INT32(OPT_KEY_SLOT_ID)); + (void) crypt_token_json_set(cd, token, NULL); + return r; } return token; } -static int _token_export(struct crypt_device *cd) +static int _token_remove(struct crypt_device *cd) { - const char *json; - int r; + crypt_token_info token_info; - r = crypt_token_json_get(cd, ARG_INT32(OPT_TOKEN_ID_ID), &json); - if (r < 0) { - log_err(_("Failed to get token %d for export."), ARG_INT32(OPT_TOKEN_ID_ID)); - return r; + if (token_handler.type) + return token_plugin_remove(cd, &token_handler); + + token_info = crypt_token_status(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); + if (token_info < CRYPT_TOKEN_INACTIVE) { + log_err(_("Token %d is invalid."), ARG_INT32(OPT_TOKEN_ID_ID)); + return -EINVAL; + } else if (token_info == CRYPT_TOKEN_INACTIVE) { + log_err(_("Token %d is not in use."), ARG_INT32(OPT_TOKEN_ID_ID)); + return -EINVAL; } - return tools_write_json_file(cd, ARG_STR(OPT_JSON_FILE_ID), json); + return crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); } static int action_token(void) @@ -2655,7 +2780,7 @@ static int action_token(void) r = -EINVAL; if (!strcmp(action_argv[0], "add")) { - r = _token_add(cd); /* adds only luks2-keyring type */ + r = _token_add(cd); tools_token_msg(r, CREATED); } else if (!strcmp(action_argv[0], "remove")) { r = _token_remove(cd); @@ -2710,6 +2835,7 @@ static int _get_device_active_name(struct crypt_device *cd, const char *data_dev "To run reencryption in online mode, use --active-name parameter instead.\n"), data_device); if (r < 0) return -ENOMEM; + r = noDialog(msg, _("Operation aborted.\n")) ? 0 : -EINVAL; free(msg); } @@ -3422,16 +3548,88 @@ static struct action_type { {} }; +static struct tools_arg *find_arg_in_args(const char *name, struct tools_arg *args, size_t args_len) +{ + size_t i; + + if (!args) + return NULL; + + for (i = 0; i < args_len; i++) { + if (args[i].name && !strcmp(name, args[i].name)) + return args + i; + } + + return NULL; +} + +static void plugin_cb(poptContext popt_context, + enum poptCallbackReason reason, + struct poptOption *key, + const char *arg, + void *data); + +static void basic_options_cb(poptContext popt_context, + enum poptCallbackReason reason, + struct poptOption *key, + const char *arg, + void *data); + +static void help(poptContext popt_context, + enum poptCallbackReason reason, + struct poptOption *key, + const char *arg, + void *data); + +static struct poptOption popt_basic_options[] = { + { NULL, '\0', POPT_ARG_CALLBACK, basic_options_cb, 0, NULL, NULL }, +#define ARG(A, B, C, D, E, F, G, H) { A, B, C, NULL, A ## _ID, D, E }, +#include "cryptsetup_arg_list.h" +#undef arg + POPT_TABLEEND +}; +static struct poptOption popt_help_options[] = { + { NULL, '\0', POPT_ARG_CALLBACK, help, 0, NULL, NULL }, + { "help", '?', POPT_ARG_NONE, NULL, 0, N_("Show this help message"), NULL }, + { "usage", '\0', POPT_ARG_NONE, NULL, 0, N_("Display brief usage"), NULL }, + { "version",'V', POPT_ARG_NONE, NULL, 0, N_("Print package version"), NULL }, + POPT_TABLEEND +}; +static struct poptOption popt_options[] = { + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, N_("Help options:"), NULL }, + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_basic_options, 0, NULL, NULL }, + POPT_TABLEEND, /* placeholder for plugin popt options */ + POPT_TABLEEND +}; + static void help(poptContext popt_context, enum poptCallbackReason reason __attribute__((unused)), struct poptOption *key, const char *arg __attribute__((unused)), void *data __attribute__((unused))) { + if (processing_help) + return; + if (key->shortName == '?') { struct action_type *action; const struct crypt_pbkdf_type *pbkdf_luks1, *pbkdf_luks2; + /* load plugin for plugin specific args help */ + processing_help = true; + + while (poptGetNextOpt(popt_context) != -1); + + if (token_handler.type) + (void)tools_plugin_load(token_handler.type, + &popt_options[2], + &token_handler, + tool_core_args, + ARRAY_SIZE(tool_core_args), + popt_basic_options, + plugin_cb, + true); + log_std("%s\n",PACKAGE_STRING); poptPrintHelp(popt_context, stdout, 0); @@ -3544,18 +3742,34 @@ static void basic_options_cb(poptContext popt_context, const char *arg, void *data __attribute__((unused))) { + if (popt_first_run) { + switch (key->val) { + case OPT_DEBUG_JSON_ID: + ARG_SET_TRUE(OPT_DEBUG_JSON_ID); + /* fall through */ + case OPT_DEBUG_ID: + ARG_SET_TRUE(OPT_DEBUG_ID); + log_parms.debug = true; + /* fall through */ + case OPT_VERBOSE_ID: + ARG_SET_TRUE(OPT_VERBOSE_ID); + log_parms.verbose = true; + break; +#ifdef USE_EXTERNAL_CLI_TOKENS + case OPT_PLUGIN_ID: + if (!ARG_SET(OPT_PLUGIN_ID) && !token_handler.type) { + ARG_SET_STR(OPT_PLUGIN_ID, poptGetOptArg(popt_context)); + token_handler.type = strdup(ARG_STR(OPT_PLUGIN_ID)); + } +#endif + } + return; + } + tools_parse_arg_value(popt_context, tool_core_args[key->val].type, tool_core_args + key->val, arg, key->val, needs_size_conversion); /* special cases additional handling */ switch (key->val) { - case OPT_DEBUG_JSON_ID: - /* fall through */ - case OPT_DEBUG_ID: - log_parms.debug = true; - /* fall through */ - case OPT_VERBOSE_ID: - log_parms.verbose = true; - break; case OPT_DEVICE_SIZE_ID: if (ARG_UINT64(OPT_DEVICE_SIZE_ID) == 0) usage(popt_context, EXIT_FAILURE, poptStrerror(POPT_ERROR_BADNUMBER), @@ -3604,6 +3818,18 @@ static void basic_options_cb(poptContext popt_context, poptGetInvocationName(popt_context)); data_shift = -(int64_t)ARG_UINT64(OPT_REDUCE_DEVICE_SIZE_ID); break; + case OPT_PLUGIN_ID: +#ifdef USE_EXTERNAL_CLI_TOKENS + /* technically we can load multiple tokens but there no use case yet. */ + assert(token_handler.type && token_handler.loaded); + if (token_handler.type && strcmp(token_handler.type, ARG_STR(OPT_PLUGIN_ID))) + usage(popt_context, EXIT_FAILURE, _("Loading multiple plugins at once is not supported."), + poptGetInvocationName(popt_context)); +#else + usage(popt_context, EXIT_FAILURE, _("Command line interface plugins support is disabled."), + poptGetInvocationName(popt_context)); +#endif + break; case OPT_SECTOR_SIZE_ID: if (ARG_UINT32(OPT_SECTOR_SIZE_ID) < SECTOR_SIZE || ARG_UINT32(OPT_SECTOR_SIZE_ID) > MAX_SECTOR_SIZE || @@ -3623,31 +3849,32 @@ static void basic_options_cb(poptContext popt_context, } } +static void plugin_cb(poptContext popt_context, + enum poptCallbackReason reason __attribute__((unused)), + struct poptOption *key, + const char *arg, + void *data) +{ + struct tools_arg *targ; + struct tools_token_handler *th = (struct tools_token_handler *)data; + + if (processing_help) + return; + + assert(!strncmp(key->longName, "plugin-", 7)); + + targ = find_arg_in_args(key->longName + strlen(th->type) + 8, th->args_plugin, th->total_args_count); + if (targ) + tools_parse_arg_value(popt_context, targ->type, targ, arg, 0, NULL); +} + int main(int argc, const char **argv) { - static struct poptOption popt_help_options[] = { - { NULL, '\0', POPT_ARG_CALLBACK, help, 0, NULL, NULL }, - { "help", '?', POPT_ARG_NONE, NULL, 0, N_("Show this help message"), NULL }, - { "usage", '\0', POPT_ARG_NONE, NULL, 0, N_("Display brief usage"), NULL }, - { "version",'V', POPT_ARG_NONE, NULL, 0, N_("Print package version"), NULL }, - POPT_TABLEEND - }; - static struct poptOption popt_basic_options[] = { - { NULL, '\0', POPT_ARG_CALLBACK, basic_options_cb, 0, NULL, NULL }, -#define ARG(A, B, C, D, E, F, G, H) { A, B, C, NULL, A ## _ID, D, E }, -#include "cryptsetup_arg_list.h" -#undef arg - POPT_TABLEEND - }; - static struct poptOption popt_options[] = { - { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, N_("Help options:"), NULL }, - { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_basic_options, 0, NULL, NULL }, - POPT_TABLEEND - }; poptContext popt_context; struct action_type *action; const char *aname; int r; + bool plugin; crypt_set_log_callback(NULL, tool_log, &log_parms); @@ -3659,7 +3886,45 @@ int main(int argc, const char **argv) poptSetOtherOptionHelp(popt_context, _("[OPTION...] ")); - while ((r = poptGetNextOpt(popt_context)) > 0) {} + /* ignore errors for now, we're setting debug levels and looking for plugins */ + while (poptGetNextOpt(popt_context) != -1); + + popt_first_run = false; + + if (ARG_SET(OPT_DEBUG_ID) || ARG_SET(OPT_DEBUG_JSON_ID)) + crypt_set_debug_level(ARG_SET(OPT_DEBUG_JSON_ID)? CRYPT_DEBUG_JSON : CRYPT_DEBUG_ALL); + + plugin = ARG_SET(OPT_PLUGIN_ID); + aname = poptGetArg(popt_context); + + tools_cleanup(); + poptResetContext(popt_context); + + if (plugin && aname && !strcmp(aname, "token") && token_handler.type) { + if (tools_plugin_load(token_handler.type, + &popt_options[2], + &token_handler, + tool_core_args, + ARRAY_SIZE(tool_core_args), + popt_basic_options, + plugin_cb, false)) + usage(popt_context, EXIT_FAILURE, + _("Failed to load token plugin."), + poptGetInvocationName(popt_context)); + + tools_plugin_assign_args_to_action(&token_handler, tool_core_args, + ARRAY_SIZE(tool_core_args), TOKEN_ACTION); + + tool_ctx.plugin_args.name = token_handler.type; + tool_ctx.plugin_args.args = token_handler.args_plugin; + tool_ctx.plugin_args.count = token_handler.total_args_count; + } + + while ((r = poptGetNextOpt(popt_context)) > 0); + + if (r < -1) + usage(popt_context, EXIT_FAILURE, poptStrerror(r), + poptBadOption(popt_context, POPT_BADOPTION_NOALIAS)); if (r < -1) usage(popt_context, EXIT_FAILURE, poptStrerror(r), @@ -3891,10 +4156,8 @@ int main(int argc, const char **argv) _("Keyslot specification is required."), poptGetInvocationName(popt_context)); - if (ARG_SET(OPT_DEBUG_ID) || ARG_SET(OPT_DEBUG_JSON_ID)) { - crypt_set_debug_level(ARG_SET(OPT_DEBUG_JSON_ID)? CRYPT_DEBUG_JSON : CRYPT_DEBUG_ALL); + if (ARG_SET(OPT_DEBUG_ID) || ARG_SET(OPT_DEBUG_JSON_ID)) dbg_version_and_cmd(argc, argv); - } /* reencrypt action specific check */ if (ARG_SET(OPT_DECRYPT_ID) && !ARG_SET(OPT_HEADER_ID)) @@ -3913,13 +4176,6 @@ int main(int argc, const char **argv) usage(popt_context, EXIT_FAILURE, _("Options --keyslot-cipher and --keyslot-key-size must be used together."), poptGetInvocationName(popt_context)); - if (ARG_SET(OPT_TEST_ARGS_ID)) { - log_std(_("No action taken. Invoked with --test-args option.\n")); - tools_cleanup(); - poptFreeContext(popt_context); - return 0; - } - /* token action specific check */ if (!strcmp(aname, TOKEN_ACTION)) { if (strcmp(action_argv[0], "add") && @@ -3929,14 +4185,29 @@ int main(int argc, const char **argv) usage(popt_context, EXIT_FAILURE, _("Unknown token action."), poptGetInvocationName(popt_context)); - if (!ARG_SET(OPT_KEY_DESCRIPTION_ID) && !strcmp(action_argv[0], "add")) + if (!ARG_SET(OPT_PLUGIN_ID) && !ARG_SET(OPT_KEY_DESCRIPTION_ID) && !strcmp(action_argv[0], "add")) usage(popt_context, EXIT_FAILURE, _("Option --key-description is required."), poptGetInvocationName(popt_context)); - if (ARG_INT32(OPT_TOKEN_ID_ID) == CRYPT_ANY_TOKEN && - (!strcmp(action_argv[0], "remove") || !strcmp(action_argv[0], "export"))) + if (!ARG_SET(OPT_PLUGIN_ID) && ARG_INT32(OPT_TOKEN_ID_ID) == CRYPT_ANY_TOKEN && !strcmp(action_argv[0], "remove")) + usage(popt_context, EXIT_FAILURE, _("Option --token-id is required."), + poptGetInvocationName(popt_context)); + + if (ARG_INT32(OPT_TOKEN_ID_ID) == CRYPT_ANY_TOKEN && !strcmp(action_argv[0], "export")) usage(popt_context, EXIT_FAILURE, _("Option --token-id is required."), poptGetInvocationName(popt_context)); + + /* token action specific check (plugin) */ + if (ARG_SET(OPT_PLUGIN_ID) && token_plugin_validate_args(action_argv[0], &token_handler)) + usage(popt_context, EXIT_FAILURE, _("Invalid plugin token arguments."), + poptGetInvocationName(popt_context)); + } + + if (ARG_SET(OPT_TEST_ARGS_ID)) { + log_std(_("No action taken. Invoked with --test-args option.\n")); + tools_cleanup(); + poptFreeContext(popt_context); + return 0; } if (ARG_SET(OPT_DISABLE_KEYRING_ID)) @@ -3953,3 +4224,8 @@ int main(int argc, const char **argv) poptFreeContext(popt_context); return r; } + +static void __attribute__((destructor)) cryptsetup_exit(void) +{ + tools_plugin_unload(&token_handler); +} diff --git a/src/cryptsetup.h b/src/cryptsetup.h index 65f6d0dc..4e96e225 100644 --- a/src/cryptsetup.h +++ b/src/cryptsetup.h @@ -50,6 +50,7 @@ #include "libcryptsetup.h" #include "libcryptsetup_cli.h" #include "lib/cli/cli_internal.h" +#include "plugin.h" #define CONST_CAST(x) (x)(uintptr_t) #define DEFAULT_CIPHER(type) (DEFAULT_##type##_CIPHER "-" DEFAULT_##type##_MODE) @@ -140,4 +141,53 @@ struct tools_log_params { bool debug; }; +/* external token plugins */ + +struct tools_token_handler { + crypt_token_handle_init_func init; + crypt_token_handle_free_func free; + crypt_token_version_func version; + + crypt_token_create_params_func create_params; + crypt_token_validate_create_params_func validate_create_params; + crypt_token_create_func create; + + crypt_token_remove_params_func remove_params; + crypt_token_validate_remove_params_func validate_remove_params; + crypt_token_remove_func remove; + + size_t total_args_count; + + const char *type; + char *create_desc; + char *remove_desc; + + bool loaded; + void *dlhandle; + struct poptOption popt_table_plugin[3]; + struct poptOption popt_create_args[3]; + struct poptOption popt_remove_args[3]; + struct poptOption *popt_create_ref_args; + struct poptOption *popt_create_plg_args; + struct poptOption *popt_remove_ref_args; + struct poptOption *popt_remove_plg_args; + struct tools_arg *args_plugin; +}; + +int tools_plugin_load(const char *type, + struct poptOption *plugin_options, + struct tools_token_handler *token_handler, + struct tools_arg *core_args, + size_t core_args_len, + struct poptOption *popt_core_options, + void *plugin_cb, + bool quiet); + +void tools_plugin_unload(struct tools_token_handler *th); + +void tools_plugin_assign_args_to_action(struct tools_token_handler *thandle, + struct tools_arg *args, + size_t args_len, + const char *action); + #endif /* CRYPTSETUP_H */ diff --git a/src/cryptsetup_arg_list.h b/src/cryptsetup_arg_list.h index b61a811b..cf37ce84 100644 --- a/src/cryptsetup_arg_list.h +++ b/src/cryptsetup_arg_list.h @@ -125,6 +125,8 @@ ARG(OPT_PERF_SUBMIT_FROM_CRYPT_CPUS, '\0', POPT_ARG_NONE, N_("Use dm-crypt submi ARG(OPT_PERSISTENT, '\0', POPT_ARG_NONE, N_("Set activation flags persistent for device"), NULL, CRYPT_ARG_BOOL, {}, OPT_PERSISTENT_ACTIONS) +ARG(OPT_PLUGIN, '\0', POPT_ARG_STRING, N_("Load token plugin"), NULL, CRYPT_ARG_STRING, {}, OPT_PLUGIN_ACTIONS) + ARG(OPT_PRIORITY, '\0', POPT_ARG_STRING, N_("Keyslot priority: ignore, normal, prefer"), NULL, CRYPT_ARG_STRING, {}, OPT_PRIORITY_ACTIONS) ARG(OPT_PROGRESS_FREQUENCY, '\0', POPT_ARG_STRING, N_("Progress line update (in seconds)"), N_("secs"), CRYPT_ARG_UINT32, {}, {}) diff --git a/src/cryptsetup_args.h b/src/cryptsetup_args.h index 5056bc0e..a51986f3 100644 --- a/src/cryptsetup_args.h +++ b/src/cryptsetup_args.h @@ -80,6 +80,7 @@ #define OPT_USE_RANDOM_ACTIONS { FORMAT_ACTION } #define OPT_USE_URANDOM_ACTIONS { FORMAT_ACTION } #define OPT_UUID_ACTIONS { FORMAT_ACTION, UUID_ACTION } +#define OPT_PLUGIN_ACTIONS { TOKEN_ACTION } enum { OPT_UNUSED_ID = 0, /* leave unused due to popt library */ diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 00000000..94dd0cb6 --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,52 @@ +/* + * cryptsetup command line plugin API + * + * Copyright (C) 2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2020 Ondrej Kozina + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PLUGIN_H +#define PLUGIN_H + +#include + +#include "libcryptsetup_cli.h" + +/* plugin command line argument prefix */ +#define CRYPT_PLUGIN "plugin" + +typedef struct crypt_arg_list { + const char *name; + const char *desc; /* optional */ + crypt_arg_type_info arg_type; + const struct crypt_arg_list *next; +} crypt_arg_list; + +typedef int (*crypt_token_handle_init_func) (struct crypt_cli *ctx, void **handle); +typedef void (*crypt_token_handle_free_func) (void *handle); +typedef const char* (*crypt_token_version_func)(void); + +typedef int (*crypt_token_create_func) (struct crypt_device *cd, void *token_handle); +typedef int (*crypt_token_validate_create_params_func) (struct crypt_device *cd, void *token_handle); + +typedef int (*crypt_token_remove_func) (struct crypt_device *cd, void *token_handle); +typedef int (*crypt_token_validate_remove_params_func) (struct crypt_device *cd, void *token_handle); + +typedef const crypt_arg_list* (*crypt_token_create_params_func) (void); +typedef const crypt_arg_list* (*crypt_token_remove_params_func) (void); + +#endif diff --git a/src/utils_plugins.c b/src/utils_plugins.c new file mode 100644 index 00000000..95a3e52d --- /dev/null +++ b/src/utils_plugins.c @@ -0,0 +1,535 @@ +/* + * cli tokens plugins helpers + * + * Copyright (C) 2020 Red Hat, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef USE_EXTERNAL_CLI_TOKENS +#include +#include +#endif + +#include "cryptsetup.h" + +#ifdef USE_EXTERNAL_CLI_TOKENS +static const struct poptOption popt_table_end = POPT_TABLEEND; + +static int tools_plugin_load_symbol(const char *symbol, void *handle, bool quiet, void **ret_symbol) +{ + char *error; + void *sym = dlsym(handle, symbol); + + error = dlerror(); + if (error) { + if (!quiet) + log_err(_("Failed to load mandatory plugin symbol %s (%s)."), symbol, error); + return -EINVAL; + } + + *ret_symbol = sym; + + return 0; +} + +static void tools_plugin_load_optional_symbol(const char *symbol, void *handle, void **ret_symbol) +{ + char *error; + void *sym = dlsym(handle, symbol); + + error = dlerror(); + if (error) { + log_dbg("Failed to load optional plugin symbol %s (%s).", symbol, error); + return; + } + + *ret_symbol = sym; +} + +static int dlopen_plugin(const char *type, struct tools_token_handler *th, bool quiet) +{ + char plugin[64]; + void *h; + int r = snprintf(plugin, sizeof(plugin), "libcryptsetup-token-%s.so", type); + + log_dbg("Loading plugin %s.", plugin); + + if (r < 0 || (size_t)r >= sizeof(plugin)) + return -EINVAL; + + r = -EINVAL; + h = dlopen(plugin, RTLD_LAZY); + if (!h) { + if (!quiet) + log_err("Failed to load cryptsetup plugin: %s.", dlerror()); + return r; + } + dlerror(); + + r = tools_plugin_load_symbol("crypt_token_handle_init", h, quiet, (void **)&th->init); + if (r) + goto out; + + r = tools_plugin_load_symbol("crypt_token_handle_free", h, quiet, (void **)&th->free); + if (r) + goto out; + + r = tools_plugin_load_symbol("crypt_token_version", h, quiet, (void **)&th->version); + if (r) + goto out; + + tools_plugin_load_optional_symbol("crypt_token_create", h, (void **)&th->create); + if (th->create) { + r = tools_plugin_load_symbol("crypt_token_create_params", h, quiet, (void **)&th->create_params); + if (r) + goto out; + } + + tools_plugin_load_optional_symbol("crypt_token_remove", h, (void **)&th->remove); + if (th->remove) { + r = tools_plugin_load_symbol("crypt_token_remove_params", h, quiet, (void **)&th->remove_params); + if (r) + goto out; + } + + tools_plugin_load_optional_symbol("crypt_token_validate_create_params", h, (void **)&th->validate_create_params); + + tools_plugin_load_optional_symbol("crypt_token_validate_remove_params", h, (void **)&th->validate_remove_params); + + th->loaded = true; + th->dlhandle = h; + r = 0; +out: + if (r) + dlclose(h); + return r; +} + +static int tools_find_arg_id_in_args(const char *name, struct tools_arg *args, size_t args_len) +{ + int i; + + if (!args) + return -EINVAL; + + for (i = 0; i < (int)args_len; i++) + if (args[i].name && !strcmp(name, args[i].name)) + return i; + + return -ENOENT; +} + +static int add_plugin_arg(struct tools_token_handler *thandle, const char *name, crypt_arg_type_info arg_type) +{ + int r; + + r = tools_find_arg_id_in_args(name, thandle->args_plugin, thandle->total_args_count); + if (r == -ENOENT) { + r = thandle->total_args_count++; + thandle->args_plugin[r].name = name; + thandle->args_plugin[r].type = arg_type; + } + + return r; +} + +static int plugin_add_options(struct tools_token_handler *thandle, + const crypt_arg_list* (*params)(void), + size_t private_args_count, + size_t total_args_count, + struct poptOption r_top_opts[3], + struct poptOption **r_private_opts, + struct poptOption **r_ref_opts, + struct tools_arg *core_args, + size_t core_args_len, + struct poptOption *popt_basic_options, + void *plugin_cb) +{ + int i, r = -ENOMEM; + const crypt_arg_list *item; + size_t ref_len = 0, last = 0, plg_cnt = 0, ref_cnt = 0; + struct poptOption *plugin_opts = NULL, *ref_opts = NULL; + + if (!total_args_count || !params) + return 0; + + item = params(); + + if (total_args_count > private_args_count) + ref_len = total_args_count - private_args_count; + + if (private_args_count) { + if (private_args_count < SIZE_MAX / sizeof(*plugin_opts) - 2) + plugin_opts = malloc((private_args_count + 2) * sizeof(*plugin_opts)); + if (!plugin_opts) + goto out; + + plugin_opts[0].shortName = '\0'; + plugin_opts[0].argInfo = POPT_ARG_CALLBACK; + plugin_opts[0].arg = plugin_cb; + plugin_opts[0].descrip = (const char *)thandle; + + r_top_opts[last].argInfo = POPT_ARG_INCLUDE_TABLE; + r_top_opts[last++].arg = plugin_opts; + } + + if (ref_len) { + if (ref_len < SIZE_MAX / sizeof(*ref_opts) - 1) + ref_opts = malloc((ref_len + 1) * sizeof(*ref_opts)); + if (!ref_opts) + goto out; + + r_top_opts[last].argInfo = POPT_ARG_INCLUDE_TABLE; + r_top_opts[last++].arg = ref_opts; + } + + r_top_opts[last] = popt_table_end; + + while (item) { + if (strncmp(item->name, "plugin-", 7)) { + if (ref_cnt >= ref_len) { + r = -EINVAL; + log_dbg("Plugin core arguments references being inconsistent."); + goto out; + } + i = tools_find_arg_id_in_args(item->name, core_args, core_args_len); + if (i > 0) { + ref_opts[ref_cnt] = popt_basic_options[i]; + /* replace with plugin specific description if provided */ + if (item->desc) + ref_opts[ref_cnt].descrip = item->desc; + ref_cnt++; + } + item = item->next; + continue; + } + + if (plg_cnt >= private_args_count) { + r = -EINVAL; + log_dbg("Plugin arguments reporting being inconsistent."); + goto out; + } + + r = add_plugin_arg(thandle, item->name + strlen(thandle->type) + 8, item->arg_type); + if (r < 0) + goto out; + + plugin_opts[plg_cnt+1].longName = item->name; + switch (item->arg_type) { + case CRYPT_ARG_BOOL: + plugin_opts[plg_cnt+1].argInfo = POPT_ARG_NONE; + break; + case CRYPT_ARG_STRING: + case CRYPT_ARG_INT32: + case CRYPT_ARG_UINT32: + case CRYPT_ARG_INT64: + case CRYPT_ARG_UINT64: + plugin_opts[plg_cnt+1].argInfo = POPT_ARG_STRING; + break; + } + plugin_opts[plg_cnt+1].descrip = item->desc; + plg_cnt++; + item = item->next; + } + + if (plugin_opts) + plugin_opts[plg_cnt+1] = popt_table_end; + if (ref_opts) + ref_opts[ref_cnt] = popt_table_end; + + *r_private_opts = plugin_opts; + *r_ref_opts = ref_opts; + + return 0; +out: + free(plugin_opts); + free(ref_opts); + + return r; +} + +static int validate_plugin_args(const char *type, + const crypt_arg_list *(*params)(void), + struct tools_arg *core_args, + size_t core_args_len, + size_t *r_private_args_count, + size_t *r_total_args_count) +{ + size_t n, private_args = 0, args = 0; + const crypt_arg_list *item = params ? params() : NULL; + + while (item) { + if (!item->name || item->arg_type > CRYPT_ARG_UINT64) + return -EINVAL; + log_dbg("Validating plugin parameter %s (type %d).", item->name, item->arg_type); + if (!strncmp(item->name, "plugin-", 7)) { + /* plugin specific argument must be prefixed with "plugin--" */ + n = strlen(type); + if (strncmp(item->name + 7, type, n)) { + log_dbg("Invalid argument %s name (expected: plugin-%s-).", item->name, type); + return -EINVAL; + } + if ((*(item->name + n + 7) != '-') || !*(item->name + n + 8)) { + log_dbg("Invalid argument %s name (expected: plugin-%s-).", item->name, type); + return -EINVAL; + } + private_args++; + } else { + if (tools_find_arg_id_in_args(item->name, core_args, core_args_len) < 0) { + log_dbg("Plugin requests access to undefined core argument %s.", item->name); + return -EINVAL; + } + } + + args++; + item = item->next; + } + + *r_private_args_count = private_args; + *r_total_args_count = args; + + return 0; +} + +static int plugin_allocate_internal(struct tools_token_handler *thandle, + size_t create_private_args_count, + size_t remove_private_args_count) +{ + struct tools_arg *args = NULL; + size_t len = create_private_args_count + remove_private_args_count; + + /* size_t overflow check */ + if (len < create_private_args_count || + len < remove_private_args_count) + return -ENOMEM; + + if (!len) + return 0; + + if (len < SIZE_MAX / sizeof(*args)) + args = malloc(len * sizeof(*args)); + if (!args) + return -ENOMEM; + + thandle->args_plugin = args; + + return 0; +} + +int tools_plugin_load(const char *type, + struct poptOption *plugin_options, + struct tools_token_handler *token_handler, + struct tools_arg *core_args, + size_t core_args_len, + struct poptOption *popt_core_options, + void *plugin_cb, + bool quiet) +{ + int r; + size_t last_id = 0, create_private_args_count = 0, create_args_count = 0, + remove_private_args_count = 0, remove_args_count = 0; + + r = dlopen_plugin(type, token_handler, quiet); + if (r) + return r; + + r = validate_plugin_args(token_handler->type, token_handler->create_params, + core_args, core_args_len, &create_private_args_count, &create_args_count); + if (r) { + if (!quiet) + log_err(_("Plugin %s create parameters are invalid."), type); + goto out; + } + + r = validate_plugin_args(token_handler->type, token_handler->remove_params, + core_args, core_args_len, &remove_private_args_count, &remove_args_count); + if (r) { + if (!quiet) + log_err(_("Plugin %s remove parameters are invalid."), type); + goto out; + } + + r = plugin_allocate_internal(token_handler, create_private_args_count, remove_private_args_count); + if (r) + goto out; + + r = plugin_add_options(token_handler, + token_handler->create_params, + create_private_args_count, + create_args_count, + token_handler->popt_create_args, + &token_handler->popt_create_plg_args, + &token_handler->popt_create_ref_args, + core_args, + core_args_len, + popt_core_options, + plugin_cb); + if (r) + goto out; + + r = plugin_add_options(token_handler, + token_handler->remove_params, + remove_private_args_count, + remove_args_count, + token_handler->popt_remove_args, + &token_handler->popt_remove_plg_args, + &token_handler->popt_remove_ref_args, + core_args, + core_args_len, + popt_core_options, + plugin_cb); + if (r) + goto out; + + if (create_args_count) { + r = asprintf(&token_handler->create_desc, N_("Plugin %s token add action arguments:"), type); + if (r < 0) + goto out; + token_handler->popt_table_plugin[last_id].longName = NULL; + token_handler->popt_table_plugin[last_id].shortName = '\0'; + token_handler->popt_table_plugin[last_id].argInfo = POPT_ARG_INCLUDE_TABLE; + token_handler->popt_table_plugin[last_id].arg = token_handler->popt_create_args; + token_handler->popt_table_plugin[last_id].descrip = token_handler->create_desc; + token_handler->popt_table_plugin[last_id].val = 0; + token_handler->popt_table_plugin[last_id++].argDescrip = NULL; + } + + if (remove_args_count) { + r = asprintf(&token_handler->remove_desc, N_("Plugin %s token remove action arguments:"), type); + if (r < 0) + goto out; + token_handler->popt_table_plugin[last_id].longName = NULL; + token_handler->popt_table_plugin[last_id].shortName = '\0'; + token_handler->popt_table_plugin[last_id].argInfo = POPT_ARG_INCLUDE_TABLE; + token_handler->popt_table_plugin[last_id].arg = token_handler->popt_remove_args; + token_handler->popt_table_plugin[last_id].descrip = token_handler->remove_desc; + token_handler->popt_table_plugin[last_id].val = 0; + token_handler->popt_table_plugin[last_id++].argDescrip = NULL; + } + + token_handler->popt_table_plugin[last_id] = popt_table_end; + + plugin_options->longName = NULL; + plugin_options->shortName = '\0'; + plugin_options->argInfo = POPT_ARG_INCLUDE_TABLE; + plugin_options->arg = &token_handler->popt_table_plugin; + plugin_options->val = 0; + plugin_options->argDescrip = NULL; + + r = 0; +out: + if (r) + tools_plugin_unload(token_handler); + + return r; +} + +void tools_plugin_unload(struct tools_token_handler *th) +{ + if (!th || !th->type) + return; + + if (th->loaded) + dlclose(th->dlhandle); + + free(CONST_CAST(void *)th->type); + free(CONST_CAST(void *)th->create_desc); + free(CONST_CAST(void *)th->remove_desc); + + tools_args_free(th->args_plugin, th->total_args_count); + + free(th->args_plugin); + free(th->popt_create_ref_args); + free(th->popt_create_plg_args); + free(th->popt_remove_ref_args); + free(th->popt_remove_plg_args); + + memset(th, 0, sizeof(*th)); +} + +static bool args_add_action(const char *action, struct tools_arg *args, size_t args_size, unsigned arg_id) +{ + unsigned i; + + for (i = 0; i < MAX_ACTIONS && args[arg_id].actions_array[i]; i++) { + if (!strcmp(args[arg_id].actions_array[i], action)) + return true; + } + + if (i >= MAX_ACTIONS) + return false; + + /* do not restrict otherwise global arguments */ + if (i) + args[arg_id].actions_array[i] = action; + + return true; +} + +static void assign_args_to_action(const crypt_arg_list *(*params)(void), + struct tools_arg *args, + size_t args_len, + const char *action) +{ + int p; + const crypt_arg_list *item; + + if (!params) + return; + + item = params(); + while (item) { + if (strncmp(item->name, "plugin-", 7)) { + p = tools_find_arg_id_in_args(item->name, args, args_len); + assert(p > 0); + assert(args_add_action(action, args, args_len, p)); + } + item = item->next; + } +} + +void tools_plugin_assign_args_to_action(struct tools_token_handler *thandle, + struct tools_arg *args, + size_t args_len, + const char *action) +{ + assign_args_to_action(thandle->create_params, args, args_len, action); + assign_args_to_action(thandle->remove_params, args, args_len, action); +} +#else +int tools_plugin_load(const char *type, + struct poptOption *plugin_options, + struct tools_token_handler *token_handler, + struct tools_arg *core_args, + size_t core_args_len, + struct poptOption *popt_core_options, + void *plugin_cb, + bool quiet) +{ + return -ENOTSUP; +} + +void tools_plugin_unload(struct tools_token_handler *th) +{ +} + +void tools_plugin_assign_args_to_action(struct tools_token_handler *thandle, + struct tools_arg *args, + size_t args_len, + const char *action) +{ +} +#endif //USE_EXTERNAL_CLI_TOKENS diff --git a/tokens/libcryptsetup-token.sym b/tokens/libcryptsetup-token.sym index c4456ddd..9e12baae 100644 --- a/tokens/libcryptsetup-token.sym +++ b/tokens/libcryptsetup-token.sym @@ -2,3 +2,16 @@ CRYPTSETUP_TOKEN_1.0 { global: cryptsetup_token_handler; local: *; }; + +CRYPTSETUP_CLI_TOKEN_1.0 { + global: crypt_token_handle_init; + crypt_token_handle_free; + crypt_token_version; + crypt_token_create; + crypt_token_create_params; + crypt_token_validate_create_params; + crypt_token_remove; + crypt_token_remove_params; + crypt_token_validate_remove_params; + local: *; +}; From bd51d08267701188e20375d20f79129bbc09c19d Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Mon, 24 Jun 2019 09:26:33 +0200 Subject: [PATCH 05/14] Add SSH token. --- configure.ac | 9 + misc/luks2_keyslot_example/keyslot_test.c | 12 +- tokens/Makemodule.am | 7 + tokens/ssh/libcryptsetup-token-ssh.c | 520 ++++++++++++++++++++++ 4 files changed, 542 insertions(+), 6 deletions(-) create mode 100644 tokens/ssh/libcryptsetup-token-ssh.c diff --git a/configure.ac b/configure.ac index 0e76e7da..49ffcbb5 100644 --- a/configure.ac +++ b/configure.ac @@ -361,6 +361,10 @@ AC_ARG_ENABLE([udev], AS_HELP_STRING([--disable-udev], [disable udev support]), [], [enable_udev=yes]) +AC_ARG_ENABLE([ssh-token], + AS_HELP_STRING([--enable-ssh-token], [enable build of ssh-token])) +AM_CONDITIONAL(SSHPLUGIN_TOKEN, test "x$enable_ssh_token" = "xyes") + dnl Try to use pkg-config for devmapper, but fallback to old detection PKG_CHECK_MODULES([DEVMAPPER], [devmapper >= 1.02.03],, [ AC_CHECK_LIB(devmapper, dm_task_set_name,, @@ -394,6 +398,11 @@ PKG_CHECK_MODULES([JSON_C], [json-c]) AC_CHECK_DECLS([json_object_object_add_ex], [], [], [#include ]) AC_CHECK_DECLS([json_object_deep_copy], [], [], [#include ]) +dnl Check for libssh for SSH plugin +if test "x$enable_ssh_token" = "xyes"; then + PKG_CHECK_MODULES([LIBSSH], [libssh]) +fi + dnl Crypto backend configuration. AC_ARG_WITH([crypto_backend], AS_HELP_STRING([--with-crypto_backend=BACKEND], [crypto backend (gcrypt/openssl/nss/kernel/nettle) [openssl]]), diff --git a/misc/luks2_keyslot_example/keyslot_test.c b/misc/luks2_keyslot_example/keyslot_test.c index a19cd294..e3e556cd 100644 --- a/misc/luks2_keyslot_example/keyslot_test.c +++ b/misc/luks2_keyslot_example/keyslot_test.c @@ -242,7 +242,7 @@ static int _password_auth(struct crypt_device *cd, ssh_session ssh, password_cb_ return r; } -static int SSHTEST_token_open(struct crypt_device *cd, +static int SSHPLUGIN_token_open(struct crypt_device *cd, int token, char **password, size_t *password_len, @@ -280,9 +280,9 @@ static int SSHTEST_token_open(struct crypt_device *cd, return r ? -EINVAL : r; } -const crypt_token_handler SSHTEST_token = { +const crypt_token_handler SSHPLUGIN_token = { .name = "sshkeytest", - .open = SSHTEST_token_open, + .open = SSHPLUGIN_token_open, }; static int token_add(const char *device, const char *server, @@ -292,7 +292,7 @@ static int token_add(const char *device, const char *server, json_object *jobj = NULL, *jobj_keyslots; int r; - r = crypt_token_register(&SSHTEST_token); + r = crypt_token_register(&SSHPLUGIN_token); if (r < 0) return EXIT_FAILURE; @@ -307,7 +307,7 @@ static int token_add(const char *device, const char *server, } jobj = json_object_new_object(); - json_object_object_add(jobj, "type", json_object_new_string(SSHTEST_token.name)); /* mandatory */ + json_object_object_add(jobj, "type", json_object_new_string(SSHPLUGIN_token.name)); /* mandatory */ jobj_keyslots = json_object_new_array(); json_object_array_add(jobj_keyslots, json_object_new_string("0")); /* assign to first keyslot only */ @@ -361,7 +361,7 @@ static int open_by_token(const char *device, const char *name) struct crypt_device *cd = NULL; int r; - r = crypt_token_register(&SSHTEST_token); + r = crypt_token_register(&SSHPLUGIN_token); if (r < 0) return EXIT_FAILURE; diff --git a/tokens/Makemodule.am b/tokens/Makemodule.am index 61645b23..a7e1e0b4 100644 --- a/tokens/Makemodule.am +++ b/tokens/Makemodule.am @@ -3,3 +3,10 @@ EXTRA_DIST += tokens/libcryptsetup-token.sym TOKENS_LDFLAGS = $(AM_LDFLAGS) -no-undefined \ -Wl,--version-script=$(top_srcdir)/tokens/libcryptsetup-token.sym \ -version-info 0:0:0 + +if SSHPLUGIN_TOKEN +libcryptsetup_token_ssh_la_LDFLAGS = $(TOKENS_LDFLAGS) +libcryptsetup_token_ssh_la_SOURCES = tokens/ssh/libcryptsetup-token-ssh.c +libcryptsetup_token_ssh_la_LIBADD = -lssh libcryptsetup.la @JSON_C_LIBS@ +lib_LTLIBRARIES += libcryptsetup-token-ssh.la +endif diff --git a/tokens/ssh/libcryptsetup-token-ssh.c b/tokens/ssh/libcryptsetup-token-ssh.c new file mode 100644 index 00000000..1b00f9af --- /dev/null +++ b/tokens/ssh/libcryptsetup-token-ssh.c @@ -0,0 +1,520 @@ +/* + * Example of LUKS2 keyslot handler, token lib + * + * Copyright (C) 2016-2020 Milan Broz + * Copyright (C) 2020 Vojtech Trefny + * + * Use: + * - generate LUKS device + * - store passphrase used in previous step remotely (single line w/o \r\n) + * - add new token using this example + * - activate device by token + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "libcryptsetup.h" +#include "../../src/plugin.h" + +#define PASSWORD_LENGTH 8192 + +#define TOKEN_NAME "ssh" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +#define SERVER_ARG "plugin-ssh-server" +#define USER_ARG "plugin-ssh-user" +#define PATH_ARG "plugin-ssh-path" +#define KEYPATH_ARG "plugin-ssh-keypath" + +#define CREATE_VALID (1 << 0) +#define CREATED (1 << 1) + +#define l_err(cd, x...) crypt_logf(cd, CRYPT_LOG_ERROR, x) +#define l_dbg(cd, x...) crypt_logf(cd, CRYPT_LOG_DEBUG, x) + +typedef int (*password_cb_func) (char **password); + +struct sshplugin_context { + const char *server; + const char *user; + const char *path; + const char *sshkey_path; + + int token; + int keyslot; + + uint8_t status; + + struct crypt_cli *cli; +}; + +int crypt_token_handle_init(struct crypt_cli *cli, void **handle) +{ + struct sshplugin_context *sc; + + if (!handle) + return -EINVAL; + + sc = calloc(1, sizeof(*sc)); + if (!sc) + return -ENOMEM; + + sc->cli = cli; + + *handle = sc; + + return 0; +} + +void crypt_token_handle_free(void *handle) +{ + free(handle); +} + +const char *crypt_token_version(void) +{ + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR; +} + +const static crypt_arg_list args[] = { + /* plugin specific args */ + { SERVER_ARG, "SSH server URL", CRYPT_ARG_STRING, &args[1] }, + { USER_ARG, "SSH username", CRYPT_ARG_STRING, &args[2] }, + { PATH_ARG, "Path to the keyfile on SSH server", CRYPT_ARG_STRING, &args[3] }, + { KEYPATH_ARG, "Path to the SSH key to use", CRYPT_ARG_STRING, &args[4] }, + /* inherited from cryptsetup core args */ + { "token-id", NULL, CRYPT_ARG_INT32, &args[5] }, + { "key-slot", NULL, CRYPT_ARG_INT32, NULL }, +}; + +const crypt_arg_list *crypt_token_create_params(void) +{ + return args; +} + +static json_object *get_token_jobj(struct crypt_device *cd, int token) +{ + const char *json_slot; + + /* libcryptsetup API call */ + if (crypt_token_json_get(cd, token, &json_slot)) + return NULL; + + return json_tokener_parse(json_slot); +} + +static int sshplugin_download_password(struct crypt_device *cd, ssh_session ssh, + const char *path, char **password, size_t *password_len) +{ + char *pass = NULL; + size_t pass_len; + int r; + sftp_attributes sftp_attr = NULL; + sftp_session sftp = NULL; + sftp_file file = NULL; + + + sftp = sftp_new(ssh); + if (!sftp) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_init(sftp); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: "); + goto out; + } + + file = sftp_open(sftp, path, O_RDONLY, 0); + if (!file) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + sftp_attr = sftp_fstat(file); + if (!sftp_attr) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot stat sftp file: "); + r = SSH_FX_FAILURE; + goto out; + } + + pass_len = sftp_attr->size > PASSWORD_LENGTH ? PASSWORD_LENGTH : sftp_attr->size; + pass = malloc(pass_len); + if (!pass) { + crypt_log(cd, CRYPT_LOG_ERROR, "Not enough memory.\n"); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_read(file, pass, pass_len); + if (r < 0 || (size_t)r != pass_len) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote key: "); + r = SSH_FX_FAILURE; + goto out; + } + + *password = pass; + *password_len = pass_len; + + r = SSH_OK; +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + free(pass); + } + + if (sftp_attr) + sftp_attributes_free(sftp_attr); + + if (file) + sftp_close(file); + if (sftp) + sftp_free(sftp); + return r == SSH_OK ? 0 : -EINVAL; +} + +static ssh_session sshplugin_session_init(struct crypt_device *cd, + const char *host, const char *user) +{ + int r, port = 22; + ssh_session ssh = ssh_new(); + if (!ssh) + return NULL; + + ssh_options_set(ssh, SSH_OPTIONS_HOST, host); + ssh_options_set(ssh, SSH_OPTIONS_USER, user); + ssh_options_set(ssh, SSH_OPTIONS_PORT, &port); + + crypt_log(cd, CRYPT_LOG_NORMAL, "SSH token initiating ssh session.\n"); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_session_is_known_server(ssh); + if (r != SSH_SERVER_KNOWN_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: "); + r = SSH_AUTH_ERROR; + goto out; + } + + r = SSH_OK; + + /* initialise list of authentication methods. yes, according to official libssh docs... */ + ssh_userauth_none(ssh, NULL); +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + ssh_disconnect(ssh); + ssh_free(ssh); + ssh = NULL; + } + + return ssh; +} + +static int sshplugin_public_key_auth(struct crypt_device *cd, ssh_session ssh, const ssh_key pkey) +{ + int r; + + crypt_log(cd, CRYPT_LOG_DEBUG, "Trying public key authentication method.\n"); + + if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PUBLICKEY)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key auth method not allowed on host.\n"); + return SSH_AUTH_ERROR; + } + + r = ssh_userauth_try_publickey(ssh, NULL, pkey); + if (r == SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_DEBUG, "Public key method accepted.\n"); + r = ssh_userauth_publickey(ssh, NULL, pkey); + } + + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: "); + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + return r; +} + +static int SSHPLUGIN_open_pin(struct crypt_device *cd, int token, const char *pin, + char **password, size_t *password_len, void *usrptr) +{ + int r; + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token, *jobj_keypath; + ssh_key pkey; + ssh_session ssh; + + jobj_token = get_token_jobj(cd, token); + json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_token, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_token, "ssh_path", &jobj_path); + json_object_object_get_ex(jobj_token, "ssh_keypath",&jobj_keypath); + + r = ssh_pki_import_privkey_file(json_object_get_string(jobj_keypath), pin, NULL, NULL, &pkey); + if (r != SSH_OK) { + if (r == SSH_EOF) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to open and import private key.\n"); + return -EINVAL; + } + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key (password protected?).\n"); + return -EAGAIN; + } + + ssh = sshplugin_session_init(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user)); + if (!ssh) { + ssh_key_free(pkey); + return -EINVAL; + } + + r = sshplugin_public_key_auth(cd, ssh, pkey); + ssh_key_free(pkey); + + if (r == SSH_AUTH_SUCCESS) + r = sshplugin_download_password(cd, ssh, json_object_get_string(jobj_path), + password, password_len); + + ssh_disconnect(ssh); + ssh_free(ssh); + + return r ? -EINVAL : r; +} + +static int SSHPLUGIN_open(struct crypt_device *cd, int token, + char **password, size_t *password_len, void *usrptr) +{ + return SSHPLUGIN_open_pin(cd, token, NULL, password, password_len, usrptr); +} + +static void SSHPLUGIN_dump(struct crypt_device *cd, const char *json) +{ + json_object *jobj_token, *jobj_server, *jobj_user, *jobj_path, *jobj_keypath; + char buf[4096]; + + jobj_token = json_tokener_parse(json); + if (!jobj_token) + return; + + json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_token, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_token, "ssh_path", &jobj_path); + json_object_object_get_ex(jobj_token, "ssh_keypath",&jobj_keypath); + + snprintf(buf, sizeof(buf) - 1, "\tssh_server: %s\n\tssh_user: %s\n" + "\tssh_path: %s\n\tssh_key_path: %s\n", + json_object_get_string(jobj_server), + json_object_get_string(jobj_user), + json_object_get_string(jobj_path), + json_object_get_string(jobj_keypath)); + + crypt_log(cd, CRYPT_LOG_NORMAL, buf); + json_object_put(jobj_token); +} + +static int plugin_get_arg_value(struct crypt_device *cd, struct crypt_cli *cli, const char *key, crypt_arg_type_info type, void *rvalue) +{ + int r; + crypt_arg_type_info ti; + + r = crypt_cli_arg_type(cli, key, &ti); + if (r == -ENOENT) + l_err(cd, "%s argument is not defined.", key); + if (r) + return r; + + if (ti != type) { + l_err(cd, "%s argument type is unexpected.", key); + return -EINVAL; + } + + r = crypt_cli_arg_value(cli, key, rvalue); + if (r) + l_err(cd, "Failed to get %s value.", key); + + return r; +} + +int crypt_token_validate_create_params(struct crypt_device *cd, void *handle) { + int r; + + struct sshplugin_context *sc = (struct sshplugin_context *)handle; + + if (!sc) + return -EINVAL; + + if (crypt_cli_arg_set(sc->cli, "token-id")) { + r = plugin_get_arg_value(cd, sc->cli, "token-id", CRYPT_ARG_INT32, &sc->token); + if (r) + return r; + } else + sc->token = CRYPT_ANY_TOKEN; + + if (crypt_cli_arg_set(sc->cli, "key-slot")) { + r = plugin_get_arg_value(cd, sc->cli, "key-slot", CRYPT_ARG_INT32, &sc->keyslot); + if (r) + return r; + } else + sc->keyslot = 0; + + if (crypt_cli_arg_set(sc->cli, SERVER_ARG)) { + r = plugin_get_arg_value(cd, sc->cli, SERVER_ARG, CRYPT_ARG_STRING, &sc->server); + if (r) + return r; + } else { + l_err(cd, "SSH server URL must be specified."); + return -EINVAL; + } + + if (crypt_cli_arg_set(sc->cli, USER_ARG)) { + r = plugin_get_arg_value(cd, sc->cli, USER_ARG, CRYPT_ARG_STRING, &sc->user); + if (r) + return r; + } else { + l_err(cd, "Username must be specified."); + return -EINVAL; + } + + if (crypt_cli_arg_set(sc->cli, PATH_ARG)) { + r = plugin_get_arg_value(cd, sc->cli, PATH_ARG, CRYPT_ARG_STRING, &sc->path); + if (r) + return r; + } else { + l_err(cd, "Key path must be specified."); + return -EINVAL; + } + + if (crypt_cli_arg_set(sc->cli, KEYPATH_ARG)) { + r = plugin_get_arg_value(cd, sc->cli, KEYPATH_ARG, CRYPT_ARG_STRING, &sc->sshkey_path); + if (r) + return r; + } else { + l_err(cd, "SSH key path must be specified."); + return -EINVAL; + } + + sc->status |= CREATE_VALID; + + return 0; +} + +static int sshplugin_token_add(struct crypt_device *cd, + int token, + const char *server, + const char *user, + const char *path, + const char *keypath) +{ + json_object *jobj = NULL; + json_object *jobj_keyslots = NULL; + const char *string_token; + int r; + + jobj = json_object_new_object(); + if (!jobj) + return -EINVAL; + + /* type is mandatory field in all tokens and must match handler name member */ + json_object_object_add(jobj, "type", json_object_new_string(TOKEN_NAME)); + + jobj_keyslots = json_object_new_array(); + + /* mandatory array field (may be empty and assigned later */ + json_object_object_add(jobj, "keyslots", jobj_keyslots); + + /* custom metadata */ + json_object_object_add(jobj, "ssh_server", json_object_new_string(server)); + json_object_object_add(jobj, "ssh_user", json_object_new_string(user)); + json_object_object_add(jobj, "ssh_path", json_object_new_string(path)); + json_object_object_add(jobj, "ssh_keypath", json_object_new_string(keypath)); + + string_token = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); + if (!string_token) { + r = -EINVAL; + goto out; + } + + l_dbg(cd, "Token JSON: %s\n", string_token); + + r = crypt_token_json_set(cd, token, string_token); +out: + json_object_put(jobj); + return r; +} + +int crypt_token_create(struct crypt_device *cd, void *handle) { + int r; + struct sshplugin_context *sc = (struct sshplugin_context *)handle; + + if (!sc) + return -EINVAL; + + if (!sc->status) { + r = crypt_token_validate_create_params(cd, handle); + if (r) + return r; + } + + if (sc->status != CREATE_VALID) + return -EINVAL; + + r = sshplugin_token_add(cd, sc->token, sc->server, sc->user, sc->path, sc->sshkey_path); + if (r < 0) { + l_err(cd, "Failed to add token."); + return r; + } + + + sc->token = r; + l_dbg(cd, "Token: %d\n", sc->token); + + r = crypt_token_assign_keyslot(cd, sc->token, sc->keyslot); + if (r < 0) { + l_err(cd, "Failed to assign keyslot %d to token %d.", sc->keyslot, sc->token); + crypt_token_json_set(cd, sc->token, NULL); + } + + if (r > 0) { + r = 0; + sc->status |= CREATED; + } + + return r; +} + +const crypt_token_handler cryptsetup_token_handler = { + .name = "ssh", + .open = SSHPLUGIN_open, + .open_pin = SSHPLUGIN_open_pin, + .dump = SSHPLUGIN_dump, +}; From 6ba4b67f6c36a0f4e58e211fb473d678e73ee891 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Wed, 4 Nov 2020 09:48:31 +0100 Subject: [PATCH 06/14] Add test for SSH plugin Activateion test needs ssh server running on the machine. The test creates a new user "sshtest" and uses it to test activation using the plugin. --- .travis-functions.sh | 12 +++- tests/Makefile.am | 9 ++- tests/ssh-plugin-test | 148 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100755 tests/ssh-plugin-test diff --git a/.travis-functions.sh b/.travis-functions.sh index f852b282..b21a9335 100644 --- a/.travis-functions.sh +++ b/.travis-functions.sh @@ -17,6 +17,8 @@ MAKE="make -j2" DUMP_CONFIG_LOG="short" export TS_OPT_parsable="yes" +export RUN_SSH_PLUGIN_TEST=1 + function configure_travis { ./configure "$@" @@ -38,6 +40,9 @@ function check_nonroot configure_travis \ --enable-cryptsetup-reencrypt \ --enable-internal-sse-argon2 \ + --enable-external-tokens \ + --enable-external-cli-tokens \ + --enable-ssh-token \ "$cfg_opts" \ || return @@ -52,9 +57,12 @@ function check_root [ -z "$cfg_opts" ] && return - configure_travis \ + configure_travis \ --enable-cryptsetup-reencrypt \ --enable-internal-sse-argon2 \ + --enable-external-tokens \ + --enable-external-cli-tokens \ + --enable-ssh-token \ "$cfg_opts" \ || return @@ -105,6 +113,8 @@ function travis_install_script dkms \ linux-headers-$(uname -r) \ linux-modules-extra-$(uname -r) \ + libssh-dev \ + sshpass \ || return # For VeraCrypt test diff --git a/tests/Makefile.am b/tests/Makefile.am index b096859c..5328dcde 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -33,6 +33,12 @@ if INTEGRITYSETUP TESTS += integrity-compat-test endif +if CLI_TOKENS +if SSHPLUGIN_TOKEN +TESTS += ssh-plugin-test +endif +endif + EXTRA_DIST = compatimage.img.xz compatv10image.img.xz \ compatimage2.img.xz \ conversion_imgs.tar.xz \ @@ -72,7 +78,8 @@ EXTRA_DIST = compatimage.img.xz compatv10image.img.xz \ blkid-luks2-pv.img.xz \ Makefile.localtest \ bitlk-compat-test \ - bitlk-images.tar.xz + bitlk-images.tar.xz \ + ssh-plugin-test CLEANFILES = cryptsetup-tst* valglog* *-fail-*.log clean-local: diff --git a/tests/ssh-plugin-test b/tests/ssh-plugin-test new file mode 100755 index 00000000..ca4d37a2 --- /dev/null +++ b/tests/ssh-plugin-test @@ -0,0 +1,148 @@ +#!/bin/bash + +[ -z "$CRYPTSETUP_PATH" ] && CRYPTSETUP_PATH=".." +CRYPTSETUP=$CRYPTSETUP_PATH/cryptsetup +IMG="ssh_test.img" +MAP="sshtest" +USER="sshtest" +PASSWD="sshtest" +LOOPDEV=$(losetup -f 2>/dev/null) +SSH_OPTIONS="-o StrictHostKeyChecking=no" + +SSH_SERVER="localhost" +SSH_PATH="/home/$USER/keyfile" +SSH_KEY_PATH="$HOME/sshtest-key" + +FAST_PBKDF_OPT="--pbkdf pbkdf2 --pbkdf-force-iterations 1000" + +[ -z "$srcdir" ] && srcdir="." + +function remove_mapping() +{ + [ -b /dev/mapper/$MAP ] && dmsetup remove --retry $MAP + losetup -d $LOOPDEV >/dev/null 2>&1 + rm -f $IMG >/dev/null 2>&1 +} + +function remove_user() +{ + id -u $USER >/dev/null 2>&1 && userdel -r -f $USER >/dev/null 2>&1 + rm -f $SSH_KEY_PATH "$SSH_KEY_PATH.pub" >/dev/null 2>&1 +} + +function create_user() +{ + id -u $USER >/dev/null 2>&1 + [ $? -eq 0 ] && skip "User account $USER exists, aborting." + [ -f $SSH_KEY_PATH ] && skip "SSH key $SSH_KEY_PATH already exists, aborting." + + useradd -m $USER -p $(openssl passwd -crypt $PASSWD) || skip "Failed to add user for SSH plugin test." + + ssh-keygen -f $SSH_KEY_PATH -q -N "" >/dev/null 2>&1 + [ $? -ne 0 ] && remove_user && skip "Failed to create SSH key." +} + +function ssh_check() +{ + systemctl status sshd >/dev/null 2>&1 || skip "SSH server not running, skipping." +} + +function bin_check() +{ + which $1 >/dev/null 2>&1 || skip "WARNING: test require $1 binary, test skipped." +} + +function ssh_setup() +{ + # ssh-copy-id + sshpass -p $PASSWD ssh-copy-id -i $SSH_KEY_PATH $SSH_OPTIONS $USER@$SSH_SERVER >/dev/null 2>&1 + [ $? -ne 0 ] && remove_user && skip "Failed to copy SSH key." + + # try to ssh and also create keyfile + ssh -i $SSH_KEY_PATH $SSH_OPTIONS -o BatchMode=yes -n $USER@$SSH_SERVER -f "echo -n $PASSWD > $SSH_PATH" >/dev/null 2>&1 + [ $? -ne 0 ] && remove_user && skip "Failed to connect using SSH." +} + +function fail() +{ + echo "[FAILED]" + [ -n "$1" ] && echo "$1" + echo "FAILED backtrace:" + while caller $frame; do ((frame++)); done + remove_mapping + remove_user + exit 2 +} + +function skip() +{ + [ -n "$1" ] && echo "$1" + exit 77 +} + +format() +{ + dd if=/dev/zero of=$IMG bs=1M count=32 >/dev/null 2>&1 + sync + losetup $LOOPDEV $IMG + + echo $PASSWD | $CRYPTSETUP luksFormat --type luks2 $FAST_PBKDF_OPT $LOOPDEV -q + [ $? -ne 0 ] && fail "Format failed." +} + +check_dump() +{ + dump=$1 + + token=$(echo "$dump" | grep Tokens -A 1 | tail -1 | cut -d: -f2 | tr -d "\t\n ") + [ "$token" = "ssh" ] || fail " token check from dump failed." + + server=$(echo "$dump" | grep ssh_server | cut -d: -f2 | tr -d "\t\n ") + [ "$server" = $SSH_SERVER ] || fail " server check from dump failed." + + user=$(echo "$dump" | grep ssh_user | cut -d: -f2 | tr -d "\t\n ") + [ "$user" = "$USER" ] || fail " user check from dump failed." + + path=$(echo "$dump" | grep ssh_path | cut -d: -f2 | tr -d "\t\n ") + [ "$path" = "$SSH_PATH" ] || fail " path check from dump failed." + + key_path=$(echo "$dump" | grep ssh_key_path | cut -d: -f2 | tr -d "\t\n ") + [ "$key_path" = "$SSH_KEY_PATH" ] || fail " key_path check from dump failed." +} + +[ $(id -u) != 0 ] && skip "WARNING: You must be root to run this test, test skipped." + +# Prevent running dangerous useradd operation by default +[ -z "$RUN_SSH_PLUGIN_TEST" ] && skip "WARNING: Variable RUN_SSH_PLUGIN_TEST must be defined, test skipped." + +bin_check useradd +bin_check ssh +bin_check ssh-keygen +bin_check sshpass + +format + +echo -n "Adding SSH token: " +$CRYPTSETUP token add --plugin=ssh \ + --plugin-ssh-server $SSH_SERVER \ + --plugin-ssh-user $USER \ + --plugin-ssh-path $SSH_PATH \ + --plugin-ssh-keypath "$SSH_KEY_PATH" \ + $LOOPDEV +[ $? -ne 0 ] && fail "Failed to add SSH token to $LOOPDEV" + +out=$($CRYPTSETUP luksDump $LOOPDEV) +check_dump "$out" +echo "[OK]" + +echo -n "Activating using SSH token: " +ssh_check +create_user +ssh_setup + +echo "" | $CRYPTSETUP luksOpen -r $LOOPDEV $MAP -q >/dev/null 2>&1 +[ $? -ne 0 ] && fail "Failed to open $LOOPDEV using SSH token" +echo "[OK]" + +remove_mapping +remove_user From a5b8476343abf39e4b78191957cf4398ab33409f Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Mon, 2 Mar 2020 12:56:56 +0100 Subject: [PATCH 07/14] Add TPM2 token --- configure.ac | 13 + man/Makemodule.am | 4 +- man/cryptsetup-tpm2.8 | 187 +++++++ tokens/Makemodule.am | 15 + tokens/tpm2/libcryptsetup-token-tpm2.c | 608 ++++++++++++++++++++++ tokens/tpm2/utils_tpm2.c | 672 +++++++++++++++++++++++++ tokens/tpm2/utils_tpm2.h | 149 ++++++ tokens/tpm2/utils_tpm2_json.c | 373 ++++++++++++++ 8 files changed, 2020 insertions(+), 1 deletion(-) create mode 100644 man/cryptsetup-tpm2.8 create mode 100644 tokens/tpm2/libcryptsetup-token-tpm2.c create mode 100644 tokens/tpm2/utils_tpm2.c create mode 100644 tokens/tpm2/utils_tpm2.h create mode 100644 tokens/tpm2/utils_tpm2_json.c diff --git a/configure.ac b/configure.ac index 49ffcbb5..7cd53341 100644 --- a/configure.ac +++ b/configure.ac @@ -365,6 +365,10 @@ AC_ARG_ENABLE([ssh-token], AS_HELP_STRING([--enable-ssh-token], [enable build of ssh-token])) AM_CONDITIONAL(SSHPLUGIN_TOKEN, test "x$enable_ssh_token" = "xyes") +AC_ARG_ENABLE([tpm2-token], + AS_HELP_STRING([--enable-tpm2-token], [enable build of TPM2 token])) +AM_CONDITIONAL(TPM2_TOKEN, test "x$enable_tpm2_token" = "xyes") + dnl Try to use pkg-config for devmapper, but fallback to old detection PKG_CHECK_MODULES([DEVMAPPER], [devmapper >= 1.02.03],, [ AC_CHECK_LIB(devmapper, dm_task_set_name,, @@ -403,6 +407,12 @@ if test "x$enable_ssh_token" = "xyes"; then PKG_CHECK_MODULES([LIBSSH], [libssh]) fi +if test "x$enable_tpm2_token" = "xyes"; then + PKG_CHECK_MODULES([TSS2_ESYS], [tss2-esys]) + PKG_CHECK_MODULES([TSS2_RC], [tss2-rc]) + PKG_CHECK_MODULES([TSS2_TCTILDR], [tss2-tctildr]) +fi + dnl Crypto backend configuration. AC_ARG_WITH([crypto_backend], AS_HELP_STRING([--with-crypto_backend=BACKEND], [crypto backend (gcrypt/openssl/nss/kernel/nettle) [openssl]]), @@ -558,6 +568,9 @@ AC_SUBST([CRYPTO_STATIC_LIBS]) AC_SUBST([JSON_C_LIBS]) AC_SUBST([LIBARGON2_LIBS]) AC_SUBST([BLKID_LIBS]) +AC_SUBST([TSS2_ESYS_LIBS]) +AC_SUBST([TSS2_RC_LIBS]) +AC_SUBST([TSS2_TCTILDR_LIBS]) AC_SUBST([LIBCRYPTSETUP_VERSION]) AC_SUBST([LIBCRYPTSETUP_VERSION_INFO]) diff --git a/man/Makemodule.am b/man/Makemodule.am index 3f68441f..9b03bd97 100644 --- a/man/Makemodule.am +++ b/man/Makemodule.am @@ -1,7 +1,9 @@ -EXTRA_DIST += man/cryptsetup.8 man/integritysetup.8 man/veritysetup.8 man/cryptsetup-reencrypt.8 +EXTRA_DIST += man/cryptsetup.8 man/cryptsetup-tpm2.8 man/integritysetup.8 man/veritysetup.8 man/cryptsetup-reencrypt.8 man8_MANS += man/cryptsetup.8 +man8_MANS += man/cryptsetup-tpm2.8 + if VERITYSETUP man8_MANS += man/veritysetup.8 endif diff --git a/man/cryptsetup-tpm2.8 b/man/cryptsetup-tpm2.8 new file mode 100644 index 00000000..0fa6ea15 --- /dev/null +++ b/man/cryptsetup-tpm2.8 @@ -0,0 +1,187 @@ +.TH CRYPTSETUP-TPM2 "8" "December 2019" "cryptsetup-tpm2" "Maintenance Commands" +.SH NAME +cryptsetup-tpm2 - tool for activating LUKS2 encrypted volumes using TPM2 +.SH SYNOPSIS +.B cryptsetup-tpm2 +.SH DESCRIPTION +.PP +cryptsetup-tpm2 is used to conveniently lock and activate LUKS2 volumes using a +passphrase stored in a \fBTPM 2.0\fR. \fBTPM\fR is a secure cryptoprocessor +(similar to a smartcard) present in most of the recent laptops (and some desktops). +cryptsetup-tpm2 stores metadata in a \fItpm2\fR token inside the \fItokens\fR +section of the LUKS2 header (see \fILUKS2 On-Disk Format Specification\fR). + +This utility supports sealing the TPM key to a current HW/SW state. To fully +utilize this feature, it is required that the state is measured to the PCR banks +by BIOS/UEFI, bootloader (e.g. \fITrustedGrub\fR) and kernel (IMA subsystem). +If anything in the boot chain deviates from the pre-measured state, the passphrase +won't be released from the TPM. + +You can seal the passphrase to a specified PCR. They have the following meaning: +.IP +\fIPCR#0-7\fR Measured by BIOS/EFI. includes measurement of items like boot +options and order, microcode or secure boot status. +.IP +\fIPCR#16\fR Debug register + +.PP +In TrustedGrub2 PCRs contain the following measurements: +.IP +\fIPCR#8\fR +First sector of TrustedGRUB2 kernel (diskboot.img) +.IP +\fIPCR#9\fR +TrustedGRUB2 kernel (core.img) +.IP +\fIPCR#10\fR +Loader measurements - currently linux-kernel, initrd, ntldr, chainloader, multiboot, module +.IP +\fIPCR#11\fR +Contains all commandline arguments from scripts (e.g. grub.cfg) and those entered in the shell +.IP +\fIPCR#12\fR +LUKS-header +.IP +\fIPCR#13\fR +Parts of GRUB2 that are loaded from disk like GRUB2-modules + +.PP +To protect the TPM-stored passphrase \fIdictionary attack (DA) protection\fR +can be enabled inside the TPM. \fBBe careful not to enter wrong TPM password +too many times with this option enabled.\fR + +\fBIt is strongly recommended to have a least one regular (non-TPM) passphrase +added.\fR This will prevent a lockout in case of changed PCR configuration +(e.g. after kernel update or kernel command line change) or after exceeded +number of attempts on a DA-protected passphrase. + +.SH BASIC COMMANDS + +\fIadd\fR +.IP +This will generate a random LUKS2 passphrase (using random data supplied from +the TPM) and store the passphrase inside NV memory of the TPM. + +\fB\fR can be [--tpm2-nv, --tpm2-pcr, --tpm2-bank, --tpm2-daprotect, +--tpm2-no-pin, --tpm2-key-size, --timeout=secs] + + +.PP +\fIopen\fR +.IP +Retrieves a LUKS2 passphrase from the TPM's NV index stored in the header +metadata. If the NV index is PIN protected, the user will be asked for the PIN. +Then \fB\fR will be activated using the passphrase and opened as +\fB\fR. + +\fB\fR can be [--token-id] + +.PP +\fIkill\fR --token-id or --tpm2-nv= +.IP +This will remove the randomly generated passphrase from the TPM and the +associated LUKS2 keyslot. Specifying either token ID or NV index is mandatory. + +.PP +\fIdump\fR +.IP +The LUKS2 header will be dumped in a human-readable way. This includes the +fields specific to TPM2 token. + +.PP +\fIlist\fR +.IP +This action does not operate on any data device. It only lists supported PCRs +for every bank. + +.SH OPTIONS +.TP +.B --tpm2-nv=<0x01800000..0x01BFFFFF> +Specify an index on which to store the randomly generated passphrase if none is +supplied a free one is found. +.TP +.B --tpm2-pcr=[,[,[...]]] +When supplied, the randomly generated passphrase will will be sealed to current +values of the specified PCRs. +.TP +.B --tpm2-bank=[,[,[...]]] +Selection of banks for PCR sealing +.TP +.B --tpm2-daprotect +Enable dictionary attack protection on the TPM stored passphrase. +.TP +.B --tpm2-no-pin +Don't PIN-protect TPM NV index. This way you don't have to enter any +passphrase/PIN when activating the disk. \fBWhen enabled, it is strongly +recommended to have TPM enabled bootloader and kernel which make measurements +of the current state and to seal the passphrase to the appropriate PCRs. Even +then, the system is susceptible to cold-boot attacks.\fR +.TP +.B --tpm2-key-size= +Size of a randomly generated passphrase which will be stored in the TPM. It +makes no sense to set it to a greater number than the master key length. +.TP +.B --token-id=INT +Specify the token number (as listed by the \fIlist\fR command) +.TP +.B --timeout=secs +Timeout for TPM PIN interactive passphrase prompt (in seconds) +.TP +.B --debug +Run in debug mode with full diagnostic logs. Debug output lines are always +prefixed by '#'. + +.SH RETURN CODES +Cryptsetup returns 0 on success and a non-zero value on error. + +Error codes are: 1 wrong parameters, 2 no permission (bad passphrase), +3 out of memory, 4 wrong device specified, 5 device already exists +or device is busy. + +.SH AUTHORS +cryptsetup-tpm2 is based on code originally written by Andreas Fuchs from +Fraunhofer SIT sponsored by Infineon Technologies AG and further extended by +Daniel Zatovic +.br +The cryptsetup-tpm2 man page was written by Daniel Zatovic + +.br + +.SH COPYRIGHT +Copyright \(co 2018-2019 Fraunhofer SIT sponsored by Infineon Technologies AG +.br +Copyright \(co 2019-2020 Red Hat, Inc. All rights reserved. +.br +Copyright \(co 2019-2020 Daniel Zatovic +.br +Copyright \(co 2009-2020 Milan Broz +.br + +.B libcryptsetup + +.IP +Copyright \(co 2004 Jana Saout +.br +Copyright \(co 2004-2006 Clemens Fruhwirth +.br +Copyright \(co 2012-2014 Arno Wagner +.br +Copyright \(co 2009-2020 Red Hat, Inc. +.br +Copyright \(co 2009-2020 Milan Broz +.br + +.PP +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +The LUKS website at \fBhttps://gitlab.com/cryptsetup/cryptsetup/\fR + +The cryptsetup FAQ, contained in the distribution package and +online at +\fBhttps://gitlab.com/cryptsetup/cryptsetup/wikis/FrequentlyAskedQuestions\fR + +The cryptsetup mailing list and list archive, see FAQ entry 1.6. + +The LUKS on-disk format specification available at +\fBhttps://gitlab.com/cryptsetup/cryptsetup/wikis/Specification\fR diff --git a/tokens/Makemodule.am b/tokens/Makemodule.am index a7e1e0b4..30c70d0f 100644 --- a/tokens/Makemodule.am +++ b/tokens/Makemodule.am @@ -10,3 +10,18 @@ libcryptsetup_token_ssh_la_SOURCES = tokens/ssh/libcryptsetup-token-ssh.c libcryptsetup_token_ssh_la_LIBADD = -lssh libcryptsetup.la @JSON_C_LIBS@ lib_LTLIBRARIES += libcryptsetup-token-ssh.la endif + +if TPM2_TOKEN +XINCLUDES = -I$(top_srcdir)/src -I$(top_srcdir)/lib/crypto_backend -I$(top_srcdir)/lib -I$(top_srcdir)/lib/cli +libcryptsetup_token_tpm2_la_CPPFLAGS = $(XINCLUDES) --include config.h +libcryptsetup_token_tpm2_la_LDFLAGS = $(TOKENS_LDFLAGS) +libcryptsetup_token_tpm2_la_SOURCES = \ + tokens/tpm2/libcryptsetup-token-tpm2.c \ + tokens/tpm2/utils_tpm2.h \ + tokens/tpm2/utils_tpm2.c \ + tokens/tpm2/utils_tpm2_json.c \ + src/plugin.h + +libcryptsetup_token_tpm2_la_LIBADD = -lm libcryptsetup.la libcryptsetup_cli.la @JSON_C_LIBS@ @TSS2_ESYS_LIBS@ @TSS2_RC_LIBS@ @TSS2_TCTILDR_LIBS@ +lib_LTLIBRARIES += libcryptsetup-token-tpm2.la +endif diff --git a/tokens/tpm2/libcryptsetup-token-tpm2.c b/tokens/tpm2/libcryptsetup-token-tpm2.c new file mode 100644 index 00000000..c1a87e85 --- /dev/null +++ b/tokens/tpm2/libcryptsetup-token-tpm2.c @@ -0,0 +1,608 @@ +/* + * LUKS - Linux Unified Key Setup v2, TPM type token handler + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 Milan Broz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include "utils_tpm2.h" +#include "libcryptsetup.h" +#include "../../src/plugin.h" + +#define TOKEN_NAME "tpm2" +#define DEFAULT_TPM2_SIZE 64 +#define DEFAULT_PCR_BANK "sha256" + +#define NV_ARG "plugin-tpm2-nv" +#define PCR_ARG "plugin-tpm2-pcr" +#define BANK_ARG "plugin-tpm2-bank" +#define DAPROTECT_ARG "plugin-tpm2-daprotect" +#define NOPIN_ARG "plugin-tpm2-no-pin" +#define TCTI_ARG "plugin-tpm2-tcti" + +#define CREATE_VALID (1 << 0) +#define CREATED (1 << 1) +#define REMOVE_VALID (1 << 2) +#define REMOVED (1 << 3) + +static void tpm2_token_dump(struct crypt_device *cd, const char *json) +{ + uint32_t nvindex, pcrs, pcrbanks; + size_t nvkey_size; + bool daprotect, pin; + char buf[1024], num[32]; + unsigned i, n; + + if (tpm2_token_read(cd, json, &nvindex, &pcrs, &pcrbanks, + &daprotect, &pin, &nvkey_size)) { + l_err(cd, "Cannot read JSON token metadata."); + return; + } + + l_std(cd, "\tNVindex: 0x%08" PRIx32 "\n", nvindex); + l_std(cd, "\tNVKey: %zu [bytes]\n", nvkey_size); + + for (*buf = '\0', n = 0, i = 0; i < 32; i++) { + if (!(pcrs & (1 << i))) + continue; + snprintf(num, sizeof(num), "%s%u", n ? "," : "", i); + strcat(buf, num); + n++; + } + l_std(cd, "\tPCRs: %s\n", buf); + + *buf = '\0'; + n = 0; + + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (pcrbanks & hash_algs[i].crypt_id) { + if (n) + strcat(buf, ","); + strcat(buf, hash_algs[i].name); + n++; + } + } + + l_std(cd, "\tPCRBanks: %s\n", buf); + + *buf = '\0'; + n = 0; + if (daprotect) { + strcat(buf, "DA_PROTECT"); + n++; + } + if (pin) + strcat(buf, n++ ? ",PIN" : "PIN"); + l_std(cd, "\tflags: %s\n", buf); +} + +static int tpm2_token_open_pin(struct crypt_device *cd, + int token, + const char *tpm_pass, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + int r; + TSS2_RC tpm_rc; + ESYS_CONTEXT *ctx; + uint32_t nvindex, pcrselection, pcrbanks; + size_t nvkey_size; + bool daprotect, pin; + const char *json; + + if (tpm_init(cd, &ctx, NULL) != TSS2_RC_SUCCESS) + return -EINVAL; + + r = crypt_token_json_get(cd, token, &json); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + goto out; + } + + r = tpm2_token_read(cd, json, &nvindex, &pcrselection, &pcrbanks, + &daprotect, &pin, &nvkey_size); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + goto out; + } + + if (pin && !tpm_pass) { + if (daprotect) + l_std(cd, "TPM stored password has dictionary attack protection turned on. " + "Don't enter password too many times.\n"); + r = -EAGAIN; + goto out; + } + + *buffer = malloc(nvkey_size); + if (!*buffer) { + r = -ENOMEM; + goto out; + } + *buffer_len = nvkey_size; + + r = -EINVAL; + + tpm_rc = tpm_nv_read(cd, ctx, nvindex, tpm_pass, tpm_pass ? strlen(tpm_pass) : 0, + pcrselection, pcrbanks, *buffer, nvkey_size); + + if (tpm_rc == TSS2_RC_SUCCESS) { + r = 0; + } else if (tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_BAD_AUTH) || + tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_AUTH_FAIL)) { + LOG_TPM_ERR(cd, tpm_rc); + r = -EPERM; + } + +out: + Esys_Finalize(&ctx); + return r; +} + +static int tpm2_token_open(struct crypt_device *cd, + int token, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + return tpm2_token_open_pin(cd, token, NULL, buffer, buffer_len, usrptr); +} + +static int _tpm2_token_validate(struct crypt_device *cd, const char *json) +{ + return tpm2_token_validate(json); +} + +struct tpm2_context { + const char *tpmbanks_str; + const char *tcti_str; + uint32_t tpmbanks; + uint32_t tpmnv; + uint32_t tpmpcrs; + uint32_t pass_size; + ESYS_CONTEXT *ctx; + + bool tpmdaprotect; + bool no_tpm_pin; + + int timeout; + int keyslot; + int token; + + uint8_t status; + + struct crypt_cli *cli; +}; + +const crypt_token_handler cryptsetup_token_handler = { + .name = "tpm2", + .open = tpm2_token_open, + .open_pin = tpm2_token_open_pin, + .validate = _tpm2_token_validate, + .dump = tpm2_token_dump +}; + +int crypt_token_handle_init(struct crypt_cli *cli, void **handle) +{ + int r; + struct tpm2_context *tc; + + if (!handle) + return -EINVAL; + + tc = calloc(1, sizeof(*tc)); + if (!tc) + return -ENOMEM; + + r = tpm2_token_get_pcrbanks(DEFAULT_PCR_BANK, &tc->tpmbanks); + if (r < 0) { + free(tc); + return r; + } + + tc->cli = cli; + + *handle = tc; + + return 0; +} + +void crypt_token_handle_free(void *handle) +{ + free(handle); +} + +static const crypt_arg_list create_args[] = { + /* plugin specific args */ + { NV_ARG, "Select TPM's NV index", CRYPT_ARG_UINT32, &create_args[1] }, + { PCR_ARG, "Selection of TPM PCRs", CRYPT_ARG_UINT32, &create_args[2] }, + { BANK_ARG, "Selection of TPM PCR banks", CRYPT_ARG_STRING, &create_args[3] }, + { DAPROTECT_ARG,"Enable TPM dictionary attack protection", CRYPT_ARG_BOOL, &create_args[4] }, + { NOPIN_ARG, "Don't PIN protect TPM NV index", CRYPT_ARG_BOOL, &create_args[5] }, + { TCTI_ARG, "Select TCTI in format :, e.g. device:/dev/tpm0", CRYPT_ARG_STRING, &create_args[6] }, + /* inherited from cryptsetup core args */ + { "key-size", NULL, CRYPT_ARG_UINT32, &create_args[7] }, + { "token-id", NULL, CRYPT_ARG_INT32, &create_args[8] }, + { "key-slot", NULL, CRYPT_ARG_INT32, &create_args[9] }, + { "timeout", NULL, CRYPT_ARG_UINT32, NULL } +}; + +static const crypt_arg_list remove_args[] = { + /* plugin specific args */ + { NV_ARG, "Select TPM's NV index", CRYPT_ARG_UINT32, &remove_args[1] }, + { TCTI_ARG, "Select TCTI in format :, e.g. device:/dev/tpm0", CRYPT_ARG_STRING, &remove_args[2] }, + /* inherited from cryptsetup core args */ + { "token-id", "Token number to remove", CRYPT_ARG_INT32, NULL }, +}; + +const crypt_arg_list *crypt_token_create_params(void) +{ + return create_args; +} + +const crypt_arg_list *crypt_token_remove_params(void) +{ + return remove_args; +} + +static int plugin_get_arg_value(struct crypt_device *cd, struct crypt_cli *cli, const char *key, crypt_arg_type_info type, void *rvalue) +{ + int r; + crypt_arg_type_info ti; + + r = crypt_cli_arg_type(cli, key, &ti); + if (r == -ENOENT) + l_err(cd, "%s argument is not defined.", key); + if (r) + return r; + + if (ti != type) { + l_err(cd, "%s argument type is unexpected.", key); + return -EINVAL; + } + + r = crypt_cli_arg_value(cli, key, rvalue); + if (r) + l_err(cd, "Failed to get %s value.", key); + + return r; +} + +static int get_create_cli_args(struct crypt_device *cd, struct tpm2_context *tc) +{ + int r; + + r = plugin_get_arg_value(cd, tc->cli, "key-slot", CRYPT_ARG_INT32, &tc->keyslot); + if (r) + return r; + + r = plugin_get_arg_value(cd, tc->cli, "token-id", CRYPT_ARG_INT32, &tc->token); + if (r) + return r; + + if (crypt_cli_arg_set(tc->cli, "key-size")) { + r = plugin_get_arg_value(cd, tc->cli, "key-size", CRYPT_ARG_UINT32, &tc->pass_size); + if (r) + return r; + } else + tc->pass_size = DEFAULT_TPM2_SIZE; + + r = plugin_get_arg_value(cd, tc->cli, "timeout", CRYPT_ARG_UINT32, &tc->timeout); + if (r) + return r; + + if (crypt_cli_arg_set(tc->cli, NV_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, NV_ARG, CRYPT_ARG_UINT32, &tc->tpmnv); + if (r) + return r; + } + + if (crypt_cli_arg_set(tc->cli, PCR_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, PCR_ARG, CRYPT_ARG_UINT32, &tc->tpmpcrs); + if (r) + return r; + } + + if (crypt_cli_arg_set(tc->cli, BANK_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, BANK_ARG, CRYPT_ARG_STRING, &tc->tpmbanks_str); + if (r) + return r; + } + + if (crypt_cli_arg_set(tc->cli, TCTI_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, BANK_ARG, CRYPT_ARG_STRING, &tc->tcti_str); + if (r) + return r; + } + + tc->tpmdaprotect = crypt_cli_arg_set(tc->cli, DAPROTECT_ARG); + tc->no_tpm_pin = crypt_cli_arg_set(tc->cli, NOPIN_ARG); + + return 0; +} + +int crypt_token_validate_create_params(struct crypt_device *cd, void *handle) +{ + int r; + struct tpm2_context *tc = (struct tpm2_context *)handle; + + if (!tc) + return -EINVAL; + + r = get_create_cli_args(cd, tc); + if (r) + return r; + + if (tpm2_token_get_pcrbanks(tc->tpmbanks_str ?: DEFAULT_PCR_BANK, &tc->tpmbanks)) { + l_err(cd, "Wrong PCR bank value."); + return -EINVAL; + } + + if (!tc->tpmbanks) { + l_err(cd, "PCR banks must be selected."); + return -EINVAL; + } + + tc->status |= CREATE_VALID; + + return 0; +} + +int crypt_token_create(struct crypt_device *cd, void *handle) +{ + char *existing_pass = NULL, *tpm_pin = NULL, *random_pass = NULL; + size_t existing_pass_len, tpm_pin_len = 0; + int r; + bool supports_algs_for_pcrs; + TSS2_RC tpm_rc; + struct tpm2_context *tc = (struct tpm2_context *)handle; + + if (!tc) + return -EINVAL; + + if (!tc->status) { + r = crypt_token_validate_create_params(cd, handle); + if (r) + return r; + } + + if (tc->status != CREATE_VALID) + return -EINVAL; + + if (tc->tcti_str) + l_dbg(cd, "initializing with TCTI %s", tc->tcti_str); + else + l_dbg(cd, "initializing with default TCTI"); + + if (tpm_init(cd, &tc->ctx, tc->tcti_str) != TSS2_RC_SUCCESS) + return -EINVAL; + + tpm_rc = tpm2_supports_algs_for_pcrs(cd, tc->ctx, tc->tpmbanks, tc->tpmpcrs, &supports_algs_for_pcrs); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(NULL, "Failed to get PCRS capability from TPM."); + LOG_TPM_ERR(NULL, tpm_rc); + r = -ECOMM; + goto out; + } + + if (!supports_algs_for_pcrs) { + l_err(NULL, "Your TPM doesn't support selected PCR and banks combination."); + r = -ENOTSUP; + goto out; + } + + random_pass = crypt_safe_alloc(tc->pass_size); + if (!random_pass) { + r = -ENOMEM; + goto out; + } + + r = tpm_get_random(cd, tc->ctx, random_pass, tc->pass_size); + if (r < 0) + goto out; + + r = crypt_cli_get_key("Enter existing LUKS2 pasphrase:", + &existing_pass, &existing_pass_len, + 0, 0, NULL, tc->timeout, 0, 0, cd, NULL); + if (r < 0) + goto out; + + if (!tc->no_tpm_pin) { + r = crypt_cli_get_key("Enter new TPM password:", + &tpm_pin, &tpm_pin_len, + 0, 0, NULL, tc->timeout, 1, 0, cd, NULL); + if (r < 0) + goto out; + } + + if (tc->tpmnv == 0) { + tpm_rc = tpm_nv_find(cd, tc->ctx, &tc->tpmnv); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "Error while trying to find free NV index."); + LOG_TPM_ERR(cd, tpm_rc); + r = -EINVAL; + goto out; + } + + if (!tc->tpmnv) { + l_err(cd, "Error no free TPM NV-Index found."); + r = -EACCES; + goto out; + } + } + + tpm_rc = tpm_nv_define(cd, tc->ctx, tc->tpmnv, tpm_pin, tpm_pin_len, tc->tpmpcrs, + tc->tpmbanks, tc->tpmdaprotect, NULL, 0, tc->pass_size); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "TPM NV-Index definition failed"); + LOG_TPM_ERR(cd, tpm_rc); + r = -EINVAL; + goto out; + } + + tpm_rc = tpm_nv_write(cd, tc->ctx, tc->tpmnv, tpm_pin, tpm_pin_len, + random_pass, tc->pass_size); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "TPM NV-Index write error."); + LOG_TPM_ERR(cd, tpm_rc); + tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); + r = -EINVAL; + goto out; + } + + r = crypt_keyslot_add_by_passphrase(cd, tc->keyslot, existing_pass, existing_pass_len, random_pass, tc->pass_size); + if (r < 0) { + if (r == -EPERM) + l_err(cd, "Wrong LUKS2 passphrase supplied."); + tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); + goto out; + } + tc->keyslot = r; + l_std(cd, "Using keyslot %d.\n", tc->keyslot); + + r = tpm2_token_add(cd, tc->token, tc->tpmnv, tc->tpmpcrs, tc->tpmbanks, tc->tpmdaprotect, !tc->no_tpm_pin, tc->pass_size); + if (r < 0) { + tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); + crypt_keyslot_destroy(cd, tc->keyslot); + goto out; + } + tc->token = r; + l_std(cd, "Token: %d\n", tc->token); + + r = crypt_token_assign_keyslot(cd, tc->token, tc->keyslot); + if (r < 0) { + l_err(cd, "Failed to assign keyslot %d to token %d.", tc->keyslot, tc->token); + tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); + crypt_keyslot_destroy(cd, tc->keyslot); + crypt_token_json_set(cd, tc->token, NULL); + } + + if (r > 0) { + r = 0; + tc->status |= CREATED; + } +out: + crypt_safe_free(random_pass); + crypt_safe_free(existing_pass); + crypt_safe_free(tpm_pin); + + Esys_Finalize(&tc->ctx); + return r; +} + +static int get_remove_cli_args(struct crypt_device *cd, struct tpm2_context *tc) +{ + int r; + + r = plugin_get_arg_value(cd, tc->cli, "token-id", CRYPT_ARG_INT32, &tc->token); + if (r) + return r; + + if (crypt_cli_arg_set(tc->cli, NV_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, NV_ARG, CRYPT_ARG_UINT32, &tc->tpmnv); + if (r) + return r; + } + + return 0; +} + +int crypt_token_validate_remove_params(struct crypt_device *cd, void *handle) +{ + int r; + struct tpm2_context *tc = (struct tpm2_context *)handle; + + if (!tc || tc->status) + return -EINVAL; + + r = get_remove_cli_args(cd, tc); + if (r) + return r; + + if (tc->token < 0 && tc->token != CRYPT_ANY_TOKEN) { + l_err(cd, "Invalid token specification."); + return -EINVAL; + } + + if (!tc->tpmnv && tc->token == CRYPT_ANY_TOKEN) { + l_err(cd, "Token ID or TPM2 nvindex option must be specified."); + return -EINVAL; + } + + tc->status = REMOVE_VALID; + + return 0; +} + +int crypt_token_remove(struct crypt_device *cd, void *handle) +{ + int i, r; + const char *type; + struct tpm2_context *tc = (struct tpm2_context *)handle; + + if (!tc) + return -EINVAL; + + if (!tc->status) { + r = crypt_token_validate_remove_params(cd, handle); + if (r) + return r; + } + + if (tc->status != REMOVE_VALID) + return -EINVAL; + + if (tc->token == CRYPT_ANY_TOKEN) + tc->token = tpm2_token_by_nvindex(cd, tc->tpmnv); + + if (tc->token < 0 || + crypt_token_status(cd, tc->token, &type) != CRYPT_TOKEN_EXTERNAL || + strcmp(type, "tpm2")) { + l_err(cd, "No TPM2 token to destroy."); + return -EINVAL; + } + + /* Destroy all keyslots assigned to TPM 2 token */ + for (i = 0; i < crypt_keyslot_max(CRYPT_LUKS2); i++) { + if (!crypt_token_is_assigned(cd, tc->token, i)) { + r = crypt_keyslot_destroy(cd, i); + if (r < 0) { + l_err(cd, "Cannot destroy keyslot %d.", i); + return r; + } + } + } + + if (tpm_init(cd, &tc->ctx, tc->tcti_str) != TSS2_RC_SUCCESS) + return -EINVAL; + + /* Destroy TPM2 NV index and token object itself */ + r = tpm2_token_kill(cd, tc->ctx, tc->token); + if (!r) + tc->status |= REMOVED; + + Esys_Finalize(&tc->ctx); + return r; +} diff --git a/tokens/tpm2/utils_tpm2.c b/tokens/tpm2/utils_tpm2.c new file mode 100644 index 00000000..89300e67 --- /dev/null +++ b/tokens/tpm2/utils_tpm2.c @@ -0,0 +1,672 @@ +/* + * TPM2 utilities for LUKS2 TPM2 type keyslot + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 Milan Broz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "utils_tpm2.h" +#include "libcryptsetup.h" + +const alg_info *get_alg_info_by_name(const char *name) { + unsigned i; + + for(i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (!strcasecmp(hash_algs[i].name, name)) + return &hash_algs[i]; + } + + return NULL; +} + +const alg_info *get_alg_info_by_id(TPM2_ALG_ID id) { + unsigned i; + + for(i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (hash_algs[i].id == id) + return &hash_algs[i]; + } + + return NULL; +} + +const alg_info *get_alg_info_by_crypt_id(uint32_t crypt_id) { + unsigned i; + + for(i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (hash_algs[i].crypt_id == crypt_id) + return &hash_algs[i]; + } + + return NULL; +} + +/* + * Initialize the TPM including a potentially necessary TPM2_Startup command, + * which is needed for simulators and RPi TPM hats. + */ +TSS2_RC tpm_init(struct crypt_device *cd, ESYS_CONTEXT **ctx, const char *tcti_conf) +{ + TSS2_RC r; + l_dbg(cd, "Initializing ESYS connection"); + + TSS2_TCTI_CONTEXT *tcti_ctx = NULL; + r = Tss2_TctiLdr_Initialize (tcti_conf, &tcti_ctx); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Error initializing TCTI"); + return r; + } + + r = Esys_Initialize(ctx, tcti_ctx, NULL); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Error initializing ESYS"); + return r; + } + + r = Esys_Startup(*ctx, TPM2_SU_CLEAR); + if (r == TPM2_RC_INITIALIZE) { + l_dbg(cd, "TPM already started up. Not an error!"); + r = TSS2_RC_SUCCESS; + } + + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "TPM StartUp command failed"); + Esys_Finalize(ctx); + } + + return r; +} + +static TSS2_RC tpm_getPcrDigest(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + const TPML_PCR_SELECTION *pcrs, + TPM2_ALG_ID hashAlg, + TPM2B_DIGEST *pcrDigest) +{ + TSS2_RC r; + TPM2B_AUTH auth = {0}; + ESYS_TR hash; + TPML_DIGEST *value; + TPML_PCR_SELECTION readPCRs = { + .count = 1, + .pcrSelections = {} + }; + TPM2B_DIGEST *returnPcrDigest; + TPM2B_MAX_BUFFER digest; + unsigned int i, j; + + r = Esys_HashSequenceStart(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &auth, hashAlg, &hash); + if (r) + return r; + + for (i = 0; i < pcrs->count; i++) { + + readPCRs.pcrSelections[0].hash = pcrs->pcrSelections[i].hash; + readPCRs.pcrSelections[0].sizeofSelect = 3; + for (j = 0; j < 24; j++) { + if (!(pcrs->pcrSelections[i].pcrSelect[j / 8] & (1 << (j % 8)))) + continue; + + readPCRs.pcrSelections[0].pcrSelect[0] = 0; + readPCRs.pcrSelections[0].pcrSelect[1] = 0; + readPCRs.pcrSelections[0].pcrSelect[2] = 0; + + readPCRs.pcrSelections[0].pcrSelect[j / 8] = (1 << (j % 8)); + + r = Esys_PCR_Read(ctx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &readPCRs, NULL, NULL, &value); + //TODO: if (r == 0x984) continue; + if (r) { + l_err(cd, "PCR Read failed"); + return r; + } + if (!value->count) { + free(value); + continue; + } + + digest.size = value->digests[0].size; + memcpy(&digest.buffer[0], &value->digests[0].buffer, digest.size); + + r = Esys_SequenceUpdate(ctx, hash, ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &digest); + free(value); + if (r) + return r; + } + } + + r = Esys_SequenceComplete(ctx, hash, ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + NULL, TPM2_RH_NULL, &returnPcrDigest, NULL); + if (r) + return r; + + *pcrDigest = *returnPcrDigest; + free(returnPcrDigest); + + return 0; +} + +static TSS2_RC tpm_policy_Read(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_pcr, + uint32_t pcrbanks, + ESYS_TR *authSession, + TPM2B_DIGEST *authPolicy) +{ + TSS2_RC r; + ESYS_TR session; + TPM2B_DIGEST *policyDigest; + TPM2B_DIGEST pcrDigest = { + .size = 0, + .buffer = {} + }; + TPML_PCR_SELECTION pcrs = { + .count = 0 + }; + TPMT_SYM_DEF sym = { + .algorithm = TPM2_ALG_AES, + .keyBits = { + .aes = 128 + }, + .mode = { + .aes = TPM2_ALG_CFB + } + }; + unsigned int i; + + if (pcrbanks == 0) { + l_err(cd, "No banks selected."); + return TSS2_BASE_RC_BAD_SIZE; + } + + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (pcrbanks & hash_algs[i].crypt_id) { + pcrs.pcrSelections[pcrs.count].hash = hash_algs[i].id; + pcrs.count++; + } + } + + for (i = 0; i < pcrs.count; i++) { + pcrs.pcrSelections[i].sizeofSelect = 3; + pcrs.pcrSelections[i].pcrSelect[0] = tpm_pcr & 0xff; + pcrs.pcrSelections[i].pcrSelect[1] = tpm_pcr >> 8 & 0xff; + pcrs.pcrSelections[i].pcrSelect[2] = tpm_pcr >>16 & 0xff; + } + + r = tpm_getPcrDigest(cd, ctx, &pcrs, TPM2_ALG_SHA256, &pcrDigest); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, ESYS_TR_NONE, NULL, TPM2_SE_POLICY, + &sym, TPM2_ALG_SHA256, &session); + if (r != TSS2_RC_SUCCESS) { + return r; + } + + r = Esys_PolicyPCR(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &pcrDigest, &pcrs); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + r = Esys_PolicyPassword(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + r = Esys_PolicyCommandCode(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, TPM2_CC_NV_Read); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + r = Esys_PolicyGetDigest(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, &policyDigest); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + if (authSession) + *authSession = session; + else + Esys_FlushContext(ctx, session); + + if (authPolicy) + *authPolicy = *policyDigest; + free(policyDigest); + + return TSS2_RC_SUCCESS; +} + +static TSS2_RC tpm_nv_prep(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + ESYS_TR *nvIndex) +{ + TSS2_RC r; + TPM2B_AUTH tpm_pin = { + .size = pin_size, + .buffer = {} + }; + + if (pin_size > sizeof(tpm_pin.buffer)) + return TSS2_BASE_RC_BAD_SIZE; + + if (pin_size > 0) + memcpy(&tpm_pin.buffer[0], pin, tpm_pin.size); + + if (tpm_nv < 0x01800000 || tpm_nv > 0x01BFFFFF) { + l_err(cd, "NV index handle %08x out of range", tpm_nv); + return TSS2_BASE_RC_BAD_SIZE; + } + + r = Esys_TR_FromTPMPublic(ctx, tpm_nv, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, nvIndex); + if (r != TSS2_RC_SUCCESS) { + return r; + } + + r = Esys_TR_SetAuth(ctx, *nvIndex, &tpm_pin); + if (r != TSS2_RC_SUCCESS) { + return r; + } + + return r; +} + +TSS2_RC tpm_nv_read(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + char *nvkey, + size_t nvkey_size) +{ + TSS2_RC r; + ESYS_TR nvIndex, session; + TPM2B_MAX_NV_BUFFER *nv_pass = NULL; + + r = tpm_nv_prep(cd, ctx, tpm_nv, pin, pin_size, &nvIndex); + if (r != TSS2_RC_SUCCESS) + return r; + + r = tpm_policy_Read(cd, ctx, tpm_pcr, pcrbanks, &session, NULL); + if (r != TSS2_RC_SUCCESS) { + goto out; + } + Esys_TRSess_SetAttributes(ctx, session, 0, TPMA_SESSION_CONTINUESESSION); + + r = Esys_NV_Read(ctx, nvIndex, nvIndex, session, ESYS_TR_NONE, ESYS_TR_NONE, + nvkey_size, 0, &nv_pass); + if (r != TSS2_RC_SUCCESS) { + r = Esys_FlushContext(ctx, session); + goto out; + } + + if (nvkey_size != nv_pass->size) { + l_err(cd, "VK lengths differ"); + r = TSS2_BASE_RC_BAD_VALUE; + goto out; + } + + memcpy(nvkey, &nv_pass->buffer[0], nvkey_size); +out: + free(nv_pass); + + return r; +} + +TSS2_RC tpm_nv_write(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + const char *buffer, + size_t buffer_size) +{ + TSS2_RC r; + ESYS_TR nvIndex; + TPM2B_MAX_NV_BUFFER nv_pass = { + .size = buffer_size, + .buffer = {} + }; + + if (buffer_size > sizeof(nv_pass.buffer)) + return TPM2_RC_FAILURE; + + memcpy(&nv_pass.buffer[0], buffer, buffer_size); + + r = tpm_nv_prep(cd, ctx, tpm_nv, pin, pin_size, &nvIndex); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_NV_Write(ctx, nvIndex, nvIndex, ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &nv_pass, 0); + + return r; +} + +TSS2_RC tpm_nv_define(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + const char *ownerpw, + size_t ownerpw_size, + size_t nvkey_size) +{ + TSS2_RC r; + ESYS_TR nvIndex; + TPM2B_AUTH tpm_pin = { + .size = pin_size, + .buffer = {} + }; + TPM2B_NV_PUBLIC nvInfo = { + .size = 0, + .nvPublic = { + .nvIndex = tpm_nv, + .nameAlg = TPM2_ALG_SHA256, + .attributes = TPMA_NV_AUTHWRITE | TPMA_NV_POLICYREAD | TPMA_NV_WRITEALL, + .authPolicy = { + .size = 0, + .buffer = {}, + }, + .dataSize = (uint16_t)nvkey_size + } + }; + TPM2B_AUTH ownerauth = { + .size = ownerpw_size, + .buffer={} + }; + + if (nvkey_size > UINT16_MAX) + return TPM2_RC_FAILURE; + + if (pin_size > sizeof(tpm_pin.buffer)) + return TPM2_RC_FAILURE; + + if (pin_size > 0) + memcpy(&tpm_pin.buffer[0], pin, tpm_pin.size); + + if (!daprotect) + nvInfo.nvPublic.attributes |= TPMA_NV_NO_DA; + + if (tpm_nv < 0x01800000 || tpm_nv > 0x01BFFFFF) { + l_err(cd, "NV index handle %08x out of range", tpm_nv); + return TPM2_RC_FAILURE; + } + + if (ownerpw != NULL) { + if (ownerpw_size > sizeof(ownerauth.buffer)) + return TPM2_RC_FAILURE; + memcpy(&ownerauth.buffer[0], ownerpw, ownerauth.size); + + r = Esys_TR_SetAuth(ctx, ESYS_TR_RH_OWNER, &ownerauth); + if (r != TSS2_RC_SUCCESS) + return r; + } + + r = tpm_policy_Read(cd, ctx, tpm_pcr, pcrbanks, NULL, &nvInfo.nvPublic.authPolicy); + if (r != TSS2_RC_SUCCESS) + return r; + + l_dbg(cd, "Defining TPM handle 0x%08x.", tpm_nv); + + r = Esys_NV_DefineSpace(ctx, ESYS_TR_RH_OWNER, ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &tpm_pin, &nvInfo, &nvIndex); + return r; +} + +TSS2_RC tpm_nv_undefine(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t tpm_nv) +{ + TSS2_RC r; + ; + ESYS_TR nvIndex; + + r = tpm_nv_prep(cd, ctx, tpm_nv, NULL, 0, &nvIndex); + if (r != TSS2_RC_SUCCESS) + return r; + + l_dbg(cd, "Deleting TPM handle 0x%08x.", tpm_nv); + + r = Esys_NV_UndefineSpace(ctx, ESYS_TR_RH_OWNER, nvIndex, ESYS_TR_PASSWORD, + ESYS_TR_NONE, ESYS_TR_NONE); + + return r; +} + +TSS2_RC tpm_nv_find(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t *tpm_nv) +{ + TSS2_RC r; + ; + ESYS_TR nvIndex; + TPMS_CAPABILITY_DATA *capabilityData; + int i; + + *tpm_nv = 0; + + r = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_HANDLES, 0x01000000, 0xffff, NULL, &capabilityData); + if (r != TSS2_RC_SUCCESS) + return r; + + for (nvIndex = 0x01BF0000; nvIndex < 0x01BF00FF; nvIndex++) { + for (i = capabilityData->data.handles.count-1; i >= 0; i--) { + if (nvIndex == capabilityData->data.handles.handle[i]) + break; + } + + if (i < 0) { + *tpm_nv = nvIndex; + l_dbg(cd, "Found NV-Index 0x%08x.", nvIndex); + free(capabilityData); + return 0; + } else + l_dbg(cd, "NV-Index 0x%08x already in use.", nvIndex); + } + + free(capabilityData); + return r; +} + +TSS2_RC tpm_nv_exists(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t tpm_nv, bool *exists) +{ + TSS2_RC r; + TPMS_CAPABILITY_DATA *capabilityData; + int i; + + r = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_HANDLES, 0x01000000, 0xffff, NULL, &capabilityData); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Error retrieving TPM capabilities."); + return r; + } + + *exists = false; + for (i = capabilityData->data.handles.count-1; i >= 0; i--) { + if (tpm_nv == capabilityData->data.handles.handle[i]) { + l_dbg(cd, "TPM-NV-Handle 0x%08x does exist.", tpm_nv); + *exists = true; + break; + } + } + + free(capabilityData); + return r; +} + +int tpm_get_random(struct crypt_device *cd, ESYS_CONTEXT *ctx, char *random_bytes, size_t len) +{ + TSS2_RC r; + TPM2B_DIGEST *random_bytes_in; + unsigned int i = 0; + unsigned int j; + unsigned int retries = 5; + + if (len > UINT16_MAX) + return -EINVAL; + + while (retries && i < len) { + /* SHA256 should be supported on all TPM2 chips, so 256 bits + should be minimal supported random length */ + r = Esys_GetRandom(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_SHA256_DIGEST_SIZE, &random_bytes_in); + + if (r != TPM2_RC_SUCCESS) { + retries--; + continue; + } + + for (j = 0; j < random_bytes_in->size && i < len; j++) { + random_bytes[i] = random_bytes_in->buffer[j]; + random_bytes_in->buffer[j] = 0; + i++; + } + + free(random_bytes_in); + } + + if (i < len) { + return -EINVAL; + } + + return 0; +} + +bool pcrs_in_selection(uint32_t required_pcrs, TPMS_PCR_SELECTION *selection) { + uint32_t selected_pcrs = 0; + int i = 0; + + if (!selection) + return false; + + for (i = 0; i < selection->sizeofSelect; i++) + selected_pcrs |= selection->pcrSelect[i] << (i * 8); + + return (selected_pcrs & required_pcrs) == required_pcrs; +} + +TPMS_PCR_SELECTION *tpm2_get_pcrs_by_alg(TPMS_CAPABILITY_DATA *savedPCRs, uint32_t pcrbank) +{ + unsigned i; + const alg_info *info = get_alg_info_by_crypt_id(pcrbank); + + if (!savedPCRs) + return NULL; + if (!info) + return NULL; + + for (i = 0; i < savedPCRs->data.assignedPCR.count; i++) { + if (savedPCRs->data.assignedPCR.pcrSelections[i].hash == info->id) + return &savedPCRs->data.assignedPCR.pcrSelections[i]; + } + + return NULL; +} + +TSS2_RC getPCRsCapability(struct crypt_device *cd, ESYS_CONTEXT *ctx, TPMS_CAPABILITY_DATA **savedPCRs) +{ + TSS2_RC r; + TPMS_CAPABILITY_DATA *currentPCRs; + TPMI_YES_NO more_data; + size_t current_count = 0; + + *savedPCRs = NULL; + + do { + r = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, TPM2_CAP_PCRS, 0, TPM2_NUM_PCR_BANKS, + &more_data, ¤tPCRs); + if (r != TSS2_RC_SUCCESS) { + if (*savedPCRs) + free(*savedPCRs); + goto out; + } + + if (currentPCRs->data.assignedPCR.count + current_count > TPM2_NUM_PCR_BANKS) { + currentPCRs->data.assignedPCR.count = TPM2_NUM_PCR_BANKS - current_count; + } + + if (*savedPCRs) { + memmove(&(*savedPCRs)->data.assignedPCR.pcrSelections[current_count], + currentPCRs->data.assignedPCR.pcrSelections, + currentPCRs->data.assignedPCR.count * sizeof(currentPCRs->data.assignedPCR.pcrSelections[0])); + free(currentPCRs); + current_count += currentPCRs->data.assignedPCR.count; + } else { + *savedPCRs = currentPCRs; + current_count = currentPCRs->data.assignedPCR.count; + } + } while (more_data && current_count < TPM2_NUM_PCR_BANKS); + +out: + return r; +} + +TSS2_RC tpm2_supports_algs_for_pcrs(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t pcrbanks, uint32_t pcrs, bool *supports) +{ + TSS2_RC r = TSS2_RC_SUCCESS; + TPMS_CAPABILITY_DATA *savedPCRs; + TPMS_PCR_SELECTION *supported_pcrs_selection; + unsigned int i = 0; + + r = getPCRsCapability(cd, ctx, &savedPCRs); + if (r != TSS2_RC_SUCCESS) + return r; + + for (i = 0; i < sizeof(pcrbanks) * 8; i++) { + if (pcrbanks & (1 << i)) { + supported_pcrs_selection = tpm2_get_pcrs_by_alg(savedPCRs, pcrbanks & (1 << i)); + if (!supported_pcrs_selection) { + *supports = false; + goto out; + } + if (!pcrs_in_selection(pcrs, supported_pcrs_selection)) { + *supports = false; + goto out; + } + } + } + *supports = true; +out: + free(savedPCRs); + return r; +} diff --git a/tokens/tpm2/utils_tpm2.h b/tokens/tpm2/utils_tpm2.h new file mode 100644 index 00000000..3f3ff0f5 --- /dev/null +++ b/tokens/tpm2/utils_tpm2.h @@ -0,0 +1,149 @@ +/* + * TPM2 utilities for LUKS2 TPM2 type keyslot + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 Milan Broz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UTILS_TPM2_H +#define _UTILS_TPM2_H + +#include +#include +#include +#include +#include + +#include "libcryptsetup_cli.h" + +#define l_std(cd, x...) crypt_logf(cd, CRYPT_LOG_NORMAL, x) +#define l_err(cd, x...) crypt_logf(cd, CRYPT_LOG_ERROR, x) +#define l_dbg(cd, x...) crypt_logf(cd, CRYPT_LOG_DEBUG, x) + +#define LOG_TPM_ERR(cd, r) l_err(cd, "TPM error: %s (code 0x%08x)", Tss2_RC_Decode(r), r) + +/* Flags for activating the PCR banks */ +#define CRYPT_TPM_PCRBANK_SHA1 ((uint32_t) (1 << 0)) +#define CRYPT_TPM_PCRBANK_SHA256 ((uint32_t) (1 << 1)) +#define CRYPT_TPM_PCRBANK_SHA384 ((uint32_t) (1 << 2)) +#define CRYPT_TPM_PCRBANK_SHA512 ((uint32_t) (1 << 3)) +#define CRYPT_TPM_PCRBANK_SM3_256 ((uint32_t) (1 << 4)) +#define CRYPT_TPM_PCRBANK_SHA3_256 ((uint32_t) (1 << 5)) +#define CRYPT_TPM_PCRBANK_SHA3_384 ((uint32_t) (1 << 6)) +#define CRYPT_TPM_PCRBANK_SHA3_512 ((uint32_t) (1 << 7)) + +typedef struct alg_info alg_info; +struct alg_info { + const char *name; + TPM2_ALG_ID id; + uint32_t crypt_id; +}; + +static const alg_info hash_algs[] = { + { .name = "sha1", .id = TPM2_ALG_SHA1, .crypt_id = CRYPT_TPM_PCRBANK_SHA1 }, + { .name = "sha256", .id = TPM2_ALG_SHA256, .crypt_id = CRYPT_TPM_PCRBANK_SHA256 }, + { .name = "sha384", .id = TPM2_ALG_SHA384, .crypt_id = CRYPT_TPM_PCRBANK_SHA384 }, + { .name = "sha512", .id = TPM2_ALG_SHA512, .crypt_id = CRYPT_TPM_PCRBANK_SHA512 }, + { .name = "sm3_256", .id = TPM2_ALG_SM3_256, .crypt_id = CRYPT_TPM_PCRBANK_SM3_256 }, + { .name = "sha3_256", .id = TPM2_ALG_SHA3_256, .crypt_id = CRYPT_TPM_PCRBANK_SHA3_256 }, + { .name = "sha3_384", .id = TPM2_ALG_SHA3_384, .crypt_id = CRYPT_TPM_PCRBANK_SHA3_384 }, + { .name = "sha3_512", .id = TPM2_ALG_SHA3_512, .crypt_id = CRYPT_TPM_PCRBANK_SHA3_512 } +}; + +#define CRYPT_HASH_ALGS_COUNT (sizeof(hash_algs)/sizeof(hash_algs[0])) + +const alg_info *get_alg_info_by_name(const char *name); +const alg_info *get_alg_info_by_id(TPM2_ALG_ID id); +const alg_info *get_alg_info_by_crypt_id(uint32_t crypt_id); + +TSS2_RC tpm_nv_read(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + char *nvkey, + size_t nvkey_size); + + +TSS2_RC tpm_nv_write(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + const char *buffer, + size_t buffer_size); + +TSS2_RC tpm_nv_define(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + const char *ownerpw, + size_t ownerpw_size, + size_t nvkey_size); + +TSS2_RC tpm_init(struct crypt_device *cd, ESYS_CONTEXT **ctx, const char *tcti_conf); + +TSS2_RC tpm_nv_undefine(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t tpm_nv); + +TSS2_RC tpm_nv_find(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t *tpm_nv); + +TSS2_RC tpm_nv_exists(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t tpm_nv, bool *exists); + +int tpm_get_random(struct crypt_device *cd, ESYS_CONTEXT *ctx, char *random_bytes, size_t len); + +/* + * TPM2 token helpers + */ + +int tpm2_token_add(struct crypt_device *cd, + int token, + uint32_t tpm_nv, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + bool pin, + size_t nvkey_size); + +int tpm2_token_read(struct crypt_device *cd, + const char *json, + uint32_t *tpm_nv, + uint32_t *tpm_pcr, + uint32_t *pcrbanks, + bool *daprotect, + bool *pin, + size_t *nvkey_size); + +int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv); + +int tpm2_token_kill(struct crypt_device *cd, ESYS_CONTEXT *ctx, int token); +int tpm2_token_validate(const char *json); + +int tpm2_token_get_pcrbanks(const char *pcrbanks_str, uint32_t *pcrbanks); +int tpm2_token_get_pcrs(const char *pcrs_str, uint32_t *pcrs); +TPMS_PCR_SELECTION *tpm2_get_pcrs_by_alg(TPMS_CAPABILITY_DATA *savedPCRs, uint32_t pcrbank); +TSS2_RC getPCRsCapability(struct crypt_device *cd, ESYS_CONTEXT *ctx, TPMS_CAPABILITY_DATA **savedPCRs); +TSS2_RC tpm2_supports_algs_for_pcrs(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t pcrbanks, uint32_t pcrs, bool *supports); + +#endif /* _UTILS_TPM2_H */ diff --git a/tokens/tpm2/utils_tpm2_json.c b/tokens/tpm2/utils_tpm2_json.c new file mode 100644 index 00000000..1f884c2d --- /dev/null +++ b/tokens/tpm2/utils_tpm2_json.c @@ -0,0 +1,373 @@ +/* + * TPM2 utilities for LUKS2 TPM2 type keyslot + * + * Copyright (C) 2018-2019 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019 Daniel Zatovic + * Copyright (C) 2019 Milan Broz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include "utils_tpm2.h" +#include "libcryptsetup.h" + +int tpm2_token_get_pcrbanks(const char *pcrbanks_str, uint32_t *pcrbanks) +{ + char *p, *s, *orig_s; + int r = 0; + const alg_info *info; + + if (!pcrbanks_str || !pcrbanks) + return -EINVAL; + + s = strdup(pcrbanks_str); + if (!s) + return -ENOMEM; + orig_s = s; + + while ((p = strsep(&s, ","))) { + if (!(info = get_alg_info_by_name(p))) + r = -EINVAL; + else + *pcrbanks |= info->crypt_id; + } + + free(orig_s); + return r; +} + +static void tpm2_token_set_pcrbanks(json_object *jobj, uint32_t pcrbanks) +{ + unsigned i; + + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (pcrbanks & hash_algs[i].crypt_id) { + json_object_array_add(jobj, json_object_new_string(hash_algs[i].name)); + } + } +} + +int tpm2_token_get_pcrs(const char *pcrs_str, uint32_t *pcrs) +{ + char *p, *s, *orig_s; + int i, r = 0; + + if (!pcrs_str || !pcrs) + return -EINVAL; + + s = strdup(pcrs_str); + if (!s) + return -ENOMEM; + orig_s = s; + + while ((p = strsep(&s, ","))) { + if (sscanf(p, "%i", &i) != 1 || i < 0 || i >= 24) { + r = -EINVAL; + break; + } + *pcrs |= 1 << i; + } + + free(orig_s); + return r; +} + +static void tpm2_token_set_pcrs(json_object *jobj, uint32_t pcrselection) +{ + for (int i = 0; i < 32; i++) { + if (pcrselection & (1 << i)) + json_object_array_add(jobj, json_object_new_int64(i)); + } +} + +static bool array_of_type(json_object *jobj, json_type type) +{ + for (int i = 0; i < json_object_array_length(jobj); i++) + if (!json_object_is_type(json_object_array_get_idx(jobj, i), type)) + return false; + return true; +} + +int tpm2_token_validate(const char *json) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj; + int r = -EINVAL; + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_token, "nvindex", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "pcrbanks", &jobj) || + !json_object_is_type(jobj, json_type_array) || + !array_of_type(jobj, json_type_string)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "pcrselection", &jobj) || + !json_object_is_type(jobj, json_type_array) || + !array_of_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "flags", &jobj) || + !json_object_is_type(jobj, json_type_array) || + !array_of_type(jobj, json_type_string)) + goto out; + + r = 0; +out: + json_object_put(jobj_token); + return r; +} + +int tpm2_token_read(struct crypt_device *cd, + const char *json, + uint32_t *tpm_nv, + uint32_t *tpm_pcr, + uint32_t *pcrbanks, + bool *daprotect, + bool *pin, + size_t *nvkey_size) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj, *jobj1; + const char *str; + bool tmp_da = false, tmp_pin = false; + uint32_t tmp_pcrbanks = 0, tmp_pcrselection = 0; + int i, r = -EINVAL; + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_token, "nvindex", &jobj)) + goto out; + if (tpm_nv) + *tpm_nv = (uint32_t)json_object_get_int64(jobj); + + if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj)) + goto out; + if (nvkey_size) + *nvkey_size = (size_t)json_object_get_int64(jobj); + + /* PCR banks */ + if (!json_object_object_get_ex(jobj_token, "pcrbanks", &jobj)) + goto out; + for (tmp_pcrbanks = 0, i = 0; i < json_object_array_length(jobj); i++) { + jobj1 = json_object_array_get_idx(jobj, i); + tpm2_token_get_pcrbanks(json_object_get_string(jobj1), &tmp_pcrbanks); + } + if (pcrbanks) + *pcrbanks = tmp_pcrbanks; + + /* PCR selection */ + if (!json_object_object_get_ex(jobj_token, "pcrselection", &jobj)) + goto out; + for (tmp_pcrselection = 0, i = 0; i < json_object_array_length(jobj); i++) { + jobj1 = json_object_array_get_idx(jobj, i); + tpm2_token_get_pcrs(json_object_get_string(jobj1), &tmp_pcrselection); + } + if (tpm_pcr) + *tpm_pcr = tmp_pcrselection; + + /* flags */ + if (!json_object_object_get_ex(jobj_token, "flags", &jobj)) + goto out; + for (i = 0; i < json_object_array_length(jobj); i++) { + jobj1 = json_object_array_get_idx(jobj, i); + if (!jobj1 || !(str = json_object_get_string(jobj1))) + continue; + else if (!strcmp("DA_PROTECT", str)) + tmp_da = true; + else if (!strcmp("PIN", str)) + tmp_pin = true; + } + if (daprotect) + *daprotect = tmp_da; + if (pin) + *pin = tmp_pin; + + + r = 0; +out: + json_object_put(jobj_token); + return r; +} + +int tpm2_token_add(struct crypt_device *cd, + int token, + uint32_t tpm_nv, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + bool pin, + size_t nvkey_size) +{ + json_object *jobj, *jobj_token; + const char *string_token; + + jobj_token = json_object_new_object(); + if (!jobj_token) + return -EINVAL; + + /* type is mandatory field in all tokens and must match handler name member */ + jobj = json_object_new_string("tpm2"); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "type", jobj); + + jobj = json_object_new_int64(tpm_nv); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "nvindex", jobj); + + jobj = json_object_new_int64(nvkey_size); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "nvkey-size", jobj); + + /* PCR banks */ + jobj = json_object_new_array(); + tpm2_token_set_pcrbanks(jobj, pcrbanks); + json_object_object_add(jobj_token, "pcrbanks", jobj); + + /* PCR selection */ + jobj = json_object_new_array(); + tpm2_token_set_pcrs(jobj, tpm_pcr); + json_object_object_add(jobj_token, "pcrselection", jobj); + + /* flags */ + jobj = json_object_new_array(); + if (daprotect) + json_object_array_add(jobj, json_object_new_string("DA_PROTECT")); + if (pin) + json_object_array_add(jobj, json_object_new_string("PIN")); + json_object_object_add(jobj_token, "flags", jobj); + + /* keyslots */ + jobj = json_object_new_array(); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "keyslots", jobj); + + string_token = json_object_to_json_string_ext(jobj_token, JSON_C_TO_STRING_PLAIN); + if (!string_token) + goto out; + + l_dbg(cd, "Token JSON: %s\n", string_token); + + token = crypt_token_json_set(cd, token, string_token); + if (token < 0) + goto out; + + json_object_put(jobj_token); + return token; +out: + l_err(cd, "Error creating token JSON."); + json_object_put(jobj_token); + return -EINVAL; +} + +static uint32_t token_nvindex(struct crypt_device *cd, int token) +{ + const char *json; + uint32_t nvindex; + + if (crypt_token_json_get(cd, token, &json) < 0) + return 0; + + if (tpm2_token_read(cd, json, &nvindex, NULL, NULL, NULL, NULL, NULL)) + return 0; + + return nvindex; +} + +int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv) +{ + crypt_token_info token_info; + const char *type; + uint32_t nvindex; + int i; + + if (!tpm_nv) + return -EINVAL; + + for (i = 0;; i++) { + token_info = crypt_token_status(cd, i, &type); + + if (token_info == CRYPT_TOKEN_INVALID) + break; + + if (token_info != CRYPT_TOKEN_EXTERNAL || strcmp(type, "tpm2")) + continue; + + nvindex = token_nvindex(cd, i); + if (!nvindex) + continue; // FIXME -EINVAL? + + if (nvindex == tpm_nv) + return i; + } + + return -ENOENT; +} + +int tpm2_token_kill(struct crypt_device *cd, ESYS_CONTEXT *ctx, int token) +{ + uint32_t nvindex; + TSS2_RC r; + + nvindex = token_nvindex(cd, token); + if (!nvindex) + return -EINVAL; + + bool exists; + + r = tpm_nv_exists(cd, ctx, nvindex, &exists); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Failed to check if TPM2 NV-Index 0x%x exists.", nvindex); + LOG_TPM_ERR(cd, r); + return -EINVAL; + } + + if (exists) { + r = tpm_nv_undefine(cd, ctx, nvindex); + if (r) { + l_err(cd, "Failed to undefine TPM2 NV-Index 0x%x.", nvindex); + return -EINVAL; + } + } else { + l_err(cd, "TPM2 NV-Index 0x%x is already deleted.", nvindex); + } + + if (crypt_token_json_set(cd, token, NULL) < 0) { + l_err(cd, "Cannot destroy TPM2 token %d.", token); + return -EINVAL; + } + + return 0; +} From 664a815c0b150fbee6562508741351b99aa258af Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Mon, 2 Mar 2020 12:56:56 +0100 Subject: [PATCH 08/14] Add test for TPM2 plugin --- .travis-functions.sh | 6 ++ .travis.yml | 2 + tests/Makefile.am | 7 +- tests/tpm2-plugin-test | 148 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100755 tests/tpm2-plugin-test diff --git a/.travis-functions.sh b/.travis-functions.sh index b21a9335..e8e1ba62 100644 --- a/.travis-functions.sh +++ b/.travis-functions.sh @@ -18,6 +18,7 @@ DUMP_CONFIG_LOG="short" export TS_OPT_parsable="yes" export RUN_SSH_PLUGIN_TEST=1 +export RUN_TPM2_PLUGIN_TEST=1 function configure_travis { @@ -43,6 +44,7 @@ function check_nonroot --enable-external-tokens \ --enable-external-cli-tokens \ --enable-ssh-token \ + --enable-tpm2-token \ "$cfg_opts" \ || return @@ -63,6 +65,7 @@ function check_root --enable-external-tokens \ --enable-external-cli-tokens \ --enable-ssh-token \ + --enable-tpm2-token \ "$cfg_opts" \ || return @@ -115,6 +118,9 @@ function travis_install_script linux-modules-extra-$(uname -r) \ libssh-dev \ sshpass \ + tpm2-tools \ + libtss2-dev \ + swtpm \ || return # For VeraCrypt test diff --git a/.travis.yml b/.travis.yml index adf612be..5efe6636 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,8 @@ before_install: - export CC=`which $CC` # workaround travis-ci issue #5301 - unset PYTHON_CFLAGS + # swtpm + - sudo add-apt-repository -y ppa:stefanberger/swtpm-focal install: - source ./.travis-functions.sh diff --git a/tests/Makefile.am b/tests/Makefile.am index 5328dcde..5afc78ee 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -37,7 +37,11 @@ if CLI_TOKENS if SSHPLUGIN_TOKEN TESTS += ssh-plugin-test endif +if TPM2_TOKEN +TESTS += tpm2-plugin-test endif +endif + EXTRA_DIST = compatimage.img.xz compatv10image.img.xz \ compatimage2.img.xz \ @@ -79,7 +83,8 @@ EXTRA_DIST = compatimage.img.xz compatv10image.img.xz \ Makefile.localtest \ bitlk-compat-test \ bitlk-images.tar.xz \ - ssh-plugin-test + ssh-plugin-test \ + tpm2-plugin-test CLEANFILES = cryptsetup-tst* valglog* *-fail-*.log clean-local: diff --git a/tests/tpm2-plugin-test b/tests/tpm2-plugin-test new file mode 100755 index 00000000..f17caf25 --- /dev/null +++ b/tests/tpm2-plugin-test @@ -0,0 +1,148 @@ +#!/bin/bash + +[ -z "$CRYPTSETUP_PATH" ] && CRYPTSETUP_PATH=".." +CRYPTSETUP=$CRYPTSETUP_PATH/cryptsetup +MNT_DIR="./mnt_luks_tpm" +DEV_NAME="dummy" +PWD="93R4P4pIqAH8" +TPM_PWD="mymJeD8ivEhE" +FAST_PBKDF_OPT="--pbkdf pbkdf2 --pbkdf-force-iterations 1000" +TPM_PCR=8 +TPM_STATE_DIR="./tpm_state" +TCTI_ARG="" + +cleanup() { + [ -b /dev/mapper/$DEV_NAME ] && dmsetup remove --retry $DEV_NAME + udevadm settle >/dev/null 2>&1 + if [ -d "$MNT_DIR" ] ; then + umount -f $MNT_DIR 2>/dev/null + rmdir $MNT_DIR 2>/dev/null + fi + + pkill swtpm + sleep 2 + + if [ -d "$TPM_STATE_DIR" ] ; then + rm -rf $TPM_STATE_DIR 2>/dev/null + fi +} + +fail() +{ + [ -n "$1" ] && echo "FAIL $1" + echo "FAILED backtrace:" + while caller $frame; do ((frame++)); done + cleanup + exit 100 +} + + +format() # format +{ + dd if=/dev/zero of=$DEV bs=1M count=32 >/dev/null 2>&1 + + echo $CRYPTSETUP luksFormat --type luks2 $DEV -q $FAST_PBKDF_OPT -c aes-cbc-essiv:sha256 + echo $PWD | $CRYPTSETUP luksFormat --type luks2 $DEV -q $FAST_PBKDF_OPT -c aes-cbc-essiv:sha256 + [ $? -ne 0 ] && fail "Format failed." +} + +skip() +{ + echo "TEST SKIPPED: $1" + cleanup + exit 77 +} + +function bin_check() +{ + which $1 >/dev/null 2>&1 || skip "WARNING: test require $1 binary, test skipped." +} + +test_sealing() +{ + if [ "`tpm2_getcap pcrs | grep $1 | cut -d "[" -f2 | cut -d "]" -f1 | grep " $TPM_PCR,"`" = "" ]; then + echo "PCR $TPM_PCR doesn't support $1 hash algorithm" + + echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP --plugin tpm2 --plugin-tpm2-pcr=$TPM_PCR --plugin-tpm2-bank=$1 token add $DEV + [ $? -eq 0 ] && fail "Successfully sealed to PCR $TPM_PCR using $1 hash algorithm even though it is not supported by TPM." + + return 77 + fi + + echo "TPM supports $1" + + echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP --plugin tpm2 $TCTI_ARG --plugin-tpm2-pcr=$TPM_PCR --plugin-tpm2-bank=$1 token add $DEV + [ $? -ne 0 ] && fail "Failed to add TPM2 token sealed to PCR." + + echo $TPM_PWD | $CRYPTSETUP --token-only open $DEV $DEV_NAME + [ $? -ne 0 ] && fail "Failed open device by TPM2 token sealed to PCR." + + if [ $1 = "sha1" ]; then + tpm2_pcrextend 8:sha1=`printf '0%.0s' {1..40}` + fi + + if [ $1 = "sha256" ]; then + tpm2_pcrextend 8:sha256=`printf '0%.0s' {1..64}` + fi + + if [ $1 = "sha512" ]; then + tpm2_pcrextend 8:sha512=`printf '0%.0s' {1..128}` + fi + + echo $TPM_PWD | $CRYPTSETUP --token-only open $DEV $DEV_NAME + [ $? -eq 0 ] && fail "Opened device by TPM2 token sealed to PCR even though registers have been changed." + + $CRYPTSETUP close $DEV_NAME || fail + + echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP --plugin tpm2 --token-id=0 token remove $DEV + [ $? -ne 0 ] && fail "Failed to remove TPM2 token sealed to PCR." +} + +[ $(id -u) != 0 ] && skip "WARNING: You must be root to run this test, test skipped." + +# Prevent running TPM operations by default +[ -z "$RUN_TPM2_PLUGIN_TEST" ] && skip "WARNING: Variable RUN_TPM2_PLUGIN_TEST must be defined, test skipped." + +bin_check tpm2_getcap +bin_check tpm2_pcrextend +[[ $USE_REAL_TPM -ne 1 ]] && bin_check swtpm + +if [ ! -e /dev/vtpmx ]; then + modprobe tpm_vtpm_proxy || skip "Cannot load tpm_vtpm_proxy module." +fi + +[ ! -d $MNT_DIR ] && mkdir $MNT_DIR +[ ! -d $TPM_STATE_DIR ] && mkdir $TPM_STATE_DIR + +if [[ $USE_REAL_TPM -ne 1 ]]; then + swtpm chardev --tpm2 --vtpm-proxy --tpmstate dir=$TPM_STATE_DIR > $TPM_STATE_DIR/log& + sleep 1 + TPM_DEVICE_NUMBER=$(cat $TPM_STATE_DIR/log | head -n 1 | grep -o -E '/dev/tpm[0-9]+' | grep -E -o '[0-9]+$') + TCTI_ARG=$(echo --plugin-tpm2-tcti="device:/dev/tpmrm$TPM_DEVICE_NUMBER") +fi + +echo "[1] Using tmpfs for image" +DEV="$MNT_DIR/test.img" +mount -t tmpfs none $MNT_DIR || skip "Mounting tmpfs not available." +format + +# basic test +echo "Adding TPM2 token" +echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP --plugin tpm2 token add $DEV $TCTI_ARG +[ $? -ne 0 ] && fail "Failed to add TPM2 token." + +echo "Opening using TPM2 token" +echo $TPM_PWD | $CRYPTSETUP --token-only open $DEV $DEV_NAME +[ $? -ne 0 ] && fail "Failed open device by TPM2 token." + +$CRYPTSETUP close $DEV_NAME || fail + +echo "Removing TPM2 token" +echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP --plugin tpm2 token remove $DEV --token-id=0 +[ $? -ne 0 ] && fail "Failed to remove TPM2 token." + +test_sealing sha1 +test_sealing sha256 +test_sealing sha512 + +cleanup From 43859a45e8d2f58ba8b8851b08180292f76d2e29 Mon Sep 17 00:00:00 2001 From: Ondrej Kozina Date: Wed, 11 Nov 2020 15:01:28 +0100 Subject: [PATCH 09/14] WIP: Fix use after free() bug in utils_tpm2. --- tokens/tpm2/utils_tpm2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokens/tpm2/utils_tpm2.c b/tokens/tpm2/utils_tpm2.c index 89300e67..d521abcc 100644 --- a/tokens/tpm2/utils_tpm2.c +++ b/tokens/tpm2/utils_tpm2.c @@ -629,8 +629,8 @@ TSS2_RC getPCRsCapability(struct crypt_device *cd, ESYS_CONTEXT *ctx, TPMS_CAPAB memmove(&(*savedPCRs)->data.assignedPCR.pcrSelections[current_count], currentPCRs->data.assignedPCR.pcrSelections, currentPCRs->data.assignedPCR.count * sizeof(currentPCRs->data.assignedPCR.pcrSelections[0])); - free(currentPCRs); current_count += currentPCRs->data.assignedPCR.count; + free(currentPCRs); } else { *savedPCRs = currentPCRs; current_count = currentPCRs->data.assignedPCR.count; From 16764a04d8dd6c08f66bc75af45fafc9a2f2c3ac Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Wed, 23 Sep 2020 22:31:43 +0200 Subject: [PATCH 10/14] WIP: Store device identification to NV index. --- tokens/tpm2/libcryptsetup-token-tpm2.c | 171 ++++++++++++++++++------- tokens/tpm2/utils_tpm2.c | 63 ++++++++- tokens/tpm2/utils_tpm2.h | 13 ++ tokens/tpm2/utils_tpm2_json.c | 54 +++++++- 4 files changed, 245 insertions(+), 56 deletions(-) diff --git a/tokens/tpm2/libcryptsetup-token-tpm2.c b/tokens/tpm2/libcryptsetup-token-tpm2.c index c1a87e85..e02b0ee3 100644 --- a/tokens/tpm2/libcryptsetup-token-tpm2.c +++ b/tokens/tpm2/libcryptsetup-token-tpm2.c @@ -22,6 +22,7 @@ */ #include +#include #include #include #include @@ -33,6 +34,7 @@ #define TOKEN_NAME "tpm2" #define DEFAULT_TPM2_SIZE 64 #define DEFAULT_PCR_BANK "sha256" +#define MAX_TPM_COUNT "sha256" #define NV_ARG "plugin-tpm2-nv" #define PCR_ARG "plugin-tpm2-pcr" @@ -46,21 +48,25 @@ #define REMOVE_VALID (1 << 2) #define REMOVED (1 << 3) +#define TPMS_NO_LIMIT 100 +#define TPMS_MAX_DIGITS 2 // TPM no. 0-99 + static void tpm2_token_dump(struct crypt_device *cd, const char *json) { - uint32_t nvindex, pcrs, pcrbanks; + uint32_t nvindex, uuid_nvindex, pcrs, pcrbanks; size_t nvkey_size; bool daprotect, pin; char buf[1024], num[32]; unsigned i, n; - if (tpm2_token_read(cd, json, &nvindex, &pcrs, &pcrbanks, + if (tpm2_token_read(cd, json, &nvindex, &uuid_nvindex, &pcrs, &pcrbanks, &daprotect, &pin, &nvkey_size)) { l_err(cd, "Cannot read JSON token metadata."); return; } l_std(cd, "\tNVindex: 0x%08" PRIx32 "\n", nvindex); + l_std(cd, "\tUUID NVindex: 0x%08" PRIx32 "\n", uuid_nvindex); l_std(cd, "\tNVKey: %zu [bytes]\n", nvkey_size); for (*buf = '\0', n = 0, i = 0; i < 32; i++) { @@ -97,23 +103,26 @@ static void tpm2_token_dump(struct crypt_device *cd, const char *json) l_std(cd, "\tflags: %s\n", buf); } -static int tpm2_token_open_pin(struct crypt_device *cd, +static int tpm2_token_open_pin_with_tcti(struct crypt_device *cd, int token, const char *tpm_pass, char **buffer, size_t *buffer_len, - void *usrptr) + void *usrptr, + const char *tcti_spec) { int r; TSS2_RC tpm_rc; ESYS_CONTEXT *ctx; - uint32_t nvindex, pcrselection, pcrbanks; + uint32_t nvindex, uuid_nvindex, pcrselection, pcrbanks; size_t nvkey_size; bool daprotect, pin; const char *json; + size_t uuid_len; + char *uuid_buffer = NULL; - if (tpm_init(cd, &ctx, NULL) != TSS2_RC_SUCCESS) - return -EINVAL; + if (tpm_init(cd, &ctx, tcti_spec) != TSS2_RC_SUCCESS) + return -EACCES; r = crypt_token_json_get(cd, token, &json); if (r < 0) { @@ -121,7 +130,7 @@ static int tpm2_token_open_pin(struct crypt_device *cd, goto out; } - r = tpm2_token_read(cd, json, &nvindex, &pcrselection, &pcrbanks, + r = tpm2_token_read(cd, json, &nvindex, &uuid_nvindex, &pcrselection, &pcrbanks, &daprotect, &pin, &nvkey_size); if (r < 0) { l_err(cd, "Cannot read JSON token metadata."); @@ -136,14 +145,37 @@ static int tpm2_token_open_pin(struct crypt_device *cd, goto out; } + uuid_len = strlen(crypt_get_uuid(cd)); + uuid_buffer = calloc(1, uuid_len); + if (!uuid_buffer) { + r = -ENOMEM; + goto out; + } + + tpm_rc = tpm_nv_read(cd, ctx, uuid_nvindex, NULL, 0, 0, CRYPT_TPM_PCRBANK_SHA1, uuid_buffer, uuid_len); + if (tpm_rc != TPM2_RC_SUCCESS) { + l_err(cd, "Failed to read UUID NV index, this TPM doesn't seem to hold the passphrase."); + LOG_TPM_ERR(cd, tpm_rc); + r = -EINVAL; + goto out; + } + + if (strncmp(crypt_get_uuid(cd), uuid_buffer, uuid_len)) { + l_err(cd, "Bad UUID NV index content, this TPM doesn't hold the passphrase."); + l_dbg(cd, "Real UUID: '%s'", crypt_get_uuid(cd)); + l_dbg(cd, "Read UUID: '%s'", uuid_buffer); + r = -EINVAL; + goto out; + } + *buffer = malloc(nvkey_size); - if (!*buffer) { + if (!(*buffer)) { r = -ENOMEM; goto out; } *buffer_len = nvkey_size; - r = -EINVAL; + r = -EACCES; tpm_rc = tpm_nv_read(cd, ctx, nvindex, tpm_pass, tpm_pass ? strlen(tpm_pass) : 0, pcrselection, pcrbanks, *buffer, nvkey_size); @@ -152,15 +184,71 @@ static int tpm2_token_open_pin(struct crypt_device *cd, r = 0; } else if (tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_BAD_AUTH) || tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_AUTH_FAIL)) { + l_err(cd, "Failed to read passphrase NV index."); LOG_TPM_ERR(cd, tpm_rc); r = -EPERM; } out: + if (uuid_buffer) + free(uuid_buffer); Esys_Finalize(&ctx); return r; } +static int tpm2_token_open_pin(struct crypt_device *cd, + int token, + const char *tpm_pass, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + int access_error, i; + + l_std(cd, "Trying to activate with TPM backend: 'tabrmd'\n"); + int r = tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, "tabrmd"); + if (r != -EINVAL) { + return r; + } + + const size_t tcti_name_len = strlen("device:/dev/tpmrm") + TPMS_MAX_DIGITS + 1; + char tcti_conf[tcti_name_len]; + + for (i = 0; i < TPMS_NO_LIMIT; i++) { + snprintf(tcti_conf, tcti_name_len, "device:/dev/tpmrm%d", i); + l_dbg(cd, "Checking TPM device: '%s'\n", tcti_conf + 7); + access_error = access(tcti_conf + 7, R_OK | W_OK); + if (access_error) { + l_dbg(cd, "Device does not exist", tcti_conf); + break; + } + l_dbg(cd, "Device exists, trying to activate...", tcti_conf); + l_std(cd, "Trying to activate with TPM backend: '%s'\n", tcti_conf); + int r = tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, tcti_conf); + if (r != -EINVAL) { + return r; + } + } + + for (i = 0; i < TPMS_NO_LIMIT; i++) { + snprintf(tcti_conf, tcti_name_len, "device:/dev/tpm%d", i); + l_dbg(cd, "Checking TPM device: '%s'\n", tcti_conf + 7); + access_error = access(tcti_conf + 7, R_OK | W_OK); + if (access_error) { + l_dbg(cd, "Device does not exist", tcti_conf); + break; + } + l_dbg(cd, "Device exists, trying to activate...", tcti_conf); + l_std(cd, "Trying to activate with TPM backend: '%s'\n", tcti_conf); + int r = tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, tcti_conf); + if (r != -EINVAL) { + return r; + } + } + + return -EINVAL; +} + static int tpm2_token_open(struct crypt_device *cd, int token, char **buffer, @@ -180,6 +268,7 @@ struct tpm2_context { const char *tcti_str; uint32_t tpmbanks; uint32_t tpmnv; + uint32_t tpmuuid_nv; uint32_t tpmpcrs; uint32_t pass_size; ESYS_CONTEXT *ctx; @@ -332,7 +421,7 @@ static int get_create_cli_args(struct crypt_device *cd, struct tpm2_context *tc) } if (crypt_cli_arg_set(tc->cli, TCTI_ARG)) { - r = plugin_get_arg_value(cd, tc->cli, BANK_ARG, CRYPT_ARG_STRING, &tc->tcti_str); + r = plugin_get_arg_value(cd, tc->cli, TCTI_ARG, CRYPT_ARG_STRING, &tc->tcti_str); if (r) return r; } @@ -437,56 +526,31 @@ int crypt_token_create(struct crypt_device *cd, void *handle) goto out; } - if (tc->tpmnv == 0) { - tpm_rc = tpm_nv_find(cd, tc->ctx, &tc->tpmnv); - if (tpm_rc != TSS2_RC_SUCCESS) { - l_err(cd, "Error while trying to find free NV index."); - LOG_TPM_ERR(cd, tpm_rc); - r = -EINVAL; - goto out; - } - - if (!tc->tpmnv) { - l_err(cd, "Error no free TPM NV-Index found."); - r = -EACCES; - goto out; - } - } - - tpm_rc = tpm_nv_define(cd, tc->ctx, tc->tpmnv, tpm_pin, tpm_pin_len, tc->tpmpcrs, - tc->tpmbanks, tc->tpmdaprotect, NULL, 0, tc->pass_size); - if (tpm_rc != TSS2_RC_SUCCESS) { - l_err(cd, "TPM NV-Index definition failed"); - LOG_TPM_ERR(cd, tpm_rc); - r = -EINVAL; + r = tpm_nv_find_and_write(cd, tc->ctx, &tc->tpmnv, random_pass, tc->pass_size, tpm_pin, tpm_pin_len, tc->tpmbanks, tc->tpmpcrs, tc->tpmdaprotect); + if (r < 0) { + l_err(cd, "Failed to write passphrase to an NV index."); goto out; } - tpm_rc = tpm_nv_write(cd, tc->ctx, tc->tpmnv, tpm_pin, tpm_pin_len, - random_pass, tc->pass_size); - if (tpm_rc != TSS2_RC_SUCCESS) { - l_err(cd, "TPM NV-Index write error."); - LOG_TPM_ERR(cd, tpm_rc); - tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); - r = -EINVAL; - goto out; + + r = tpm_nv_find_and_write(cd, tc->ctx, &tc->tpmuuid_nv, crypt_get_uuid(cd), strlen(crypt_get_uuid(cd)), NULL, 0,CRYPT_TPM_PCRBANK_SHA1, 0, false); + if (r < 0) { + l_err(cd, "Failed to write disk UUID to an NV index."); + goto err_pass_nv_defined; } r = crypt_keyslot_add_by_passphrase(cd, tc->keyslot, existing_pass, existing_pass_len, random_pass, tc->pass_size); if (r < 0) { if (r == -EPERM) l_err(cd, "Wrong LUKS2 passphrase supplied."); - tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); - goto out; + goto err_uuid_nv_defined; } tc->keyslot = r; l_std(cd, "Using keyslot %d.\n", tc->keyslot); - r = tpm2_token_add(cd, tc->token, tc->tpmnv, tc->tpmpcrs, tc->tpmbanks, tc->tpmdaprotect, !tc->no_tpm_pin, tc->pass_size); + r = tpm2_token_add(cd, tc->token, tc->tpmnv, tc->tpmuuid_nv, tc->tpmpcrs, tc->tpmbanks, tc->tpmdaprotect, !tc->no_tpm_pin, tc->pass_size); if (r < 0) { - tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); - crypt_keyslot_destroy(cd, tc->keyslot); - goto out; + goto err_keyslot_created; } tc->token = r; l_std(cd, "Token: %d\n", tc->token); @@ -494,15 +558,24 @@ int crypt_token_create(struct crypt_device *cd, void *handle) r = crypt_token_assign_keyslot(cd, tc->token, tc->keyslot); if (r < 0) { l_err(cd, "Failed to assign keyslot %d to token %d.", tc->keyslot, tc->token); - tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); - crypt_keyslot_destroy(cd, tc->keyslot); crypt_token_json_set(cd, tc->token, NULL); + goto err_keyslot_created; } if (r > 0) { r = 0; tc->status |= CREATED; } + goto out; + +err_keyslot_created: + crypt_keyslot_destroy(cd, tc->keyslot); +err_uuid_nv_defined: + tpm_nv_undefine(cd, tc->ctx, tc->tpmuuid_nv); + tc->tpmuuid_nv = 0; +err_pass_nv_defined: + tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); + tc->tpmnv = 0; out: crypt_safe_free(random_pass); crypt_safe_free(existing_pass); diff --git a/tokens/tpm2/utils_tpm2.c b/tokens/tpm2/utils_tpm2.c index d521abcc..dbe35da5 100644 --- a/tokens/tpm2/utils_tpm2.c +++ b/tokens/tpm2/utils_tpm2.c @@ -71,18 +71,20 @@ const alg_info *get_alg_info_by_crypt_id(uint32_t crypt_id) { TSS2_RC tpm_init(struct crypt_device *cd, ESYS_CONTEXT **ctx, const char *tcti_conf) { TSS2_RC r; - l_dbg(cd, "Initializing ESYS connection"); + l_dbg(cd, "Initializing ESYS connection vith TCTI '%s'", tcti_conf); TSS2_TCTI_CONTEXT *tcti_ctx = NULL; r = Tss2_TctiLdr_Initialize (tcti_conf, &tcti_ctx); if (r != TSS2_RC_SUCCESS) { l_err(cd, "Error initializing TCTI"); + LOG_TPM_ERR(cd, r); return r; } r = Esys_Initialize(ctx, tcti_ctx, NULL); if (r != TSS2_RC_SUCCESS) { l_err(cd, "Error initializing ESYS"); + LOG_TPM_ERR(cd, r); return r; } @@ -94,6 +96,7 @@ TSS2_RC tpm_init(struct crypt_device *cd, ESYS_CONTEXT **ctx, const char *tcti_c if (r != TSS2_RC_SUCCESS) { l_err(cd, "TPM StartUp command failed"); + LOG_TPM_ERR(cd, r); Esys_Finalize(ctx); } @@ -200,8 +203,8 @@ static TSS2_RC tpm_policy_Read(struct crypt_device *cd, }; unsigned int i; - if (pcrbanks == 0) { - l_err(cd, "No banks selected."); + if (tpm_pcr && !pcrbanks) { + l_err(cd, "You have to also select banks when you select PCRs."); return TSS2_BASE_RC_BAD_SIZE; } @@ -269,6 +272,58 @@ static TSS2_RC tpm_policy_Read(struct crypt_device *cd, return TSS2_RC_SUCCESS; } +int tpm_nv_find_and_write(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t *tpmnv, + const char *buffer, + size_t buffer_size, + char *tpm_pin, + size_t tpm_pin_len, + uint32_t tpmbanks, + uint32_t tpmpcrs, + bool tpmdaprotect) +{ + TSS2_RC tpm_rc; + + if (!tpmnv) { + return -EINVAL; + } + + if (!(*tpmnv)) { + tpm_rc = tpm_nv_find(cd, ctx, tpmnv); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "Error while trying to find free NV index."); + LOG_TPM_ERR(cd, tpm_rc); + return -EACCES; + } + } + + if (!(*tpmnv)) { + l_err(cd, "Error no free TPM NV-Index found."); + return -ENOMEM; + } + + tpm_rc = tpm_nv_define(cd, ctx, *tpmnv, tpm_pin, tpm_pin_len, tpmpcrs, + tpmbanks, tpmdaprotect, NULL, 0, buffer_size); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "TPM NV-Index definition failed"); + LOG_TPM_ERR(cd, tpm_rc); + return -EINVAL; + } + + tpm_rc = tpm_nv_write(cd, ctx, *tpmnv, tpm_pin, tpm_pin_len, + buffer, buffer_size); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "TPM NV-Index write error."); + LOG_TPM_ERR(cd, tpm_rc); + tpm_nv_undefine(cd, ctx, *tpmnv); + *tpmnv = 0; + return -EACCES; + } + + return 0; +} + static TSS2_RC tpm_nv_prep(struct crypt_device *cd, ESYS_CONTEXT *ctx, uint32_t tpm_nv, @@ -423,7 +478,7 @@ TSS2_RC tpm_nv_define(struct crypt_device *cd, if (pin_size > sizeof(tpm_pin.buffer)) return TPM2_RC_FAILURE; - if (pin_size > 0) + if (pin && pin_size > 0) memcpy(&tpm_pin.buffer[0], pin, tpm_pin.size); if (!daprotect) diff --git a/tokens/tpm2/utils_tpm2.h b/tokens/tpm2/utils_tpm2.h index 3f3ff0f5..57dcdfee 100644 --- a/tokens/tpm2/utils_tpm2.h +++ b/tokens/tpm2/utils_tpm2.h @@ -120,6 +120,7 @@ int tpm_get_random(struct crypt_device *cd, ESYS_CONTEXT *ctx, char *random_byte int tpm2_token_add(struct crypt_device *cd, int token, uint32_t tpm_nv, + uint32_t tpm_uuid_nv, uint32_t tpm_pcr, uint32_t pcrbanks, bool daprotect, @@ -129,12 +130,24 @@ int tpm2_token_add(struct crypt_device *cd, int tpm2_token_read(struct crypt_device *cd, const char *json, uint32_t *tpm_nv, + uint32_t *tpmuuid_nv, uint32_t *tpm_pcr, uint32_t *pcrbanks, bool *daprotect, bool *pin, size_t *nvkey_size); +int tpm_nv_find_and_write(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t *tpmnv, + const char *buffer, + size_t buffer_size, + char *tpm_pin, + size_t tpm_pin_len, + uint32_t tpmbanks, + uint32_t tpmpcrs, + bool tpmdaprotect); + int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv); int tpm2_token_kill(struct crypt_device *cd, ESYS_CONTEXT *ctx, int token); diff --git a/tokens/tpm2/utils_tpm2_json.c b/tokens/tpm2/utils_tpm2_json.c index 1f884c2d..1d55de8d 100644 --- a/tokens/tpm2/utils_tpm2_json.c +++ b/tokens/tpm2/utils_tpm2_json.c @@ -147,6 +147,7 @@ int tpm2_token_validate(const char *json) int tpm2_token_read(struct crypt_device *cd, const char *json, uint32_t *tpm_nv, + uint32_t *tpmuuid_nv, uint32_t *tpm_pcr, uint32_t *pcrbanks, bool *daprotect, @@ -169,6 +170,11 @@ int tpm2_token_read(struct crypt_device *cd, if (tpm_nv) *tpm_nv = (uint32_t)json_object_get_int64(jobj); + if (!json_object_object_get_ex(jobj_token, "uuid-nvindex", &jobj)) + goto out; + if (tpmuuid_nv) + *tpmuuid_nv = (uint32_t)json_object_get_int64(jobj); + if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj)) goto out; if (nvkey_size) @@ -221,6 +227,7 @@ int tpm2_token_read(struct crypt_device *cd, int tpm2_token_add(struct crypt_device *cd, int token, uint32_t tpm_nv, + uint32_t tpm_uuid_nv, uint32_t tpm_pcr, uint32_t pcrbanks, bool daprotect, @@ -245,6 +252,11 @@ int tpm2_token_add(struct crypt_device *cd, goto out; json_object_object_add(jobj_token, "nvindex", jobj); + jobj = json_object_new_int64(tpm_uuid_nv); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "uuid-nvindex", jobj); + jobj = json_object_new_int64(nvkey_size); if (!jobj) goto out; @@ -300,12 +312,26 @@ static uint32_t token_nvindex(struct crypt_device *cd, int token) if (crypt_token_json_get(cd, token, &json) < 0) return 0; - if (tpm2_token_read(cd, json, &nvindex, NULL, NULL, NULL, NULL, NULL)) + if (tpm2_token_read(cd, json, &nvindex, NULL, NULL, NULL, NULL, NULL, NULL)) return 0; return nvindex; } +static uint32_t token_uuid_nvindex(struct crypt_device *cd, int token) +{ + const char *json; + uint32_t uuid_nvindex; + + if (crypt_token_json_get(cd, token, &json) < 0) + return 0; + + if (tpm2_token_read(cd, json, NULL, &uuid_nvindex, NULL, NULL, NULL, NULL, NULL)) + return 0; + + return uuid_nvindex; +} + int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv) { crypt_token_info token_info; @@ -338,18 +364,40 @@ int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv) int tpm2_token_kill(struct crypt_device *cd, ESYS_CONTEXT *ctx, int token) { - uint32_t nvindex; + uint32_t nvindex, uuid_nvindex; TSS2_RC r; + uuid_nvindex = token_uuid_nvindex(cd, token); + if (!uuid_nvindex) + return -EINVAL; + nvindex = token_nvindex(cd, token); if (!nvindex) return -EINVAL; bool exists; + r = tpm_nv_exists(cd, ctx, uuid_nvindex, &exists); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Failed to check if UUID TPM NV-Index 0x%x exists.", uuid_nvindex); + LOG_TPM_ERR(cd, r); + return -EINVAL; + } + + if (exists) { + r = tpm_nv_undefine(cd, ctx, uuid_nvindex); + if (r) { + l_err(cd, "Failed to undefine UUID TPM NV-Index 0x%x.", nvindex); + return -EINVAL; + } + } else { + l_err(cd, "UUID TPM NV-Index 0x%x is already deleted.", nvindex); + } + + r = tpm_nv_exists(cd, ctx, nvindex, &exists); if (r != TSS2_RC_SUCCESS) { - l_err(cd, "Failed to check if TPM2 NV-Index 0x%x exists.", nvindex); + l_err(cd, "Failed to check if TPM passphrase NV-Index 0x%x exists.", nvindex); LOG_TPM_ERR(cd, r); return -EINVAL; } From 3146a88e66ab7d9ea31ba676046ce264b685c4af Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Wed, 30 Sep 2020 23:26:37 +0200 Subject: [PATCH 11/14] WIP: Rework TCTI scanning, fix remove action. --- tokens/tpm2/libcryptsetup-token-tpm2.c | 165 +++++++++++++++++++++---- 1 file changed, 139 insertions(+), 26 deletions(-) diff --git a/tokens/tpm2/libcryptsetup-token-tpm2.c b/tokens/tpm2/libcryptsetup-token-tpm2.c index e02b0ee3..2e3f34e3 100644 --- a/tokens/tpm2/libcryptsetup-token-tpm2.c +++ b/tokens/tpm2/libcryptsetup-token-tpm2.c @@ -42,6 +42,7 @@ #define DAPROTECT_ARG "plugin-tpm2-daprotect" #define NOPIN_ARG "plugin-tpm2-no-pin" #define TCTI_ARG "plugin-tpm2-tcti" +#define FORCE_REMOVE_ARG "plugin-tpm2-force-remove" #define CREATE_VALID (1 << 0) #define CREATED (1 << 1) @@ -196,23 +197,84 @@ static int tpm2_token_open_pin_with_tcti(struct crypt_device *cd, return r; } -static int tpm2_token_open_pin(struct crypt_device *cd, +static bool tpm2_verify_tcti_for_token(struct crypt_device *cd, int token, - const char *tpm_pass, - char **buffer, - size_t *buffer_len, - void *usrptr) + const char *tcti_spec) { - int access_error, i; + int r; + bool is_valid = true; + const char *json; + uint32_t nvindex, uuid_nvindex; + size_t uuid_len; + char *uuid_buffer = NULL; - l_std(cd, "Trying to activate with TPM backend: 'tabrmd'\n"); - int r = tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, "tabrmd"); - if (r != -EINVAL) { - return r; + TSS2_RC tpm_rc; + ESYS_CONTEXT *ctx; + + if (tpm_init(cd, &ctx, tcti_spec) != TSS2_RC_SUCCESS) + return false; + + r = crypt_token_json_get(cd, token, &json); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + is_valid = false; + goto out; } + r = tpm2_token_read(cd, json, &nvindex, &uuid_nvindex, NULL, NULL, + NULL, NULL, NULL); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + is_valid = false; + goto out; + } + + uuid_len = strlen(crypt_get_uuid(cd)); + uuid_buffer = calloc(1, uuid_len); + if (!uuid_buffer) { + is_valid = false; + goto out; + } + + tpm_rc = tpm_nv_read(cd, ctx, uuid_nvindex, NULL, 0, 0, CRYPT_TPM_PCRBANK_SHA1, uuid_buffer, uuid_len); + if (tpm_rc != TPM2_RC_SUCCESS) { + l_dbg(cd, "Failed to read UUID NV index, this TPM doesn't seem to hold the passphrase."); + LOG_TPM_ERR(cd, tpm_rc); + is_valid = false; + goto out; + } + + if (strncmp(crypt_get_uuid(cd), uuid_buffer, uuid_len)) { + l_dbg(cd, "Bad UUID NV index content, this TPM doesn't hold the passphrase."); + l_dbg(cd, "Real UUID: '%s'", crypt_get_uuid(cd)); + l_dbg(cd, "TPM-stored UUID: '%s'", uuid_buffer); + is_valid = false; + goto out; + } +out: + if (uuid_buffer) { + free(uuid_buffer); + } + + Esys_Finalize(&ctx); + return is_valid; +} + +static char *tpm2_find_tcti_for_token(struct crypt_device *cd, + int token) +{ + int access_error, i; + const size_t tcti_name_len = strlen("device:/dev/tpmrm") + TPMS_MAX_DIGITS + 1; - char tcti_conf[tcti_name_len]; + char *tcti_conf = malloc(tcti_name_len); + if (!tcti_conf) + return NULL; + + strcpy(tcti_conf, "tabrmd"); + l_dbg(cd, "Verifying TCTI '%s' for token %d\n", tcti_conf, token); + + if (tpm2_verify_tcti_for_token(cd, token, tcti_conf)) + return tcti_conf; for (i = 0; i < TPMS_NO_LIMIT; i++) { snprintf(tcti_conf, tcti_name_len, "device:/dev/tpmrm%d", i); @@ -222,31 +284,47 @@ static int tpm2_token_open_pin(struct crypt_device *cd, l_dbg(cd, "Device does not exist", tcti_conf); break; } - l_dbg(cd, "Device exists, trying to activate...", tcti_conf); - l_std(cd, "Trying to activate with TPM backend: '%s'\n", tcti_conf); - int r = tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, tcti_conf); - if (r != -EINVAL) { - return r; - } + l_dbg(cd, "Device exists, verifying TCTI '%s' for token %d\n", tcti_conf, token); + + if (tpm2_verify_tcti_for_token(cd, token, tcti_conf)) + return tcti_conf; } for (i = 0; i < TPMS_NO_LIMIT; i++) { - snprintf(tcti_conf, tcti_name_len, "device:/dev/tpm%d", i); + snprintf(tcti_conf, tcti_name_len, "device:/dev/tpmrm%d", i); l_dbg(cd, "Checking TPM device: '%s'\n", tcti_conf + 7); access_error = access(tcti_conf + 7, R_OK | W_OK); if (access_error) { l_dbg(cd, "Device does not exist", tcti_conf); break; } - l_dbg(cd, "Device exists, trying to activate...", tcti_conf); - l_std(cd, "Trying to activate with TPM backend: '%s'\n", tcti_conf); - int r = tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, tcti_conf); - if (r != -EINVAL) { - return r; - } + l_dbg(cd, "Device exists, verifying TCTI '%s' for token %d\n", tcti_conf, token); + + if (tpm2_verify_tcti_for_token(cd, token, tcti_conf)) + return tcti_conf; + } + + free(tcti_conf); + return NULL; +} + +static int tpm2_token_open_pin(struct crypt_device *cd, + int token, + const char *tpm_pass, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + char *tcti_conf; + + tcti_conf = tpm2_find_tcti_for_token(cd, token); + + if (!tcti_conf) { + l_err(cd, "Couldn't find a TPM device associated with the TPM token."); + return -EINVAL; } - return -EINVAL; + return tpm2_token_open_pin_with_tcti(cd, token, tpm_pass, buffer, buffer_len,usrptr, tcti_conf); } static int tpm2_token_open(struct crypt_device *cd, @@ -275,6 +353,7 @@ struct tpm2_context { bool tpmdaprotect; bool no_tpm_pin; + bool force_remove; int timeout; int keyslot; @@ -342,6 +421,7 @@ static const crypt_arg_list remove_args[] = { /* plugin specific args */ { NV_ARG, "Select TPM's NV index", CRYPT_ARG_UINT32, &remove_args[1] }, { TCTI_ARG, "Select TCTI in format :, e.g. device:/dev/tpm0", CRYPT_ARG_STRING, &remove_args[2] }, + { FORCE_REMOVE_ARG, "Force remove the TPM token metadata from LUKS header, even if the TPM device is not present.", CRYPT_ARG_BOOL, &remove_args[3] }, /* inherited from cryptsetup core args */ { "token-id", "Token number to remove", CRYPT_ARG_INT32, NULL }, }; @@ -589,6 +669,8 @@ static int get_remove_cli_args(struct crypt_device *cd, struct tpm2_context *tc) { int r; + tc->force_remove = crypt_cli_arg_set(tc->cli, FORCE_REMOVE_ARG); + r = plugin_get_arg_value(cd, tc->cli, "token-id", CRYPT_ARG_INT32, &tc->token); if (r) return r; @@ -599,6 +681,13 @@ static int get_remove_cli_args(struct crypt_device *cd, struct tpm2_context *tc) return r; } + if (crypt_cli_arg_set(tc->cli, TCTI_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, TCTI_ARG, CRYPT_ARG_STRING, &tc->tcti_str); + if (r) + return r; + } + + return 0; } @@ -633,6 +722,7 @@ int crypt_token_remove(struct crypt_device *cd, void *handle) { int i, r; const char *type; + char *found_tcti_conf = NULL; struct tpm2_context *tc = (struct tpm2_context *)handle; if (!tc) @@ -657,19 +747,39 @@ int crypt_token_remove(struct crypt_device *cd, void *handle) return -EINVAL; } + if (tc->tcti_str && !tpm2_verify_tcti_for_token(cd, tc->token, tc->tcti_str) && !tc->force_remove) { + l_err(cd, "TPM device accessed via specified TCTI '%s' is not associated to this TPM token.", tc->tcti_str); + return -EINVAL; + } + + if (!tc->tcti_str) { + l_dbg(cd, "No TCTI was specified, scanning..."); + found_tcti_conf = tpm2_find_tcti_for_token(cd, tc->token); + + if (!found_tcti_conf && !tc->force_remove) { + l_err(cd, "No TPM device associated to this TPM token was found."); + return -EINVAL; + } + } + /* Destroy all keyslots assigned to TPM 2 token */ for (i = 0; i < crypt_keyslot_max(CRYPT_LUKS2); i++) { if (!crypt_token_is_assigned(cd, tc->token, i)) { r = crypt_keyslot_destroy(cd, i); if (r < 0) { l_err(cd, "Cannot destroy keyslot %d.", i); + if (found_tcti_conf) + free(found_tcti_conf); return r; } } } - if (tpm_init(cd, &tc->ctx, tc->tcti_str) != TSS2_RC_SUCCESS) + if (tpm_init(cd, &tc->ctx, tc->tcti_str ? tc->tcti_str : found_tcti_conf) != TSS2_RC_SUCCESS) { + if (found_tcti_conf) + free(found_tcti_conf); return -EINVAL; + } /* Destroy TPM2 NV index and token object itself */ r = tpm2_token_kill(cd, tc->ctx, tc->token); @@ -677,5 +787,8 @@ int crypt_token_remove(struct crypt_device *cd, void *handle) tc->status |= REMOVED; Esys_Finalize(&tc->ctx); + + if (found_tcti_conf) + free(found_tcti_conf); return r; } From 0723ba6fe35c65013be8f07245022b79536e883e Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Fri, 2 Oct 2020 00:36:21 +0200 Subject: [PATCH 12/14] WIP: Change UUID for a random nonce, add token versioning. --- tokens/tpm2/libcryptsetup-token-tpm2.c | 296 ++++++++++++++----------- tokens/tpm2/utils_tpm2.c | 12 + tokens/tpm2/utils_tpm2.h | 11 +- tokens/tpm2/utils_tpm2_json.c | 97 ++++++-- 4 files changed, 262 insertions(+), 154 deletions(-) diff --git a/tokens/tpm2/libcryptsetup-token-tpm2.c b/tokens/tpm2/libcryptsetup-token-tpm2.c index 2e3f34e3..bdfd3af7 100644 --- a/tokens/tpm2/libcryptsetup-token-tpm2.c +++ b/tokens/tpm2/libcryptsetup-token-tpm2.c @@ -32,9 +32,13 @@ #include "../../src/plugin.h" #define TOKEN_NAME "tpm2" +#define TOKEN_VERSION_MAJOR 1 +#define TOKEN_VERSION_MINOR 0 #define DEFAULT_TPM2_SIZE 64 #define DEFAULT_PCR_BANK "sha256" -#define MAX_TPM_COUNT "sha256" +#define TPMS_NO_LIMIT 100 +#define TPMS_MAX_DIGITS 2 // TPM no. 0-99 +#define NV_NONCE_SIZE 32 #define NV_ARG "plugin-tpm2-nv" #define PCR_ARG "plugin-tpm2-pcr" @@ -49,26 +53,34 @@ #define REMOVE_VALID (1 << 2) #define REMOVED (1 << 3) -#define TPMS_NO_LIMIT 100 -#define TPMS_MAX_DIGITS 2 // TPM no. 0-99 - static void tpm2_token_dump(struct crypt_device *cd, const char *json) { - uint32_t nvindex, uuid_nvindex, pcrs, pcrbanks; + uint32_t nvindex, nonce_nvindex, pcrs, pcrbanks, version_major, version_minor; size_t nvkey_size; bool daprotect, pin; + char* nv_nonce; char buf[1024], num[32]; unsigned i, n; - if (tpm2_token_read(cd, json, &nvindex, &uuid_nvindex, &pcrs, &pcrbanks, + if (tpm2_token_read(cd, json, &version_minor, &version_major, &nvindex, &nonce_nvindex, &nv_nonce, &pcrs, &pcrbanks, &daprotect, &pin, &nvkey_size)) { l_err(cd, "Cannot read JSON token metadata."); return; } - l_std(cd, "\tNVindex: 0x%08" PRIx32 "\n", nvindex); - l_std(cd, "\tUUID NVindex: 0x%08" PRIx32 "\n", uuid_nvindex); - l_std(cd, "\tNVKey: %zu [bytes]\n", nvkey_size); + l_std(cd, "\tTPM Token version:\t%" PRIx32 ".%" PRIx32 "\n", version_minor, version_major); + l_std(cd, "\tPassphrase NVindex:\t0x%08" PRIx32 "\n", nvindex); + l_std(cd, "\tPassphrase size:\t%zu [bytes]\n", nvkey_size); + l_std(cd, "\tIdentification nonce NVindex:\t0x%08" PRIx32 "\n", nonce_nvindex); + //l_std(cd, "\tIdentification NV Nonce:\t'%s' [%d bytes]\n", nv_nonce, NV_NONCE_SIZE); + l_std(cd, "\tIdentification NV Nonce:\t"); + + for (i = 0; (i + 1) < strlen(nv_nonce); i += 2) { + if (i && i % 32 == 0) + l_std(cd, "\n\t \t"); + l_std(cd, "%s%c%c", i % 32 == 0 ? "" : " ", nv_nonce[i], nv_nonce[i+1]); + } + l_std(cd, "\n"); for (*buf = '\0', n = 0, i = 0; i < 32; i++) { if (!(pcrs & (1 << i))) @@ -102,99 +114,8 @@ static void tpm2_token_dump(struct crypt_device *cd, const char *json) if (pin) strcat(buf, n++ ? ",PIN" : "PIN"); l_std(cd, "\tflags: %s\n", buf); -} - -static int tpm2_token_open_pin_with_tcti(struct crypt_device *cd, - int token, - const char *tpm_pass, - char **buffer, - size_t *buffer_len, - void *usrptr, - const char *tcti_spec) -{ - int r; - TSS2_RC tpm_rc; - ESYS_CONTEXT *ctx; - uint32_t nvindex, uuid_nvindex, pcrselection, pcrbanks; - size_t nvkey_size; - bool daprotect, pin; - const char *json; - size_t uuid_len; - char *uuid_buffer = NULL; - - if (tpm_init(cd, &ctx, tcti_spec) != TSS2_RC_SUCCESS) - return -EACCES; - - r = crypt_token_json_get(cd, token, &json); - if (r < 0) { - l_err(cd, "Cannot read JSON token metadata."); - goto out; - } - - r = tpm2_token_read(cd, json, &nvindex, &uuid_nvindex, &pcrselection, &pcrbanks, - &daprotect, &pin, &nvkey_size); - if (r < 0) { - l_err(cd, "Cannot read JSON token metadata."); - goto out; - } - - if (pin && !tpm_pass) { - if (daprotect) - l_std(cd, "TPM stored password has dictionary attack protection turned on. " - "Don't enter password too many times.\n"); - r = -EAGAIN; - goto out; - } - - uuid_len = strlen(crypt_get_uuid(cd)); - uuid_buffer = calloc(1, uuid_len); - if (!uuid_buffer) { - r = -ENOMEM; - goto out; - } - - tpm_rc = tpm_nv_read(cd, ctx, uuid_nvindex, NULL, 0, 0, CRYPT_TPM_PCRBANK_SHA1, uuid_buffer, uuid_len); - if (tpm_rc != TPM2_RC_SUCCESS) { - l_err(cd, "Failed to read UUID NV index, this TPM doesn't seem to hold the passphrase."); - LOG_TPM_ERR(cd, tpm_rc); - r = -EINVAL; - goto out; - } - - if (strncmp(crypt_get_uuid(cd), uuid_buffer, uuid_len)) { - l_err(cd, "Bad UUID NV index content, this TPM doesn't hold the passphrase."); - l_dbg(cd, "Real UUID: '%s'", crypt_get_uuid(cd)); - l_dbg(cd, "Read UUID: '%s'", uuid_buffer); - r = -EINVAL; - goto out; - } - - *buffer = malloc(nvkey_size); - if (!(*buffer)) { - r = -ENOMEM; - goto out; - } - *buffer_len = nvkey_size; - - r = -EACCES; - tpm_rc = tpm_nv_read(cd, ctx, nvindex, tpm_pass, tpm_pass ? strlen(tpm_pass) : 0, - pcrselection, pcrbanks, *buffer, nvkey_size); - - if (tpm_rc == TSS2_RC_SUCCESS) { - r = 0; - } else if (tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_BAD_AUTH) || - tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_AUTH_FAIL)) { - l_err(cd, "Failed to read passphrase NV index."); - LOG_TPM_ERR(cd, tpm_rc); - r = -EPERM; - } - -out: - if (uuid_buffer) - free(uuid_buffer); - Esys_Finalize(&ctx); - return r; + free(nv_nonce); } static bool tpm2_verify_tcti_for_token(struct crypt_device *cd, @@ -204,9 +125,10 @@ static bool tpm2_verify_tcti_for_token(struct crypt_device *cd, int r; bool is_valid = true; const char *json; - uint32_t nvindex, uuid_nvindex; - size_t uuid_len; - char *uuid_buffer = NULL; + uint32_t nvindex, nonce_nvindex; + char *nv_nonce_str = NULL; + char *nv_nonce_from_tpm = NULL; + char *nv_nonce_from_tpm_str = NULL; TSS2_RC tpm_rc; ESYS_CONTEXT *ctx; @@ -221,39 +143,46 @@ static bool tpm2_verify_tcti_for_token(struct crypt_device *cd, goto out; } - r = tpm2_token_read(cd, json, &nvindex, &uuid_nvindex, NULL, NULL, + r = tpm2_token_read(cd, json, NULL, NULL, &nvindex, &nonce_nvindex, &nv_nonce_str, NULL, NULL, NULL, NULL, NULL); - if (r < 0) { + if (r < 0 || !nv_nonce_str) { l_err(cd, "Cannot read JSON token metadata."); is_valid = false; goto out; } - uuid_len = strlen(crypt_get_uuid(cd)); - uuid_buffer = calloc(1, uuid_len); - if (!uuid_buffer) { + nv_nonce_from_tpm = calloc(1, NV_NONCE_SIZE); + if (!nv_nonce_from_tpm) { is_valid = false; goto out; } - tpm_rc = tpm_nv_read(cd, ctx, uuid_nvindex, NULL, 0, 0, CRYPT_TPM_PCRBANK_SHA1, uuid_buffer, uuid_len); + tpm_rc = tpm_nv_read(cd, ctx, nonce_nvindex, NULL, 0, 0, CRYPT_TPM_PCRBANK_SHA1, nv_nonce_from_tpm, NV_NONCE_SIZE); if (tpm_rc != TPM2_RC_SUCCESS) { - l_dbg(cd, "Failed to read UUID NV index, this TPM doesn't seem to hold the passphrase."); + l_dbg(cd, "Failed to read NV nonce, this TPM doesn't seem to hold the passphrase."); LOG_TPM_ERR(cd, tpm_rc); is_valid = false; goto out; } - if (strncmp(crypt_get_uuid(cd), uuid_buffer, uuid_len)) { - l_dbg(cd, "Bad UUID NV index content, this TPM doesn't hold the passphrase."); - l_dbg(cd, "Real UUID: '%s'", crypt_get_uuid(cd)); - l_dbg(cd, "TPM-stored UUID: '%s'", uuid_buffer); + nv_nonce_from_tpm_str = bytes_to_hex(nv_nonce_from_tpm, NV_NONCE_SIZE); + + if (strncmp(nv_nonce_from_tpm_str, nv_nonce_str, NV_NONCE_SIZE)) { + l_dbg(cd, "Bad NV nonce content, this TPM doesn't hold the passphrase."); + l_dbg(cd, "Nonce from header: '%s'", nv_nonce_str); + l_dbg(cd, "TPM-stored nonce: '%s'", nv_nonce_from_tpm_str); is_valid = false; goto out; } out: - if (uuid_buffer) { - free(uuid_buffer); + if (nv_nonce_str) { + free(nv_nonce_str); + } + if (nv_nonce_from_tpm) { + free(nv_nonce_from_tpm); + } + if (nv_nonce_from_tpm_str) { + free(nv_nonce_from_tpm_str); } Esys_Finalize(&ctx); @@ -308,6 +237,75 @@ static char *tpm2_find_tcti_for_token(struct crypt_device *cd, return NULL; } +static int tpm2_token_open_pin_with_tcti(struct crypt_device *cd, + int token, + const char *tpm_pass, + char **buffer, + size_t *buffer_len, + void *usrptr, + const char *tcti_spec) +{ + int r; + TSS2_RC tpm_rc; + ESYS_CONTEXT *ctx; + uint32_t nvindex, pcrselection, pcrbanks; + size_t nvkey_size; + bool daprotect, pin; + const char *json; + + if (!tpm2_verify_tcti_for_token(cd, token, tcti_spec)) + return -EINVAL; + + if (tpm_init(cd, &ctx, tcti_spec) != TSS2_RC_SUCCESS) + return -EACCES; + + r = crypt_token_json_get(cd, token, &json); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + goto out; + } + + r = tpm2_token_read(cd, json, NULL, NULL, &nvindex, NULL, NULL, &pcrselection, &pcrbanks, + &daprotect, &pin, &nvkey_size); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + goto out; + } + + if (pin && !tpm_pass) { + if (daprotect) + l_std(cd, "TPM stored password has dictionary attack protection turned on. " + "Don't enter password too many times.\n"); + r = -EAGAIN; + goto out; + } + + *buffer = malloc(nvkey_size); + if (!(*buffer)) { + r = -ENOMEM; + goto out; + } + *buffer_len = nvkey_size; + + r = -EACCES; + + tpm_rc = tpm_nv_read(cd, ctx, nvindex, tpm_pass, tpm_pass ? strlen(tpm_pass) : 0, + pcrselection, pcrbanks, *buffer, nvkey_size); + + if (tpm_rc == TSS2_RC_SUCCESS) { + r = 0; + } else if (tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_BAD_AUTH) || + tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_AUTH_FAIL)) { + l_err(cd, "Failed to read passphrase NV index."); + LOG_TPM_ERR(cd, tpm_rc); + r = -EPERM; + } + +out: + Esys_Finalize(&ctx); + return r; +} + static int tpm2_token_open_pin(struct crypt_device *cd, int token, const char *tpm_pass, @@ -346,7 +344,7 @@ struct tpm2_context { const char *tcti_str; uint32_t tpmbanks; uint32_t tpmnv; - uint32_t tpmuuid_nv; + uint32_t tpmnonce_nv; uint32_t tpmpcrs; uint32_t pass_size; ESYS_CONTEXT *ctx; @@ -402,6 +400,16 @@ void crypt_token_handle_free(void *handle) free(handle); } +#define VERSION_STR(A,B) \ +do { \ + return #A "." #B; \ +} while (0) + +const char *crypt_token_version(void) +{ + VERSION_STR(TOKEN_VERSION_MAJOR, TOKEN_VERSION_MINOR); +} + static const crypt_arg_list create_args[] = { /* plugin specific args */ { NV_ARG, "Select TPM's NV index", CRYPT_ARG_UINT32, &create_args[1] }, @@ -541,7 +549,10 @@ int crypt_token_validate_create_params(struct crypt_device *cd, void *handle) int crypt_token_create(struct crypt_device *cd, void *handle) { - char *existing_pass = NULL, *tpm_pin = NULL, *random_pass = NULL; + char *existing_pass = NULL, *tpm_pin = NULL; + char *random_pass = NULL; + char *nv_nonce = NULL; + char *nv_nonce_str = NULL; size_t existing_pass_len, tpm_pin_len = 0; int r; bool supports_algs_for_pcrs; @@ -561,9 +572,9 @@ int crypt_token_create(struct crypt_device *cd, void *handle) return -EINVAL; if (tc->tcti_str) - l_dbg(cd, "initializing with TCTI %s", tc->tcti_str); + l_dbg(cd, "Initializing Esys with TCTI %s", tc->tcti_str); else - l_dbg(cd, "initializing with default TCTI"); + l_dbg(cd, "Initializing Esys with default TCTI"); if (tpm_init(cd, &tc->ctx, tc->tcti_str) != TSS2_RC_SUCCESS) return -EINVAL; @@ -589,8 +600,24 @@ int crypt_token_create(struct crypt_device *cd, void *handle) } r = tpm_get_random(cd, tc->ctx, random_pass, tc->pass_size); - if (r < 0) + if (r < 0) { + l_err(cd, "Failed to retrieve random data for the TPM keyslot from the TPM."); goto out; + } + + nv_nonce = malloc(NV_NONCE_SIZE); + if (!nv_nonce) { + r = -ENOMEM; + goto out; + } + + r = tpm_get_random(cd, tc->ctx, nv_nonce, NV_NONCE_SIZE); + if (r < 0) { + l_err(cd, "Failed to retrieve random data for the TPM NV nonce from the TPM."); + goto out; + } + + nv_nonce_str = bytes_to_hex(nv_nonce, NV_NONCE_SIZE); r = crypt_cli_get_key("Enter existing LUKS2 pasphrase:", &existing_pass, &existing_pass_len, @@ -613,9 +640,9 @@ int crypt_token_create(struct crypt_device *cd, void *handle) } - r = tpm_nv_find_and_write(cd, tc->ctx, &tc->tpmuuid_nv, crypt_get_uuid(cd), strlen(crypt_get_uuid(cd)), NULL, 0,CRYPT_TPM_PCRBANK_SHA1, 0, false); + r = tpm_nv_find_and_write(cd, tc->ctx, &tc->tpmnonce_nv, nv_nonce, NV_NONCE_SIZE, NULL, 0,CRYPT_TPM_PCRBANK_SHA1, 0, false); if (r < 0) { - l_err(cd, "Failed to write disk UUID to an NV index."); + l_err(cd, "Failed to write random identification nonce to an NV index."); goto err_pass_nv_defined; } @@ -623,12 +650,12 @@ int crypt_token_create(struct crypt_device *cd, void *handle) if (r < 0) { if (r == -EPERM) l_err(cd, "Wrong LUKS2 passphrase supplied."); - goto err_uuid_nv_defined; + goto err_nonce_nv_defined; } tc->keyslot = r; l_std(cd, "Using keyslot %d.\n", tc->keyslot); - r = tpm2_token_add(cd, tc->token, tc->tpmnv, tc->tpmuuid_nv, tc->tpmpcrs, tc->tpmbanks, tc->tpmdaprotect, !tc->no_tpm_pin, tc->pass_size); + r = tpm2_token_add(cd, tc->token, TOKEN_VERSION_MAJOR, TOKEN_VERSION_MINOR, tc->tpmnv, tc->tpmnonce_nv, nv_nonce_str, tc->tpmpcrs, tc->tpmbanks, tc->tpmdaprotect, !tc->no_tpm_pin, tc->pass_size); if (r < 0) { goto err_keyslot_created; } @@ -650,16 +677,21 @@ int crypt_token_create(struct crypt_device *cd, void *handle) err_keyslot_created: crypt_keyslot_destroy(cd, tc->keyslot); -err_uuid_nv_defined: - tpm_nv_undefine(cd, tc->ctx, tc->tpmuuid_nv); - tc->tpmuuid_nv = 0; +err_nonce_nv_defined: + tpm_nv_undefine(cd, tc->ctx, tc->tpmnonce_nv); + tc->tpmnonce_nv = 0; err_pass_nv_defined: tpm_nv_undefine(cd, tc->ctx, tc->tpmnv); tc->tpmnv = 0; out: - crypt_safe_free(random_pass); - crypt_safe_free(existing_pass); - crypt_safe_free(tpm_pin); + if(nv_nonce) + free(nv_nonce); + if (random_pass) + crypt_safe_free(random_pass); + if (existing_pass) + crypt_safe_free(existing_pass); + if (tpm_pin) + crypt_safe_free(tpm_pin); Esys_Finalize(&tc->ctx); return r; diff --git a/tokens/tpm2/utils_tpm2.c b/tokens/tpm2/utils_tpm2.c index dbe35da5..e173458d 100644 --- a/tokens/tpm2/utils_tpm2.c +++ b/tokens/tpm2/utils_tpm2.c @@ -31,6 +31,18 @@ #include "utils_tpm2.h" #include "libcryptsetup.h" +char *bytes_to_hex(char *bytes, size_t size) { + size_t i; + char *ret = malloc(size * 2 + 1); + + for (i = 0; i < size; i++) { + snprintf(ret + i*2, 3, "%02x", (unsigned int) bytes[i]); + } + + ret[size*2] = '\0'; + + return ret; +} const alg_info *get_alg_info_by_name(const char *name) { unsigned i; diff --git a/tokens/tpm2/utils_tpm2.h b/tokens/tpm2/utils_tpm2.h index 57dcdfee..28ff1261 100644 --- a/tokens/tpm2/utils_tpm2.h +++ b/tokens/tpm2/utils_tpm2.h @@ -68,6 +68,7 @@ static const alg_info hash_algs[] = { #define CRYPT_HASH_ALGS_COUNT (sizeof(hash_algs)/sizeof(hash_algs[0])) +char *bytes_to_hex(char *bytes, size_t size); const alg_info *get_alg_info_by_name(const char *name); const alg_info *get_alg_info_by_id(TPM2_ALG_ID id); const alg_info *get_alg_info_by_crypt_id(uint32_t crypt_id); @@ -119,8 +120,11 @@ int tpm_get_random(struct crypt_device *cd, ESYS_CONTEXT *ctx, char *random_byte int tpm2_token_add(struct crypt_device *cd, int token, + uint32_t version_major, + uint32_t version_minor, uint32_t tpm_nv, - uint32_t tpm_uuid_nv, + uint32_t tpm_nonce_nvindex, + const char* tpm_nv_nonce, uint32_t tpm_pcr, uint32_t pcrbanks, bool daprotect, @@ -129,8 +133,11 @@ int tpm2_token_add(struct crypt_device *cd, int tpm2_token_read(struct crypt_device *cd, const char *json, + uint32_t *version_major, + uint32_t *version_minor, uint32_t *tpm_nv, - uint32_t *tpmuuid_nv, + uint32_t *tpm_nonce_nvindex, + char** tpm_nv_nonce, uint32_t *tpm_pcr, uint32_t *pcrbanks, bool *daprotect, diff --git a/tokens/tpm2/utils_tpm2_json.c b/tokens/tpm2/utils_tpm2_json.c index 1d55de8d..ac82ee4f 100644 --- a/tokens/tpm2/utils_tpm2_json.c +++ b/tokens/tpm2/utils_tpm2_json.c @@ -115,10 +115,26 @@ int tpm2_token_validate(const char *json) if (!jobj_token) return -EINVAL; + if (!json_object_object_get_ex(jobj_token, "version-major", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "version-minor", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + if (!json_object_object_get_ex(jobj_token, "nvindex", &jobj) || !json_object_is_type(jobj, json_type_int)) goto out; + if (!json_object_object_get_ex(jobj_token, "nonce-nvindex", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "nv-nonce", &jobj) || + !json_object_is_type(jobj, json_type_string)) + goto out; + if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj) || !json_object_is_type(jobj, json_type_int)) goto out; @@ -146,8 +162,11 @@ int tpm2_token_validate(const char *json) int tpm2_token_read(struct crypt_device *cd, const char *json, + uint32_t *version_major, + uint32_t *version_minor, uint32_t *tpm_nv, - uint32_t *tpmuuid_nv, + uint32_t *tpm_nonce_nvindex, + char** tpm_nv_nonce, uint32_t *tpm_pcr, uint32_t *pcrbanks, bool *daprotect, @@ -165,15 +184,35 @@ int tpm2_token_read(struct crypt_device *cd, if (!jobj_token) return -EINVAL; + if (!json_object_object_get_ex(jobj_token, "version-major", &jobj)) + goto out; + if (version_major) + *version_major = (uint32_t)json_object_get_int64(jobj); + + if (!json_object_object_get_ex(jobj_token, "version-minor", &jobj)) + goto out; + if (version_minor) + *version_minor = (uint32_t)json_object_get_int64(jobj); + if (!json_object_object_get_ex(jobj_token, "nvindex", &jobj)) goto out; if (tpm_nv) *tpm_nv = (uint32_t)json_object_get_int64(jobj); - if (!json_object_object_get_ex(jobj_token, "uuid-nvindex", &jobj)) + if (!json_object_object_get_ex(jobj_token, "nonce-nvindex", &jobj)) + goto out; + if (tpm_nonce_nvindex) + *tpm_nonce_nvindex = (uint32_t)json_object_get_int64(jobj); + + if (!json_object_object_get_ex(jobj_token, "nv-nonce", &jobj)) goto out; - if (tpmuuid_nv) - *tpmuuid_nv = (uint32_t)json_object_get_int64(jobj); + if (tpm_nv_nonce) { + str = json_object_get_string(jobj); + + // the returned string is freed when jobj_token is deallocated at the end of this function + if (str) + *tpm_nv_nonce = strdup(str); + } if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj)) goto out; @@ -226,8 +265,11 @@ int tpm2_token_read(struct crypt_device *cd, int tpm2_token_add(struct crypt_device *cd, int token, + uint32_t version_major, + uint32_t version_minor, uint32_t tpm_nv, - uint32_t tpm_uuid_nv, + uint32_t tpm_nonce_nvindex, + const char* tpm_nv_nonce, uint32_t tpm_pcr, uint32_t pcrbanks, bool daprotect, @@ -247,15 +289,30 @@ int tpm2_token_add(struct crypt_device *cd, goto out; json_object_object_add(jobj_token, "type", jobj); + jobj = json_object_new_int64(version_major); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "version-major", jobj); + + jobj = json_object_new_int64(version_minor); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "version-minor", jobj); + jobj = json_object_new_int64(tpm_nv); if (!jobj) goto out; json_object_object_add(jobj_token, "nvindex", jobj); - jobj = json_object_new_int64(tpm_uuid_nv); + jobj = json_object_new_int64(tpm_nonce_nvindex); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "nonce-nvindex", jobj); + + jobj = json_object_new_string(tpm_nv_nonce); if (!jobj) goto out; - json_object_object_add(jobj_token, "uuid-nvindex", jobj); + json_object_object_add(jobj_token, "nv-nonce", jobj); jobj = json_object_new_int64(nvkey_size); if (!jobj) @@ -312,24 +369,24 @@ static uint32_t token_nvindex(struct crypt_device *cd, int token) if (crypt_token_json_get(cd, token, &json) < 0) return 0; - if (tpm2_token_read(cd, json, &nvindex, NULL, NULL, NULL, NULL, NULL, NULL)) + if (tpm2_token_read(cd, json, NULL, NULL, &nvindex, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) return 0; return nvindex; } -static uint32_t token_uuid_nvindex(struct crypt_device *cd, int token) +static uint32_t token_nonce_nvindex(struct crypt_device *cd, int token) { const char *json; - uint32_t uuid_nvindex; + uint32_t nonce_nvindex; if (crypt_token_json_get(cd, token, &json) < 0) return 0; - if (tpm2_token_read(cd, json, NULL, &uuid_nvindex, NULL, NULL, NULL, NULL, NULL)) + if (tpm2_token_read(cd, json, NULL, NULL, NULL, &nonce_nvindex, NULL, NULL, NULL, NULL, NULL, NULL)) return 0; - return uuid_nvindex; + return nonce_nvindex; } int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv) @@ -364,11 +421,11 @@ int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv) int tpm2_token_kill(struct crypt_device *cd, ESYS_CONTEXT *ctx, int token) { - uint32_t nvindex, uuid_nvindex; + uint32_t nvindex, nonce_nvindex; TSS2_RC r; - uuid_nvindex = token_uuid_nvindex(cd, token); - if (!uuid_nvindex) + nonce_nvindex = token_nonce_nvindex(cd, token); + if (!nonce_nvindex) return -EINVAL; nvindex = token_nvindex(cd, token); @@ -377,21 +434,21 @@ int tpm2_token_kill(struct crypt_device *cd, ESYS_CONTEXT *ctx, int token) bool exists; - r = tpm_nv_exists(cd, ctx, uuid_nvindex, &exists); + r = tpm_nv_exists(cd, ctx, nonce_nvindex, &exists); if (r != TSS2_RC_SUCCESS) { - l_err(cd, "Failed to check if UUID TPM NV-Index 0x%x exists.", uuid_nvindex); + l_err(cd, "Failed to check if identification nonce NV-Index 0x%x exists.", nonce_nvindex); LOG_TPM_ERR(cd, r); return -EINVAL; } if (exists) { - r = tpm_nv_undefine(cd, ctx, uuid_nvindex); + r = tpm_nv_undefine(cd, ctx, nonce_nvindex); if (r) { - l_err(cd, "Failed to undefine UUID TPM NV-Index 0x%x.", nvindex); + l_err(cd, "Failed to undefine identification nonce NV-Index 0x%x.", nvindex); return -EINVAL; } } else { - l_err(cd, "UUID TPM NV-Index 0x%x is already deleted.", nvindex); + l_err(cd, "Identification nonce NV-Index 0x%x is already deleted.", nvindex); } From 30675a2445b0bbaf487f8426d16dd39263d1029b Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Mon, 23 Nov 2020 21:39:19 +0100 Subject: [PATCH 13/14] Use tabs in tests scripts consistently. Really... --- tests/align-test | 14 +++---- tests/align-test2 | 14 +++---- tests/bitlk-compat-test | 72 ++++++++++++++++----------------- tests/blockwise-compat | 18 ++++----- tests/compat-test | 42 +++++++++---------- tests/compat-test2 | 20 ++++----- tests/generators/lib.sh | 4 +- tests/integrity-compat-test | 60 +++++++++++++-------------- tests/keyring-compat-test | 2 +- tests/luks2-reencryption-test | 18 ++++----- tests/password-hash-test | 2 +- tests/reencryption-compat-test | 18 ++++----- tests/reencryption-compat-test2 | 18 ++++----- tests/ssh-plugin-test | 70 ++++++++++++++++---------------- tests/tpm2-plugin-test | 6 +-- 15 files changed, 189 insertions(+), 189 deletions(-) diff --git a/tests/align-test b/tests/align-test index ac3af88f..a6e438da 100755 --- a/tests/align-test +++ b/tests/align-test @@ -309,12 +309,12 @@ echo "# Create enterprise-class 4K drive with fs and LUKS images." # cryptsetup should properly use 4k block on direct-io add_device dev_size_mb=32 sector_size=4096 physblk_exp=0 num_tgts=1 opt_blks=64 for file in $(ls img_fs_*.img.xz) ; do - echo "Format using fs image $file." - xz -d -c $file | dd of=$DEV bs=1M 2>/dev/null || fail "bad image" - [ ! -d $MNT_DIR ] && mkdir $MNT_DIR - mount $DEV $MNT_DIR || skip "Mounting image is not available." - echo $PWD1 | $CRYPTSETUP luksFormat --type luks1 --key-size 256 $FAST_PBKDF $MNT_DIR/luks.img || fail - echo $PWD2 | $CRYPTSETUP luksFormat --type luks1 --key-size 256 $FAST_PBKDF $MNT_DIR/luks.img --header $MNT_DIR/luks_header.img || fail - umount $MNT_DIR + echo "Format using fs image $file." + xz -d -c $file | dd of=$DEV bs=1M 2>/dev/null || fail "bad image" + [ ! -d $MNT_DIR ] && mkdir $MNT_DIR + mount $DEV $MNT_DIR || skip "Mounting image is not available." + echo $PWD1 | $CRYPTSETUP luksFormat --type luks1 --key-size 256 $FAST_PBKDF $MNT_DIR/luks.img || fail + echo $PWD2 | $CRYPTSETUP luksFormat --type luks1 --key-size 256 $FAST_PBKDF $MNT_DIR/luks.img --header $MNT_DIR/luks_header.img || fail + umount $MNT_DIR done cleanup diff --git a/tests/align-test2 b/tests/align-test2 index 5442d949..c12b367f 100755 --- a/tests/align-test2 +++ b/tests/align-test2 @@ -356,12 +356,12 @@ echo "# Create enterprise-class 4K drive with fs and LUKS images." # cryptsetup should properly use 4k block on direct-io add_device dev_size_mb=32 sector_size=4096 physblk_exp=0 num_tgts=1 opt_blks=64 for file in $(ls img_fs_*.img.xz) ; do - echo "Format using fs image $file." - xz -d -c $file | dd of=$DEV bs=1M 2>/dev/null || fail "bad image" - [ ! -d $MNT_DIR ] && mkdir $MNT_DIR - mount $DEV $MNT_DIR || skip "Mounting image is not available." - echo $PWD1 | $CRYPTSETUP luksFormat $FAST_PBKDF --type luks2 $MNT_DIR/luks.img --offset 8192 || fail - echo $PWD2 | $CRYPTSETUP luksFormat $FAST_PBKDF --type luks2 $MNT_DIR/luks.img --header $MNT_DIR/luks_header.img || fail - umount $MNT_DIR + echo "Format using fs image $file." + xz -d -c $file | dd of=$DEV bs=1M 2>/dev/null || fail "bad image" + [ ! -d $MNT_DIR ] && mkdir $MNT_DIR + mount $DEV $MNT_DIR || skip "Mounting image is not available." + echo $PWD1 | $CRYPTSETUP luksFormat $FAST_PBKDF --type luks2 $MNT_DIR/luks.img --offset 8192 || fail + echo $PWD2 | $CRYPTSETUP luksFormat $FAST_PBKDF --type luks2 $MNT_DIR/luks.img --header $MNT_DIR/luks_header.img || fail + umount $MNT_DIR done cleanup diff --git a/tests/bitlk-compat-test b/tests/bitlk-compat-test index f825b311..ffa0a0ce 100755 --- a/tests/bitlk-compat-test +++ b/tests/bitlk-compat-test @@ -37,45 +37,45 @@ function skip() function load_vars() { - local file=$(echo $1 | sed -e s/^$TST_DIR\\/// | sed -e s/\.img$//) - source <(grep = <(grep -A8 "\[$file\]" $TST_DIR/images.conf)) + local file=$(echo $1 | sed -e s/^$TST_DIR\\/// | sed -e s/\.img$//) + source <(grep = <(grep -A8 "\[$file\]" $TST_DIR/images.conf)) } function check_dump() { - dump=$1 - file=$2 - - # load variables for this image from config file - load_vars $file - - # GUID - dump_guid=$(echo "$dump" | grep Version -A 1 | tail -1 | cut -d: -f2 | tr -d "\t\n ") - [ ! -z "$GUID" -a "$dump_guid" = "$GUID" ] || fail " GUID check from dump failed." - - # cipher - dump_cipher=$(echo "$dump" | grep "Cipher name" | cut -d: -f2 | tr -d "\t\n ") - dump_mode=$(echo "$dump" | grep "Cipher mode" | cut -d: -f2 | tr -d "\t\n ") - cipher=$(echo "$dump_cipher-$dump_mode") - [ ! -z "$CIPHER" -a "$cipher" = "$CIPHER" ] || fail " cipher check from dump failed." - - if echo "$file" | grep -q -e "smart-card"; then - # smart card protected VMK GUID - dump_sc_vmk=$(echo "$dump" | grep "VMK protected with smart card" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") - [ ! -z "$SC_VMK_GUID" -a "$dump_sc_vmk" = "$SC_VMK_GUID" ] || fail " smart card protected VMK GUID check from dump failed." + dump=$1 + file=$2 + + # load variables for this image from config file + load_vars $file + + # GUID + dump_guid=$(echo "$dump" | grep Version -A 1 | tail -1 | cut -d: -f2 | tr -d "\t\n ") + [ ! -z "$GUID" -a "$dump_guid" = "$GUID" ] || fail " GUID check from dump failed." + + # cipher + dump_cipher=$(echo "$dump" | grep "Cipher name" | cut -d: -f2 | tr -d "\t\n ") + dump_mode=$(echo "$dump" | grep "Cipher mode" | cut -d: -f2 | tr -d "\t\n ") + cipher=$(echo "$dump_cipher-$dump_mode") + [ ! -z "$CIPHER" -a "$cipher" = "$CIPHER" ] || fail " cipher check from dump failed." + + if echo "$file" | grep -q -e "smart-card"; then + # smart card protected VMK GUID + dump_sc_vmk=$(echo "$dump" | grep "VMK protected with smart card" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") + [ ! -z "$SC_VMK_GUID" -a "$dump_sc_vmk" = "$SC_VMK_GUID" ] || fail " smart card protected VMK GUID check from dump failed." elif echo "$file" | grep -q -e "startup-key"; then - # startup key protected VMK GUID - dump_sk_vmk=$(echo "$dump" | grep "VMK protected with startup key" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") - [ ! -z "$SK_VMK_GUID" -a "$dump_sk_vmk" = "$SK_VMK_GUID" ] || fail " startup key protected VMK GUID check from dump failed." - else - # password protected VMK GUID - dump_pw_vmk=$(echo "$dump" | grep "VMK protected with passphrase" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") - [ ! -z "$PW_VMK_GUID" -a "$dump_pw_vmk" = "$PW_VMK_GUID" ] || fail " password protected VMK GUID check from dump failed." - fi - - # recovery password protected VMK GUID - dump_rp_vmk=$(echo "$dump" | grep "VMK protected with recovery passphrase" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") - [ ! -z "$RP_VMK_GUID" -a "$dump_rp_vmk" = "$RP_VMK_GUID" ] || fail " recovery password protected VMK GUID check from dump failed." + # startup key protected VMK GUID + dump_sk_vmk=$(echo "$dump" | grep "VMK protected with startup key" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") + [ ! -z "$SK_VMK_GUID" -a "$dump_sk_vmk" = "$SK_VMK_GUID" ] || fail " startup key protected VMK GUID check from dump failed." + else + # password protected VMK GUID + dump_pw_vmk=$(echo "$dump" | grep "VMK protected with passphrase" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") + [ ! -z "$PW_VMK_GUID" -a "$dump_pw_vmk" = "$PW_VMK_GUID" ] || fail " password protected VMK GUID check from dump failed." + fi + + # recovery password protected VMK GUID + dump_rp_vmk=$(echo "$dump" | grep "VMK protected with recovery passphrase" -B 1 | head -1 | cut -d: -f2 | tr -d "\t ") + [ ! -z "$RP_VMK_GUID" -a "$dump_rp_vmk" = "$RP_VMK_GUID" ] || fail " recovery password protected VMK GUID check from dump failed." } @@ -100,7 +100,7 @@ echo "HEADER CHECK" for file in $(ls $TST_DIR/bitlk-*) ; do echo -n " $file" out=$($CRYPTSETUP bitlkDump $file) - check_dump "$out" "$file" + check_dump "$out" "$file" echo " [OK]" done @@ -114,7 +114,7 @@ remove_mapping echo "ACTIVATION FS UUID CHECK" for file in $(ls $TST_DIR/bitlk-*) ; do # load variables for this image from config file - load_vars $file + load_vars $file # test with both passphrase and recovery passphrase for PASSPHRASE in $PW $RP ; do diff --git a/tests/blockwise-compat b/tests/blockwise-compat index a7640208..43f74243 100755 --- a/tests/blockwise-compat +++ b/tests/blockwise-compat @@ -116,8 +116,8 @@ RUN() { _fsize=$(stat -c "%s" $_dev) } - case "$_res" in - P) + case "$_res" in + P) MSG="Testing $_fn on $_type with params $@ [expecting TRUE]..." $BW_UNIT $_dev $_fn $@ if [ $? -ne 0 ]; then @@ -131,8 +131,8 @@ RUN() { else MSG="$MSG[OK]" fi - ;; - F) + ;; + F) MSG="Testing $_fn on $_type with params $@ [expecting FALSE]..." $BW_UNIT $_dev $_fn $@ 2> /dev/null if [ $? -eq 0 ]; then @@ -146,11 +146,11 @@ RUN() { else MSG="$MSG[OK]" fi - ;; - *) - fail "Internal test error" - ;; - esac + ;; + *) + fail "Internal test error" + ;; + esac trunc_file $_fsize $_dev } diff --git a/tests/compat-test b/tests/compat-test index 2e456f83..2fff58f7 100755 --- a/tests/compat-test +++ b/tests/compat-test @@ -78,7 +78,7 @@ function fips_mode() function can_fail_fips() { - # Ignore this fail if running in FIPS mode + # Ignore this fail if running in FIPS mode fips_mode || fail $1 } @@ -126,8 +126,8 @@ function prepare() if [ ! -e $KEY1 ]; then #dd if=/dev/urandom of=$KEY1 count=1 bs=32 >/dev/null 2>&1 - echo -n $'\x48\xc6\x74\x4f\x41\x4e\x50\xc0\x79\xc2\x2d\x5b\x5f\x68\x84\x17' >$KEY1 - echo -n $'\x9c\x03\x5e\x1b\x4d\x0f\x9a\x75\xb3\x90\x70\x32\x0a\xf8\xae\xc4'>>$KEY1 + echo -n $'\x48\xc6\x74\x4f\x41\x4e\x50\xc0\x79\xc2\x2d\x5b\x5f\x68\x84\x17' >$KEY1 + echo -n $'\x9c\x03\x5e\x1b\x4d\x0f\x9a\x75\xb3\x90\x70\x32\x0a\xf8\xae\xc4'>>$KEY1 fi if [ ! -e $KEY2 ]; then @@ -176,15 +176,15 @@ scsi_debug_teardown() { function add_scsi_device() { scsi_debug_teardown $DEV - modprobe scsi_debug $@ delay=0 - if [ $? -ne 0 ] ; then - echo "This kernel seems to not support proper scsi_debug module, test skipped." - exit 77 - fi - - sleep 1 - DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) - [ -b $DEV ] || fail "Cannot find $DEV." + modprobe scsi_debug $@ delay=0 + if [ $? -ne 0 ] ; then + echo "This kernel seems to not support proper scsi_debug module, test skipped." + exit 77 + fi + + sleep 1 + DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) + [ -b $DEV ] || fail "Cannot find $DEV." } function valgrind_setup() @@ -785,16 +785,16 @@ $CRYPTSETUP close $DEV_NAME >/dev/null 2>&1 && fail $CRYPTSETUP -q status $DEV_NAME >/dev/null 2>&1 || fail $CRYPTSETUP close --deferred $DEV_NAME >/dev/null 2>&1 if [ $? -eq 0 ] ; then - dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" || fail - $CRYPTSETUP -q status $DEV_NAME >/dev/null 2>&1 || fail - $CRYPTSETUP close --cancel-deferred $DEV_NAME >/dev/null 2>&1 - dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" >/dev/null 2>&1 && fail - $CRYPTSETUP close --deferred $DEV_NAME >/dev/null 2>&1 - $CRYPTSETUP close $DEV_NAME2 || fail - $CRYPTSETUP -q status $DEV_NAME >/dev/null 2>&1 && fail + dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" || fail + $CRYPTSETUP -q status $DEV_NAME >/dev/null 2>&1 || fail + $CRYPTSETUP close --cancel-deferred $DEV_NAME >/dev/null 2>&1 + dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" >/dev/null 2>&1 && fail + $CRYPTSETUP close --deferred $DEV_NAME >/dev/null 2>&1 + $CRYPTSETUP close $DEV_NAME2 || fail + $CRYPTSETUP -q status $DEV_NAME >/dev/null 2>&1 && fail else - $CRYPTSETUP close $DEV_NAME2 >/dev/null 2>&1 - $CRYPTSETUP close $DEV_NAME >/dev/null 2>&1 + $CRYPTSETUP close $DEV_NAME2 >/dev/null 2>&1 + $CRYPTSETUP close $DEV_NAME >/dev/null 2>&1 fi # Interactive tests diff --git a/tests/compat-test2 b/tests/compat-test2 index 912be598..15e4f450 100755 --- a/tests/compat-test2 +++ b/tests/compat-test2 @@ -82,7 +82,7 @@ function fips_mode() function can_fail_fips() { - # Ignore this fail if running in FIPS mode + # Ignore this fail if running in FIPS mode fips_mode || fail $1 } @@ -264,15 +264,15 @@ scsi_debug_teardown() { function add_scsi_device() { scsi_debug_teardown $DEV - modprobe scsi_debug $@ delay=0 - if [ $? -ne 0 ] ; then - echo "This kernel seems to not support proper scsi_debug module, test skipped." - exit 77 - fi - - sleep 1 - DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) - [ -b $DEV ] || fail "Cannot find $DEV." + modprobe scsi_debug $@ delay=0 + if [ $? -ne 0 ] ; then + echo "This kernel seems to not support proper scsi_debug module, test skipped." + exit 77 + fi + + sleep 1 + DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) + [ -b $DEV ] || fail "Cannot find $DEV." } export LANG=C diff --git a/tests/generators/lib.sh b/tests/generators/lib.sh index 96861485..26ea6830 100644 --- a/tests/generators/lib.sh +++ b/tests/generators/lib.sh @@ -172,9 +172,9 @@ function _dd() } function write_bin_hdr_size() { - printf '%016x' $2 | xxd -r -p -l 16 | _dd of=$1 bs=8 count=1 seek=1 conv=notrunc + printf '%016x' $2 | xxd -r -p -l 16 | _dd of=$1 bs=8 count=1 seek=1 conv=notrunc } function write_bin_hdr_offset() { - printf '%016x' $2 | xxd -r -p -l 16 | _dd of=$1 bs=8 count=1 seek=32 conv=notrunc + printf '%016x' $2 | xxd -r -p -l 16 | _dd of=$1 bs=8 count=1 seek=32 conv=notrunc } diff --git a/tests/integrity-compat-test b/tests/integrity-compat-test index 12b323b2..9a0af6d2 100755 --- a/tests/integrity-compat-test +++ b/tests/integrity-compat-test @@ -102,14 +102,14 @@ kernel_param_check() # number value function valgrind_setup() { - which valgrind >/dev/null 2>&1 || fail "Cannot find valgrind." - [ ! -f $INTSETUP_VALGRIND ] && fail "Unable to get location of cryptsetup executable." - export LD_LIBRARY_PATH="$INTSETUP_LIB_VALGRIND:$LD_LIBRARY_PATH" + which valgrind >/dev/null 2>&1 || fail "Cannot find valgrind." + [ ! -f $INTSETUP_VALGRIND ] && fail "Unable to get location of cryptsetup executable." + export LD_LIBRARY_PATH="$INTSETUP_LIB_VALGRIND:$LD_LIBRARY_PATH" } function valgrind_run() { - INFOSTRING="$(basename ${BASH_SOURCE[1]})-line-${BASH_LINENO[0]}" ./valg.sh ${INTSETUP_VALGRIND} "$@" + INFOSTRING="$(basename ${BASH_SOURCE[1]})-line-${BASH_LINENO[0]}" ./valg.sh ${INTSETUP_VALGRIND} "$@" } int_check_sum_only() # checksum @@ -233,27 +233,27 @@ int_error_detection() # mode alg tagsize outtagsize sector_size key_file key_siz int_journal() # 1 alg, 2 tagsize, 3 sector_size, 4 watermark, 5 commit_time, 6 journal_integrity, 7 key-file, 8 key-size, 9 journal_integrity_out { - echo -n "[INTEGRITY JOURNAL:$6:${4}%:${5}ms]" - echo -n "[FORMAT]" - ARGS="--integrity $1 --journal-watermark $4 --journal-commit-time $5 --journal-integrity $6 --journal-integrity-key-file $7 --journal-integrity-key-size $8" - $INTSETUP format -q --tag-size $2 --sector-size $3 $ARGS $DEV || fail "Cannot format device." + echo -n "[INTEGRITY JOURNAL:$6:${4}%:${5}ms]" + echo -n "[FORMAT]" + ARGS="--integrity $1 --journal-watermark $4 --journal-commit-time $5 --journal-integrity $6 --journal-integrity-key-file $7 --journal-integrity-key-size $8" + $INTSETUP format -q --tag-size $2 --sector-size $3 $ARGS $DEV || fail "Cannot format device." - echo -n "[ACTIVATE]" + echo -n "[ACTIVATE]" - $INTSETUP open $DEV $DEV_NAME $ARGS || fail "Cannot activate device." + $INTSETUP open $DEV $DEV_NAME $ARGS || fail "Cannot activate device." - echo -n "[KEYED HASH]" - KEY_HEX=$(xxd -c 256 -l $8 -p $7) - [ -z "$KEY_HEX" ] && fail "Cannot decode key." - dmsetup table --showkeys $DEV_NAME | grep -q $KEY_HEX || fail "Key mismatch." + echo -n "[KEYED HASH]" + KEY_HEX=$(xxd -c 256 -l $8 -p $7) + [ -z "$KEY_HEX" ] && fail "Cannot decode key." + dmsetup table --showkeys $DEV_NAME | grep -q $KEY_HEX || fail "Key mismatch." - status_check "journal watermark" "${4}%" - status_check "journal commit time" "${5} ms" - status_check "journal integrity MAC" $9 + status_check "journal watermark" "${4}%" + status_check "journal commit time" "${5} ms" + status_check "journal integrity MAC" $9 - echo -n "[REMOVE]" - $INTSETUP close $DEV_NAME || fail "Cannot deactivate device." - echo "[OK]" + echo -n "[REMOVE]" + $INTSETUP close $DEV_NAME || fail "Cannot deactivate device." + echo "[OK]" } @@ -446,17 +446,17 @@ $INTSETUP close $DEV_NAME >/dev/null 2>&1 && fail $INTSETUP -q status $DEV_NAME >/dev/null 2>&1 || fail $INTSETUP close --deferred $DEV_NAME >/dev/null 2>&1 if [ $? -eq 0 ] ; then - dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" || fail - $INTSETUP close --cancel-deferred $DEV_NAME >/dev/null 2>&1 - dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" >/dev/null 2>&1 && fail - $INTSETUP close --deferred $DEV_NAME >/dev/null 2>&1 - dmsetup remove $DEV_NAME2 || fail - $INTSETUP -q status $DEV_NAME >/dev/null 2>&1 && fail - echo "[OK]" + dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" || fail + $INTSETUP close --cancel-deferred $DEV_NAME >/dev/null 2>&1 + dmsetup info $DEV_NAME | grep -q "DEFERRED REMOVE" >/dev/null 2>&1 && fail + $INTSETUP close --deferred $DEV_NAME >/dev/null 2>&1 + dmsetup remove $DEV_NAME2 || fail + $INTSETUP -q status $DEV_NAME >/dev/null 2>&1 && fail + echo "[OK]" else - dmsetup remove $DEV_NAME2 >/dev/null 2>&1 - $INTSETUP close $DEV_NAME >/dev/null 2>&1 - echo "[N/A]" + dmsetup remove $DEV_NAME2 >/dev/null 2>&1 + $INTSETUP close $DEV_NAME >/dev/null 2>&1 + echo "[N/A]" fi cleanup diff --git a/tests/keyring-compat-test b/tests/keyring-compat-test index 7a49936e..c0e58cdd 100755 --- a/tests/keyring-compat-test +++ b/tests/keyring-compat-test @@ -92,7 +92,7 @@ function test_and_prepare_keyring() { function fips_mode() { - [ -n "$FIPS_MODE" ] && [ "$FIPS_MODE" -gt 0 ] + [ -n "$FIPS_MODE" ] && [ "$FIPS_MODE" -gt 0 ] } add_device() { diff --git a/tests/luks2-reencryption-test b/tests/luks2-reencryption-test index b47a968c..80d3d678 100755 --- a/tests/luks2-reencryption-test +++ b/tests/luks2-reencryption-test @@ -125,15 +125,15 @@ function fips_mode() function add_scsi_device() { scsi_debug_teardown $DEV - modprobe scsi_debug $@ delay=0 - if [ $? -ne 0 ] ; then - echo "This kernel seems to not support proper scsi_debug module, test skipped." - exit 77 - fi - - sleep 1 - DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) - [ -b $DEV ] || fail "Cannot find $DEV." + modprobe scsi_debug $@ delay=0 + if [ $? -ne 0 ] ; then + echo "This kernel seems to not support proper scsi_debug module, test skipped." + exit 77 + fi + + sleep 1 + DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) + [ -b $DEV ] || fail "Cannot find $DEV." } function open_crypt() # $1 pwd, $2 hdr diff --git a/tests/password-hash-test b/tests/password-hash-test index 0fb58b3b..90ee704c 100755 --- a/tests/password-hash-test +++ b/tests/password-hash-test @@ -23,7 +23,7 @@ cleanup() { function fail() { - echo " $1 [FAILED]" + echo " $1 [FAILED]" echo "FAILED backtrace:" while caller $frame; do ((frame++)); done cleanup 2 diff --git a/tests/reencryption-compat-test b/tests/reencryption-compat-test index 263da0d6..2b768180 100755 --- a/tests/reencryption-compat-test +++ b/tests/reencryption-compat-test @@ -54,15 +54,15 @@ function skip() function add_scsi_device() { del_scsi_device - modprobe scsi_debug $@ delay=0 - if [ $? -ne 0 ] ; then - echo "This kernel seems to not support proper scsi_debug module, test skipped." - exit 77 - fi - - sleep 2 - SCSI_DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) - [ -b $SCSI_DEV ] || fail "Cannot find $SCSI_DEV." + modprobe scsi_debug $@ delay=0 + if [ $? -ne 0 ] ; then + echo "This kernel seems to not support proper scsi_debug module, test skipped." + exit 77 + fi + + sleep 2 + SCSI_DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) + [ -b $SCSI_DEV ] || fail "Cannot find $SCSI_DEV." } function open_crypt() # $1 pwd, $2 hdr diff --git a/tests/reencryption-compat-test2 b/tests/reencryption-compat-test2 index 4ec26fa0..a4fb0e9e 100755 --- a/tests/reencryption-compat-test2 +++ b/tests/reencryption-compat-test2 @@ -75,15 +75,15 @@ function skip() function add_scsi_device() { del_scsi_device - modprobe scsi_debug $@ delay=0 - if [ $? -ne 0 ] ; then - echo "This kernel seems to not support proper scsi_debug module, test skipped." - exit 77 - fi - - sleep 2 - SCSI_DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) - [ -b $SCSI_DEV ] || fail "Cannot find $SCSI_DEV." + modprobe scsi_debug $@ delay=0 + if [ $? -ne 0 ] ; then + echo "This kernel seems to not support proper scsi_debug module, test skipped." + exit 77 + fi + + sleep 2 + SCSI_DEV="/dev/"$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) + [ -b $SCSI_DEV ] || fail "Cannot find $SCSI_DEV." } function open_crypt() # $1 pwd, $2 hdr diff --git a/tests/ssh-plugin-test b/tests/ssh-plugin-test index ca4d37a2..284eafab 100755 --- a/tests/ssh-plugin-test +++ b/tests/ssh-plugin-test @@ -20,31 +20,31 @@ FAST_PBKDF_OPT="--pbkdf pbkdf2 --pbkdf-force-iterations 1000" function remove_mapping() { [ -b /dev/mapper/$MAP ] && dmsetup remove --retry $MAP - losetup -d $LOOPDEV >/dev/null 2>&1 - rm -f $IMG >/dev/null 2>&1 + losetup -d $LOOPDEV >/dev/null 2>&1 + rm -f $IMG >/dev/null 2>&1 } function remove_user() { - id -u $USER >/dev/null 2>&1 && userdel -r -f $USER >/dev/null 2>&1 - rm -f $SSH_KEY_PATH "$SSH_KEY_PATH.pub" >/dev/null 2>&1 + id -u $USER >/dev/null 2>&1 && userdel -r -f $USER >/dev/null 2>&1 + rm -f $SSH_KEY_PATH "$SSH_KEY_PATH.pub" >/dev/null 2>&1 } function create_user() { - id -u $USER >/dev/null 2>&1 - [ $? -eq 0 ] && skip "User account $USER exists, aborting." - [ -f $SSH_KEY_PATH ] && skip "SSH key $SSH_KEY_PATH already exists, aborting." + id -u $USER >/dev/null 2>&1 + [ $? -eq 0 ] && skip "User account $USER exists, aborting." + [ -f $SSH_KEY_PATH ] && skip "SSH key $SSH_KEY_PATH already exists, aborting." - useradd -m $USER -p $(openssl passwd -crypt $PASSWD) || skip "Failed to add user for SSH plugin test." + useradd -m $USER -p $(openssl passwd -crypt $PASSWD) || skip "Failed to add user for SSH plugin test." - ssh-keygen -f $SSH_KEY_PATH -q -N "" >/dev/null 2>&1 - [ $? -ne 0 ] && remove_user && skip "Failed to create SSH key." + ssh-keygen -f $SSH_KEY_PATH -q -N "" >/dev/null 2>&1 + [ $? -ne 0 ] && remove_user && skip "Failed to create SSH key." } function ssh_check() { - systemctl status sshd >/dev/null 2>&1 || skip "SSH server not running, skipping." + systemctl status sshd >/dev/null 2>&1 || skip "SSH server not running, skipping." } function bin_check() @@ -54,23 +54,23 @@ function bin_check() function ssh_setup() { - # ssh-copy-id - sshpass -p $PASSWD ssh-copy-id -i $SSH_KEY_PATH $SSH_OPTIONS $USER@$SSH_SERVER >/dev/null 2>&1 - [ $? -ne 0 ] && remove_user && skip "Failed to copy SSH key." + # ssh-copy-id + sshpass -p $PASSWD ssh-copy-id -i $SSH_KEY_PATH $SSH_OPTIONS $USER@$SSH_SERVER >/dev/null 2>&1 + [ $? -ne 0 ] && remove_user && skip "Failed to copy SSH key." - # try to ssh and also create keyfile - ssh -i $SSH_KEY_PATH $SSH_OPTIONS -o BatchMode=yes -n $USER@$SSH_SERVER -f "echo -n $PASSWD > $SSH_PATH" >/dev/null 2>&1 - [ $? -ne 0 ] && remove_user && skip "Failed to connect using SSH." + # try to ssh and also create keyfile + ssh -i $SSH_KEY_PATH $SSH_OPTIONS -o BatchMode=yes -n $USER@$SSH_SERVER -f "echo -n $PASSWD > $SSH_PATH" >/dev/null 2>&1 + [ $? -ne 0 ] && remove_user && skip "Failed to connect using SSH." } function fail() { echo "[FAILED]" - [ -n "$1" ] && echo "$1" + [ -n "$1" ] && echo "$1" echo "FAILED backtrace:" while caller $frame; do ((frame++)); done remove_mapping - remove_user + remove_user exit 2 } @@ -92,22 +92,22 @@ format() check_dump() { - dump=$1 + dump=$1 - token=$(echo "$dump" | grep Tokens -A 1 | tail -1 | cut -d: -f2 | tr -d "\t\n ") - [ "$token" = "ssh" ] || fail " token check from dump failed." + token=$(echo "$dump" | grep Tokens -A 1 | tail -1 | cut -d: -f2 | tr -d "\t\n ") + [ "$token" = "ssh" ] || fail " token check from dump failed." - server=$(echo "$dump" | grep ssh_server | cut -d: -f2 | tr -d "\t\n ") - [ "$server" = $SSH_SERVER ] || fail " server check from dump failed." + server=$(echo "$dump" | grep ssh_server | cut -d: -f2 | tr -d "\t\n ") + [ "$server" = $SSH_SERVER ] || fail " server check from dump failed." - user=$(echo "$dump" | grep ssh_user | cut -d: -f2 | tr -d "\t\n ") - [ "$user" = "$USER" ] || fail " user check from dump failed." + user=$(echo "$dump" | grep ssh_user | cut -d: -f2 | tr -d "\t\n ") + [ "$user" = "$USER" ] || fail " user check from dump failed." - path=$(echo "$dump" | grep ssh_path | cut -d: -f2 | tr -d "\t\n ") - [ "$path" = "$SSH_PATH" ] || fail " path check from dump failed." + path=$(echo "$dump" | grep ssh_path | cut -d: -f2 | tr -d "\t\n ") + [ "$path" = "$SSH_PATH" ] || fail " path check from dump failed." - key_path=$(echo "$dump" | grep ssh_key_path | cut -d: -f2 | tr -d "\t\n ") - [ "$key_path" = "$SSH_KEY_PATH" ] || fail " key_path check from dump failed." + key_path=$(echo "$dump" | grep ssh_key_path | cut -d: -f2 | tr -d "\t\n ") + [ "$key_path" = "$SSH_KEY_PATH" ] || fail " key_path check from dump failed." } [ $(id -u) != 0 ] && skip "WARNING: You must be root to run this test, test skipped." @@ -124,11 +124,11 @@ format echo -n "Adding SSH token: " $CRYPTSETUP token add --plugin=ssh \ - --plugin-ssh-server $SSH_SERVER \ - --plugin-ssh-user $USER \ - --plugin-ssh-path $SSH_PATH \ - --plugin-ssh-keypath "$SSH_KEY_PATH" \ - $LOOPDEV + --plugin-ssh-server $SSH_SERVER \ + --plugin-ssh-user $USER \ + --plugin-ssh-path $SSH_PATH \ + --plugin-ssh-keypath "$SSH_KEY_PATH" \ + $LOOPDEV [ $? -ne 0 ] && fail "Failed to add SSH token to $LOOPDEV" out=$($CRYPTSETUP luksDump $LOOPDEV) diff --git a/tests/tpm2-plugin-test b/tests/tpm2-plugin-test index f17caf25..3818f4b6 100755 --- a/tests/tpm2-plugin-test +++ b/tests/tpm2-plugin-test @@ -48,9 +48,9 @@ format() # format skip() { - echo "TEST SKIPPED: $1" - cleanup - exit 77 + echo "TEST SKIPPED: $1" + cleanup + exit 77 } function bin_check() From e3dd01c89ec529021cb4bb7b6d402fe38e30a117 Mon Sep 17 00:00:00 2001 From: Ondrej Kozina Date: Thu, 26 Nov 2020 15:47:28 +0100 Subject: [PATCH 14/14] Reject LUKS2 decryption with broken cmd line arguments. LUKS2 decryption is currently not supported for devices with LUKS2 metadata placed in head of data devices. The decryption still works correctly, but resulting plaintext device has data on unexpected (original) offset. For example at offset of 16MiB in case of default LUKS2 header. Fixes: #614. --- src/cryptsetup.c | 17 +++++++++++++++++ src/cryptsetup.h | 2 ++ src/utils_blockdev.c | 20 ++++++++++++++++++++ tests/luks2-reencryption-test | 15 ++++++++++++++- 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 9efbe37f..8a3fce67 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -3086,6 +3086,23 @@ static int action_decrypt_luks2(struct crypt_device *cd) }; size_t passwordLen; + if (!crypt_get_device_name(cd)) + return -EINVAL; + if (!crypt_get_metadata_device_name(cd)) { + log_err(_("LUKS2 decryption is supported with detached header device only.")); + return -ENOTSUP; + } + + r = tools_devices_identical(crypt_get_metadata_device_name(cd), crypt_get_device_name(cd)); + if (r) { + if (r > 0) + log_err(_("LUKS2 decryption is supported with detached header device only.")); + else + log_dbg("Failed to compare header and data devices."); + + return -ENOTSUP; + } + _set_reencryption_flags(¶ms.flags); r = tools_get_key(NULL, &password, &passwordLen, diff --git a/src/cryptsetup.h b/src/cryptsetup.h index 4e96e225..e2800104 100644 --- a/src/cryptsetup.h +++ b/src/cryptsetup.h @@ -113,6 +113,8 @@ int tools_reencrypt_progress(uint64_t size, uint64_t offset, void *usrptr); int tools_read_json_file(struct crypt_device *cd, const char *file, char **json, size_t *json_size, bool batch_mode); int tools_write_json_file(struct crypt_device *cd, const char *file, const char *json); +int tools_devices_identical(const char *header, const char *data); + int tools_detect_signatures(const char *device, int ignore_luks, size_t *count, bool batch_mode); int tools_wipe_all_signatures(const char *path); diff --git a/src/utils_blockdev.c b/src/utils_blockdev.c index 78b488b1..5ecadad6 100644 --- a/src/utils_blockdev.c +++ b/src/utils_blockdev.c @@ -317,3 +317,23 @@ int tools_wipe_all_signatures(const char *path) blk_free(h); return r; } + +#define same_inode(buf1, buf2) \ + ((buf1).st_ino == (buf2).st_ino && \ + (buf1).st_dev == (buf2).st_dev) + +int tools_devices_identical(const char *dev1, const char *dev2) +{ + struct stat st_dev1, st_dev2; + + if (!dev1 || !dev2) + return 0; + + if (dev1 == dev2 || !strcmp(dev1, dev2)) + return 1; + + if (stat(dev1, &st_dev1) < 0 || stat(dev2, &st_dev2) < 0) + return -EINVAL; + + return same_inode(st_dev1, st_dev2); +} diff --git a/tests/luks2-reencryption-test b/tests/luks2-reencryption-test index 80d3d678..5a5638c9 100755 --- a/tests/luks2-reencryption-test +++ b/tests/luks2-reencryption-test @@ -23,6 +23,7 @@ VKEY1=vkey1 PWD1="93R4P4pIqAH8" PWD2="1cND4319812f" PWD3="1-9Qu5Ejfnqv" +DEV_LINK="reenc-test-link" [ -f /etc/system-fips ] && FIPS_MODE=$(cat /proc/sys/crypto/fips_enabled 2>/dev/null) @@ -97,7 +98,7 @@ function remove_mapping() [ -b /dev/mapper/$OVRDEV-err ] && dmsetup remove --retry $OVRDEV-err 2>/dev/null [ -n "$LOOPDEV" ] && losetup -d $LOOPDEV unset LOOPDEV - rm -f $IMG $IMG_HDR $KEY1 $VKEY1 $DEVBIG >/dev/null 2>&1 + rm -f $IMG $IMG_HDR $KEY1 $VKEY1 $DEVBIG $DEV_LINK >/dev/null 2>&1 rmmod scsi_debug 2> /dev/null scsi_debug_teardown $DEV } @@ -930,6 +931,18 @@ if ! dm_delay_features; then exit 0 fi +# check tool can block some funny user ideas +preparebig 64 +ln -s $DEV $DEV_LINK || fail +echo $PWD1 | $CRYPTSETUP luksFormat --type luks2 -c serpent-xts-plain -q $FAST_PBKDF_ARGON $DEV || fail +$CRYPTSETUP reencrypt --decrypt $DEV -q 2>/dev/null && fail +$CRYPTSETUP reencrypt --decrypt $DEV --header $DEV -q 2>/dev/null && fail +$CRYPTSETUP reencrypt --decrypt $DEV --header $DEV_LINK -q 2>/dev/null && fail +open_crypt $PWD1 +$CRYPTSETUP reencrypt --decrypt --active-name $DEV_NAME -q 2>/dev/null && fail +$CRYPTSETUP reencrypt --decrypt --active-name $DEV_NAME --header $DEV -q 2>/dev/null && fail +$CRYPTSETUP reencrypt --decrypt --active-name $DEV_NAME --header $DEV_LINK -q 2>/dev/null && fail + echo "[6] Reencryption recovery" # (check opt-io size optimization in reencryption code does not affect recovery) # device with opt-io size 32k