From fd8ed7f26b5e365c27599bb6b223caaaa20dd2ca Mon Sep 17 00:00:00 2001 From: =?utf8?q?Kamil=20Szcz=C4=99k?= Date: Sat, 11 May 2024 10:42:14 +0200 Subject: [PATCH] cryptsetup: allow customizing cache behavior The new "password-cache" option allows customizing behavior of the ask-password module in regards to caching credentials in the kernel keyring. There are 3 possible values for this option: * read-only - look for credentials in kernel keyring before asking * on - same as read-only, but also save credentials input by user * off - disable keyring credential cache Currently the cache is forced upon the user and this can cause issues. For example, if user wants to attach two volumes with two different FIDO2 tokens in a quick succession, the attachment operation for the second volume will use the PIN cached from the first FIDO2 token, which of course will fail and since tokens are only attempted once, this will cause fallback to a password prompt. --- man/crypttab.xml | 20 ++++++++++++++ man/systemd-cryptsetup.xml | 5 ++-- src/cryptsetup/cryptsetup.c | 52 ++++++++++++++++++++++++++++------- src/shared/cryptsetup-fido2.c | 2 -- src/shared/cryptsetup-tpm2.c | 2 ++ 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/man/crypttab.xml b/man/crypttab.xml index 8f0dbe06e50..8ffeaf7fcba 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -673,6 +673,26 @@ + + + + Controls whether to use cache for passwords or security token PINs. + Takes a boolean or the special string read-only. Defaults to + yes. + + If set to read-only, the kernel keyring is checked for a + password/PIN before requesting one interactively. If set to yes, + in addition to checking the keyring, any password/PIN entered interactively is cached + in the keyring with a 2.5-minute timeout before being purged. + + Note that this option is not permitted for PKCS#11 security tokens. The reasoning + behind this is that PKCS#11 security tokens are usually configured to lock after being + supplied an invalid PIN multiple times, so using the cache might inadvertently lock the + token. + + + + diff --git a/man/systemd-cryptsetup.xml b/man/systemd-cryptsetup.xml index 676a38a763f..1c2db11a45c 100644 --- a/man/systemd-cryptsetup.xml +++ b/man/systemd-cryptsetup.xml @@ -94,8 +94,9 @@ If the try-empty-password option is specified then unlocking the volume with an empty password is attempted. - The kernel keyring is then checked for a suitable cached password from previous - attempts. + If the password-cache= option is set to yes or + read-only, the kernel keyring is then checked for a suitable cached password from + previous attempts. Finally, the user is queried for a password, possibly multiple times, unless the headless option is set. diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 9306d771b99..4eec4c450a7 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -84,7 +84,8 @@ static char *arg_header = NULL; static unsigned arg_tries = 3; static bool arg_readonly = false; static bool arg_verify = false; -static AskPasswordFlags arg_ask_password_flags = 0; +static bool arg_password_cache_set = false; /* Not the actual argument value, just an indicator that some value is set */ +static AskPasswordFlags arg_ask_password_flags = ASK_PASSWORD_ACCEPT_CACHED | ASK_PASSWORD_PUSH_CACHE; static bool arg_discards = false; static bool arg_same_cpu_crypt = false; static bool arg_submit_from_crypt_cpus = false; @@ -299,6 +300,21 @@ static int parse_one_option(const char *option) { SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_ECHO, r); SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_SILENT, !r); } + } else if ((val = startswith(option, "password-cache="))) { + arg_password_cache_set = true; + + if (streq(val, "read-only")) { + arg_ask_password_flags |= ASK_PASSWORD_ACCEPT_CACHED; + arg_ask_password_flags &= ~ASK_PASSWORD_PUSH_CACHE; + } else { + r = parse_boolean(val); + if (r < 0) { + log_warning_errno(r, "Invalid password-cache= option \"%s\", ignoring.", val); + return 0; + } + + SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, r); + } } else if (STR_IN_SET(option, "allow-discards", "discard")) arg_discards = true; else if (streq(option, "same-cpu-crypt")) @@ -649,6 +665,17 @@ static int parse_crypt_config(const char *options) { log_warning("skip= ignored with type %s", arg_type); } + if (arg_pkcs11_uri || arg_pkcs11_uri_auto) { + /* If password-cache was not configured explicitly, default to no cache for PKCS#11 */ + if (!arg_password_cache_set) + arg_ask_password_flags &= ~(ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE); + + /* This prevents future backward-compatibility issues if we decide to allow caching for PKCS#11 */ + if (FLAGS_SET(arg_ask_password_flags, ASK_PASSWORD_ACCEPT_CACHED)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Password cache is not supported for PKCS#11 security tokens."); + } + return 0; } @@ -847,13 +874,13 @@ static int get_password( const char *vol, const char *src, usec_t until, - bool accept_cached, + bool ignore_cached, PassphraseType passphrase_type, char ***ret) { _cleanup_free_ char *friendly = NULL, *text = NULL, *disk_path = NULL, *id = NULL; _cleanup_strv_free_erase_ char **passwords = NULL; - AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE; + AskPasswordFlags flags = arg_ask_password_flags; int r; assert(vol); @@ -886,11 +913,10 @@ static int get_password( .credential = "cryptsetup.passphrase", }; - r = ask_password_auto( - &req, - until, - flags | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), - &passwords); + if (ignore_cached) + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + + r = ask_password_auto(&req, until, flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -1349,7 +1375,7 @@ static int crypt_activate_by_token_pin_ask_password( const char *credential) { #if HAVE_LIBCRYPTSETUP_PLUGINS - AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + AskPasswordFlags flags = arg_ask_password_flags; _cleanup_strv_free_erase_ char **pins = NULL; int r; @@ -2521,7 +2547,13 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); } - r = get_password(volume, source, until, use_cached_passphrase && !arg_verify, passphrase_type, &passwords); + r = get_password( + volume, + source, + until, + /* ignore_cached= */ !use_cached_passphrase || arg_verify, + passphrase_type, + &passwords); use_cached_passphrase = false; if (r == -EAGAIN) continue; diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c index 001285efd1c..8a5d42babab 100644 --- a/src/shared/cryptsetup-fido2.c +++ b/src/shared/cryptsetup-fido2.c @@ -44,8 +44,6 @@ int acquire_fido2_key( return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Local verification is required to unlock this volume, but the 'headless' parameter was set."); - askpw_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; - assert(cid); assert(key_file || key_data); diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c index d029f101add..95c01678aa9 100644 --- a/src/shared/cryptsetup-tpm2.c +++ b/src/shared/cryptsetup-tpm2.c @@ -178,6 +178,8 @@ int acquire_tpm2_key( if (r < 0) return r; + askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + if (iovec_is_set(salt)) { uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; CLEANUP_ERASE(salted_pin); -- 2.47.3