From d14114995776d6c6640bf7af3845581df8b6532c Mon Sep 17 00:00:00 2001 From: Jonas Witschel Date: Wed, 25 May 2022 14:05:31 +0200 Subject: [PATCH 1/5] cryptsetup: refactor asking for a PIN into a more generic function This functionality will be useful for other LUKS2 token types as well in the future. --- src/cryptsetup/cryptsetup.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index bd666230be32c..ea56f552f0d3e 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -851,20 +851,24 @@ static int acquire_pins_from_env_variable(char ***ret_pins) { } #endif -static int attach_luks2_by_fido2_via_plugin( +static int crypt_activate_by_token_pin_ask_password( struct crypt_device *cd, const char *name, + const char *type, usec_t until, bool headless, void *usrptr, - uint32_t activation_flags) { + uint32_t activation_flags, + const char *message, + const char *key_name, + const char *credential_name) { #if HAVE_LIBCRYPTSETUP_PLUGINS AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; _cleanup_strv_free_erase_ char **pins = NULL; int r; - r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, NULL, 0, usrptr, activation_flags); + r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, NULL, 0, usrptr, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ r = 0; if (r != -ENOANO) /* needs pin or pin is wrong */ @@ -875,7 +879,7 @@ static int attach_luks2_by_fido2_via_plugin( return r; STRV_FOREACH(p, pins) { - r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ r = 0; if (r != -ENOANO) /* needs pin or pin is wrong */ @@ -887,12 +891,12 @@ static int attach_luks2_by_fido2_via_plugin( for (;;) { pins = strv_free_erase(pins); - r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); + r = ask_password_auto(message, "drive-harddisk", NULL, key_name, credential_name, until, flags, &pins); if (r < 0) return r; STRV_FOREACH(p, pins) { - r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ r = 0; if (r != -ENOANO) /* needs pin or pin is wrong */ @@ -907,6 +911,27 @@ static int attach_luks2_by_fido2_via_plugin( #endif } +static int attach_luks2_by_fido2_via_plugin( + struct crypt_device *cd, + const char *name, + usec_t until, + bool headless, + void *usrptr, + uint32_t activation_flags) { + + return crypt_activate_by_token_pin_ask_password( + cd, + name, + "systemd-fido2", + until, + headless, + usrptr, + activation_flags, + "Please enter security token PIN:", + "fido2-pin", + "cryptsetup.fido2-pin"); +} + static int attach_luks_or_plain_or_bitlk_by_fido2( struct crypt_device *cd, const char *name, From 35ba2b4f0193ea791a19d23ba9f316082f36852b Mon Sep 17 00:00:00 2001 From: Jonas Witschel Date: Wed, 25 May 2022 14:06:12 +0200 Subject: [PATCH 2/5] cryptsetup: implement cryptsetup_token_open_pin for systemd-tpm2 LUKS2 token This finishes the implementation started in commit 1f895adac287b5f1b6b854caa586093616ccc172 ("cryptsetup: add libcryptsetup TPM2 PIN support"). Note that the previous implementation took a shortcut by returning EOPNOTSUPP instead of the correct ENOANO as per the cryptsetup documentation. This meant that systemd-cryptsetup fell back to the non-plugin implementation in order to ask for the PIN. Since this does not happen any more when returning ENOANO, we need to ask for the PIN in attach_luks2_by_tpm2_via_plugin() instead like attach_luks2_by_fido2_via_plugin() does. --- .../cryptsetup-token-systemd-tpm2.c | 48 +++++++++++++------ .../cryptsetup-tokens/cryptsetup-token-util.c | 26 ++++++++++ .../cryptsetup-tokens/cryptsetup-token-util.h | 2 + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 15 ++---- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 1 + src/cryptsetup/cryptsetup.c | 24 ++++++---- 6 files changed, 82 insertions(+), 34 deletions(-) diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 23df974999478..bbc8a39c98b7d 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -35,22 +35,11 @@ static int log_debug_open_error(struct crypt_device *cd, int r) { return crypt_log_debug_errno(cd, r, TOKEN_NAME " open failed: %m."); } -/* - * This function is called from within following libcryptsetup calls - * provided conditions further below are met: - * - * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-tpm2'): - * - * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device - * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY - * and token is assigned to at least single keyslot). - * - * - if plugin defines validate function (see cryptsetup_token_validate below) it must have - * passed the check (aka return 0) - */ -_public_ int cryptsetup_token_open( +_public_ int cryptsetup_token_open_pin( struct crypt_device *cd, /* is always LUKS2 context */ int token /* is always >= 0 */, + const char *pin, + size_t pin_size, char **password, /* freed by cryptsetup_token_buffer_free */ size_t *password_len, void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { @@ -66,8 +55,9 @@ _public_ int cryptsetup_token_open( _cleanup_free_ void *blob = NULL, *policy_hash = NULL; _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL; _cleanup_(erase_and_freep) void *decrypted_key = NULL; - _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL; + assert(!pin || pin_size); assert(password); assert(password_len); assert(token >= 0); @@ -77,6 +67,10 @@ _public_ int cryptsetup_token_open( assert(token == r); assert(json); + r = crypt_normalize_pin(pin, pin_size, &pin_string); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Can not normalize PIN: %m"); + if (usrptr) params = *(systemd_tpm2_plugin_params *)usrptr; @@ -105,6 +99,7 @@ _public_ int cryptsetup_token_open( policy_hash, policy_hash_size, flags, + pin_string, &decrypted_key, &decrypted_key_size); if (r < 0) @@ -122,6 +117,29 @@ _public_ int cryptsetup_token_open( return 0; } +/* + * This function is called from within following libcryptsetup calls + * provided conditions further below are met: + * + * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-tpm2'): + * + * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device + * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY + * and token is assigned to at least single keyslot). + * + * - if plugin defines validate function (see cryptsetup_token_validate below) it must have + * passed the check (aka return 0) + */ +_public_ int cryptsetup_token_open( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr); +} + /* * libcryptsetup callback for memory deallocation of 'password' parameter passed in * any crypt_token_open_* plugin function diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c index f4086ae367b41..e305d8ba7958f 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c @@ -56,3 +56,29 @@ int crypt_dump_hex_string(const char *hex_str, char **ret_dump_str) { return 0; } + +int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string) { + + _cleanup_free_ char *pin_string = NULL; + + assert(pin || !pin_size); + assert(ret_pin_string); + + if (!pin) { + *ret_pin_string = NULL; + return 0; + } + + /* Refuse embedded NULL bytes, but allow trailing NULL */ + if (memchr(pin, 0, pin_size - 1)) + return -EINVAL; + + /* Enforce trailing NULL byte if missing */ + pin_string = memdup_suffix0(pin, pin_size); + if (!pin_string) + return -ENOMEM; + + *ret_pin_string = TAKE_PTR(pin_string); + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h index 57ffca136f474..146beffb3054c 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -36,3 +36,5 @@ int crypt_dump_buffer_to_hex_string( char **ret_dump_str); int crypt_dump_hex_string(const char *hex_str, char **ret_dump_str); + +int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 0d6e4bc0f8c67..f0286ec1bf4f7 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -22,11 +22,11 @@ int acquire_luks2_key( const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, + const char *pin, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { _cleanup_free_ char *auto_device = NULL; - _cleanup_(erase_and_freep) char *pin_str = NULL; int r; assert(ret_decrypted_key); @@ -42,22 +42,15 @@ int acquire_luks2_key( device = auto_device; } - r = getenv_steal_erase("PIN", &pin_str); - if (r < 0) - return log_error_errno(r, "Failed to acquire PIN from environment: %m"); - if (!r) { - /* PIN entry is not supported by plugin, let it fallback, possibly to sd-cryptsetup's - * internal handling. */ - if (flags & TPM2_FLAGS_USE_PIN) - return -EOPNOTSUPP; - } + if ((flags & TPM2_FLAGS_USE_PIN) && !pin) + return -ENOANO; return tpm2_unseal( device, pcr_mask, pcr_bank, primary_alg, key_data, key_data_size, - policy_hash, policy_hash_size, pin_str, + policy_hash, policy_hash_size, pin, ret_decrypted_key, ret_decrypted_key_size); } diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index 34c93216eee18..5e33418025203 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -16,6 +16,7 @@ int acquire_luks2_key( const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, + const char *pin, void **ret_decrypted_key, size_t *ret_decrypted_key_size); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index ea56f552f0d3e..7a9878e6897be 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1275,11 +1275,11 @@ static int make_tpm2_device_monitor( static int attach_luks2_by_tpm2_via_plugin( struct crypt_device *cd, const char *name, + usec_t until, + bool headless, uint32_t flags) { #if HAVE_LIBCRYPTSETUP_PLUGINS - int r; - systemd_tpm2_plugin_params params = { .search_pcr_mask = arg_tpm2_pcr_mask, .device = arg_tpm2_device @@ -1289,11 +1289,17 @@ static int attach_luks2_by_tpm2_via_plugin( return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Libcryptsetup has external plugins support disabled."); - r = crypt_activate_by_token_pin(cd, name, "systemd-tpm2", CRYPT_ANY_TOKEN, NULL, 0, ¶ms, flags); - if (r > 0) /* returns unlocked keyslot id on success */ - r = 0; - - return r; + return crypt_activate_by_token_pin_ask_password( + cd, + name, + "systemd-tpm2", + until, + headless, + ¶ms, + flags, + "Please enter TPM2 PIN:", + "tpm2-pin", + "cryptsetup.tpm2-pin"); #else return -EOPNOTSUPP; #endif @@ -1354,7 +1360,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( return -EAGAIN; /* Mangle error code: let's make any form of TPM2 failure non-fatal. */ } } else { - r = attach_luks2_by_tpm2_via_plugin(cd, name, flags); + r = attach_luks2_by_tpm2_via_plugin(cd, name, until, arg_headless, flags); + if (r >= 0) + return 0; /* EAGAIN means: no tpm2 chip found * EOPNOTSUPP means: no libcryptsetup plugins support */ if (r == -ENXIO) From 89db47550d137d2e120f9e7002d831591eaa269f Mon Sep 17 00:00:00 2001 From: Jonas Witschel Date: Fri, 5 Aug 2022 11:11:54 +0200 Subject: [PATCH 3/5] cryptsetup-token-systemd-fido2: use crypt_normalize_pin Use the helper function introduced in the previous commit ("cryptsetup: implement cryptsetup_token_open_pin for systemd-tpm2 LUKS2 token") for cryptsetup-token-systemd-tpm2. --- .../cryptsetup-token-systemd-fido2.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index 0db0f562e5180..3027804065006 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -43,18 +43,11 @@ _public_ int cryptsetup_token_open_pin( assert_se(token == r); assert(json); - if (pin && memchr(pin, 0, pin_size - 1)) - return crypt_log_error_errno(cd, ENOANO, "PIN must be characters string."); - - /* pin was passed as pin = pin, pin_size = strlen(pin). We need to add terminating - * NULL byte to addressable memory*/ - if (pin && pin[pin_size-1] != '\0') { - pin_string = strndup(pin, pin_size); - if (!pin_string) - return crypt_log_oom(cd); - } + r = crypt_normalize_pin(pin, pin_size, &pin_string); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Can not normalize PIN: %m"); - return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string ?: pin, password, password_len); + return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string, password, password_len); } /* From ee6c66acc5a61cb56f9b1d6e3132728c4d1ce66d Mon Sep 17 00:00:00 2001 From: Jonas Witschel Date: Wed, 25 May 2022 14:06:12 +0200 Subject: [PATCH 4/5] cryptsetup: ask for PIN when trying to activate using a LUKS2 token plugin crypt_activate_by_token() fails with ENOANO if the token is protected with a PIN, in this case we need to call crypt_activate_by_token_pin() with a PIN. This logic is already implemented in crypt_activate_by_token_pin_ask_password(). This code path is relevant when using systemd-gpt-auto-generator because there is no a priory information about the type of the used security device, so systemd-cryptsetup tries to unlock the volume using the corresponding cryptsetup plugin. --- src/cryptsetup/cryptsetup.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 7a9878e6897be..c8e71c43de5c9 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1886,7 +1886,17 @@ static int run(int argc, char *argv[]) { /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ if (!key_file && !key_data) { - r = crypt_activate_by_token(cd, volume, CRYPT_ANY_TOKEN, NULL, flags); + r = crypt_activate_by_token_pin_ask_password( + cd, + volume, + NULL, + until, + arg_headless, + NULL, + flags, + "Please enter LUKS2 token PIN:", + "luks2-pin", + "cryptsetup.luks2-pin"); if (r >= 0) { log_debug("Volume %s activated with LUKS token id %i.", volume, r); return 0; From 559a1d35d510e3ae637953b1ae9bfab4a124c2f8 Mon Sep 17 00:00:00 2001 From: Jonas Witschel Date: Wed, 25 May 2022 14:06:44 +0200 Subject: [PATCH 5/5] cryptsetup: test unlocking using a TPM2 LUKS2 token plugin with a PIN Test the functionality implemented in the previous commit ("cryptsetup: ask for PIN when trying to activate using a LUKS2 token plugin"): when "tpm2-device" is not specified, systemd-cryptsetup calls crypt_activate_by_token_pin() to try to unlock using a LUKS2 token plugin, test whether this is able to obtain the provided PIN. --- test/units/testsuite-70.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh index 877359f4b0960..6ebb1c72c959d 100755 --- a/test/units/testsuite-70.sh +++ b/test/units/testsuite-70.sh @@ -29,6 +29,17 @@ env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm # Check failure with wrong PIN env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; } +# Check LUKS2 token plugin unlock (i.e. without specifying tpm2-device=auto) +if cryptsetup --help | grep -q 'LUKS2 external token plugin support is compiled-in'; then + env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1 + /usr/lib/systemd/systemd-cryptsetup detach test-volume + + # Check failure with wrong PIN + env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1 && { echo 'unexpected success'; exit 1; } +else + echo 'cryptsetup has no LUKS2 token plugin support, skipping' +fi + # Check failure with wrong PCR (and correct PIN) tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000 env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }