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.
This commit is contained in:
Kamil Szczęk 2024-05-11 10:42:14 +02:00 committed by Luca Boccassi
parent 53b6c99018
commit fd8ed7f26b
5 changed files with 67 additions and 14 deletions

View file

@ -673,6 +673,26 @@
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
<varlistentry>
<term><option>password-cache=yes|no|read-only</option></term>
<listitem><para>Controls whether to use cache for passwords or security token PINs.
Takes a boolean or the special string <literal>read-only</literal>. Defaults to
<literal>yes</literal>.</para>
<para>If set to <literal>read-only</literal>, the kernel keyring is checked for a
password/PIN before requesting one interactively. If set to <literal>yes</literal>,
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.</para>
<para>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.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>pkcs11-uri=</option></term>

View file

@ -94,8 +94,9 @@
<listitem><para>If the <varname>try-empty-password</varname> option is specified then unlocking the
volume with an empty password is attempted.</para></listitem>
<listitem><para>The kernel keyring is then checked for a suitable cached password from previous
attempts.</para></listitem>
<listitem><para>If the <varname>password-cache=</varname> option is set to <literal>yes</literal> or
<literal>read-only</literal>, the kernel keyring is then checked for a suitable cached password from
previous attempts.</para></listitem>
<listitem><para>Finally, the user is queried for a password, possibly multiple times, unless
the <varname>headless</varname> option is set.</para></listitem>

View file

@ -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;

View file

@ -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);

View file

@ -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);