]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptsetup: allow customizing cache behavior
authorKamil Szczęk <kamil@szczek.dev>
Sat, 11 May 2024 08:42:14 +0000 (10:42 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Thu, 27 Jun 2024 11:00:49 +0000 (13:00 +0200)
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
man/systemd-cryptsetup.xml
src/cryptsetup/cryptsetup.c
src/shared/cryptsetup-fido2.c
src/shared/cryptsetup-tpm2.c

index 8f0dbe06e50dad0371de4102f391b30d38c7c28a..8ffeaf7fcbad97c1bfffb78f24826cf9a5622182 100644 (file)
         <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>
 
index 676a38a763f0b91fe2f9369944edfcc2f3fe0d46..1c2db11a45c53699f19f2663566f692619325762 100644 (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>
index 9306d771b997a1426ba31dade34d81110cdc4f02..4eec4c450a7614560c55bf0db785f49f59416eb8 100644 (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;
index 001285efd1ce879850b137aae52b0761fc5c1741..8a5d42bababcbcb1ebd0cd91a42a4c9889141c46 100644 (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);
 
index d029f101add5bc5f876ac52d3710cd3a430734a6..95c01678aa95c5b71d62522d09c9e4b8f4929824 100644 (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);