diff --git a/.travis-functions.sh b/.travis-functions.sh index f852b282..e8e1ba62 100644 --- a/.travis-functions.sh +++ b/.travis-functions.sh @@ -17,6 +17,9 @@ MAKE="make -j2" DUMP_CONFIG_LOG="short" export TS_OPT_parsable="yes" +export RUN_SSH_PLUGIN_TEST=1 +export RUN_TPM2_PLUGIN_TEST=1 + function configure_travis { ./configure "$@" @@ -38,6 +41,10 @@ function check_nonroot configure_travis \ --enable-cryptsetup-reencrypt \ --enable-internal-sse-argon2 \ + --enable-external-tokens \ + --enable-external-cli-tokens \ + --enable-ssh-token \ + --enable-tpm2-token \ "$cfg_opts" \ || return @@ -52,9 +59,13 @@ 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 \ + --enable-tpm2-token \ "$cfg_opts" \ || return @@ -105,6 +116,11 @@ function travis_install_script dkms \ linux-headers-$(uname -r) \ 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/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/configure.ac b/configure.ac index b7a46e6a..7cd53341 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]) @@ -350,6 +361,14 @@ 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") + +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,, @@ -383,6 +402,17 @@ 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 + +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]]), @@ -538,10 +568,15 @@ 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]) +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/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/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/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 3bc0cd26..8a3fce67 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,50 +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; - tools_token_msg(token, CREATED); - 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 tools_write_json_file(cd, ARG_STR(OPT_JSON_FILE_ID), json); +} + +static int token_plugin_validate_add_args(struct tools_token_handler *token_handler, void *handle, struct crypt_device *cd) +{ + if (!token_handler->create) { + log_err(_("Plugin %s does not support token add action."), token_handler->type); + return -ENOTSUP; + } + + 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; + + 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_remove(struct crypt_device *cd) +static int token_plugin_remove(struct crypt_device *cd, struct tools_token_handler *token_handler) { - crypt_token_info token_info; int r; + void *phandle = NULL; - 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 || !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_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_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; } - r = crypt_token_json_set(cd, ARG_INT32(OPT_TOKEN_ID_ID), NULL); - tools_token_msg(r, REMOVED); + 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_import(struct crypt_device *cd) +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); @@ -2602,75 +2725,47 @@ 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; - 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); - } + 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) +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) { 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 +2777,19 @@ static int action_token(void) return r; } - if (action == ADD) - r = _token_add(cd); /* adds only luks2-keyring type */ - else if (action == REMOVE) + r = -EINVAL; + + if (!strcmp(action_argv[0], "add")) { + r = _token_add(cd); + tools_token_msg(r, CREATED); + } else if (!strcmp(action_argv[0], "remove")) { r = _token_remove(cd); - else if (action == IMPORT) + tools_token_msg(r, REMOVED); + } else if (!strcmp(action_argv[0], "import")) { r = _token_import(cd); - else if (action == EXPORT) + tools_token_msg(r, CREATED); + } else if (!strcmp(action_argv[0], "export")) r = _token_export(cd); - else { - log_dbg("Internal token action error."); - r = -EINVAL; - } crypt_free(cd); @@ -2739,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); } @@ -2989,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, @@ -3451,16 +3565,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); @@ -3573,18 +3759,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), @@ -3633,6 +3835,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 || @@ -3652,31 +3866,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); @@ -3688,7 +3903,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), @@ -3920,10 +4173,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)) @@ -3942,6 +4193,33 @@ 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)); + /* 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_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_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(); @@ -3963,3 +4241,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..e2800104 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) @@ -112,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); @@ -140,4 +143,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_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/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/tests/Makefile.am b/tests/Makefile.am index b096859c..5afc78ee 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -33,6 +33,16 @@ if INTEGRITYSETUP TESTS += integrity-compat-test endif +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 \ conversion_imgs.tar.xz \ @@ -72,7 +82,9 @@ 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 \ + tpm2-plugin-test CLEANFILES = cryptsetup-tst* valglog* *-fail-*.log clean-local: 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..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 } @@ -125,15 +126,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 @@ -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 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 new file mode 100755 index 00000000..284eafab --- /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 diff --git a/tests/tpm2-plugin-test b/tests/tpm2-plugin-test new file mode 100755 index 00000000..3818f4b6 --- /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 diff --git a/tokens/Makemodule.am b/tokens/Makemodule.am new file mode 100644 index 00000000..30c70d0f --- /dev/null +++ b/tokens/Makemodule.am @@ -0,0 +1,27 @@ +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 + +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/libcryptsetup-token.sym b/tokens/libcryptsetup-token.sym new file mode 100644 index 00000000..9e12baae --- /dev/null +++ b/tokens/libcryptsetup-token.sym @@ -0,0 +1,17 @@ +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: *; +}; 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, +}; diff --git a/tokens/tpm2/libcryptsetup-token-tpm2.c b/tokens/tpm2/libcryptsetup-token-tpm2.c new file mode 100644 index 00000000..bdfd3af7 --- /dev/null +++ b/tokens/tpm2/libcryptsetup-token-tpm2.c @@ -0,0 +1,826 @@ +/* + * 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 +#include "utils_tpm2.h" +#include "libcryptsetup.h" +#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 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" +#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 FORCE_REMOVE_ARG "plugin-tpm2-force-remove" + +#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, 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, &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, "\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))) + 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); + + free(nv_nonce); +} + +static bool tpm2_verify_tcti_for_token(struct crypt_device *cd, + int token, + const char *tcti_spec) +{ + int r; + bool is_valid = true; + const char *json; + 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; + + 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, NULL, NULL, &nvindex, &nonce_nvindex, &nv_nonce_str, NULL, NULL, + NULL, NULL, NULL); + if (r < 0 || !nv_nonce_str) { + l_err(cd, "Cannot read JSON token metadata."); + is_valid = false; + goto out; + } + + 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, 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 NV nonce, this TPM doesn't seem to hold the passphrase."); + LOG_TPM_ERR(cd, tpm_rc); + is_valid = false; + goto out; + } + + 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 (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); + 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 = 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); + 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, 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); + 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, 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_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, + 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 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, + 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 tpmnonce_nv; + uint32_t tpmpcrs; + uint32_t pass_size; + ESYS_CONTEXT *ctx; + + bool tpmdaprotect; + bool no_tpm_pin; + bool force_remove; + + 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); +} + +#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] }, + { 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] }, + { 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 }, +}; + +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, TCTI_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; + 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; + 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 Esys with TCTI %s", tc->tcti_str); + else + l_dbg(cd, "Initializing Esys 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) { + 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, + 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; + } + + 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; + } + + + 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 random identification nonce 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."); + goto err_nonce_nv_defined; + } + tc->keyslot = r; + l_std(cd, "Using keyslot %d.\n", tc->keyslot); + + 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; + } + 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); + 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_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: + 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; +} + +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; + + 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, TCTI_ARG)) { + r = plugin_get_arg_value(cd, tc->cli, TCTI_ARG, CRYPT_ARG_STRING, &tc->tcti_str); + 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; + char *found_tcti_conf = NULL; + 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; + } + + 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 ? 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); + if (!r) + tc->status |= REMOVED; + + Esys_Finalize(&tc->ctx); + + if (found_tcti_conf) + free(found_tcti_conf); + return r; +} diff --git a/tokens/tpm2/utils_tpm2.c b/tokens/tpm2/utils_tpm2.c new file mode 100644 index 00000000..e173458d --- /dev/null +++ b/tokens/tpm2/utils_tpm2.c @@ -0,0 +1,739 @@ +/* + * 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" + +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; + + 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 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; + } + + 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"); + LOG_TPM_ERR(cd, r); + 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 (tpm_pcr && !pcrbanks) { + l_err(cd, "You have to also select banks when you select PCRs."); + 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; +} + +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, + 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 && 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])); + current_count += currentPCRs->data.assignedPCR.count; + free(currentPCRs); + } 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..28ff1261 --- /dev/null +++ b/tokens/tpm2/utils_tpm2.h @@ -0,0 +1,169 @@ +/* + * 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])) + +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); + +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 version_major, + uint32_t version_minor, + uint32_t tpm_nv, + uint32_t tpm_nonce_nvindex, + const char* tpm_nv_nonce, + 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 *version_major, + uint32_t *version_minor, + uint32_t *tpm_nv, + uint32_t *tpm_nonce_nvindex, + char** tpm_nv_nonce, + 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); +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..ac82ee4f --- /dev/null +++ b/tokens/tpm2/utils_tpm2_json.c @@ -0,0 +1,478 @@ +/* + * 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, "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; + + 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 *version_major, + uint32_t *version_minor, + uint32_t *tpm_nv, + uint32_t *tpm_nonce_nvindex, + char** tpm_nv_nonce, + 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, "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, "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 (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; + 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 version_major, + uint32_t version_minor, + uint32_t tpm_nv, + uint32_t tpm_nonce_nvindex, + const char* tpm_nv_nonce, + 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(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_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, "nv-nonce", 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, NULL, NULL, &nvindex, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) + return 0; + + return nvindex; +} + +static uint32_t token_nonce_nvindex(struct crypt_device *cd, int token) +{ + const char *json; + uint32_t nonce_nvindex; + + if (crypt_token_json_get(cd, token, &json) < 0) + return 0; + + if (tpm2_token_read(cd, json, NULL, NULL, NULL, &nonce_nvindex, NULL, NULL, NULL, NULL, NULL, NULL)) + return 0; + + return nonce_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, nonce_nvindex; + TSS2_RC r; + + nonce_nvindex = token_nonce_nvindex(cd, token); + if (!nonce_nvindex) + return -EINVAL; + + nvindex = token_nvindex(cd, token); + if (!nvindex) + return -EINVAL; + + bool exists; + + r = tpm_nv_exists(cd, ctx, nonce_nvindex, &exists); + if (r != TSS2_RC_SUCCESS) { + 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, nonce_nvindex); + if (r) { + l_err(cd, "Failed to undefine identification nonce NV-Index 0x%x.", nvindex); + return -EINVAL; + } + } else { + l_err(cd, "Identification nonce 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 TPM passphrase 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; +}