]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptenroll: support for enrolling FIDO2 tokens in manual mode
authorKamil Szczęk <kamil@szczek.dev>
Fri, 7 Jun 2024 11:22:49 +0000 (13:22 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 20 Jun 2024 12:26:24 +0000 (14:26 +0200)
systemd-cryptsetup supports a FIDO2 mode with manual parameters, where
the user provides all the information necessary for recreating the
secret, such as: credential ID, relaying party ID and the salt. This
feature works great for implementing 2FA schemes, where the salt file
is for example a secret unsealed from the TPM or some other source.
While the unlocking part is quite straightforward to set up, enrolling
such a keyslot - not so easy. There is no clearly documented
way on how to set this up and online resources are scarce on this topic
too. By implementing a straightforward way to enroll such a keyslot
directly from systemd-cryptenroll we streamline the enrollment process
and reduce chances for user error when doing such things manually.

14 files changed:
man/crypttab.xml
man/systemd-cryptenroll.xml
shell-completion/bash/systemd-cryptenroll
src/cryptenroll/cryptenroll-fido2.c
src/cryptenroll/cryptenroll-fido2.h
src/cryptenroll/cryptenroll.c
src/cryptsetup/cryptsetup.c
src/home/homectl-fido2.c
src/shared/cryptsetup-fido2.c
src/shared/fido2-util.c [new file with mode: 0644]
src/shared/fido2-util.h [new file with mode: 0644]
src/shared/libfido2-util.c
src/shared/libfido2-util.h
src/shared/meson.build

index 3aa809e6672ce4baac35d864b8817e5f28b1a7fe..39acaa1ac99e722a06d0f4efb13a2e4e623238c0 100644 (file)
         from the key file. See
         <citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
         for possible values and the default value of this option. This
-        option is ignored in plain encryption mode, as the key file
-        size is then given by the key size.</para>
+        option is ignored in plain encryption mode, where the key file
+        size is determined by the key size. It is also ignored when
+        the key file is used as a salt file for a FIDO2 token, as the
+        salt size in that case is defined by the FIDO2 specification
+        to be exactly 32 bytes.</para>
 
         <xi:include href="version-info.xml" xpointer="v188"/></listitem>
       </varlistentry>
         (configured in the line's third column) to operate. If not configured and the volume is of type
         LUKS2, the CID and the key are read from LUKS2 JSON token metadata instead. Use
         <citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-        as simple tool for enrolling FIDO2 security tokens, compatible with this automatic mode, which is
-        only available for LUKS2 volumes.</para>
+        as simple tool for enrolling FIDO2 security tokens for LUKS2 volumes.</para>
 
         <para>Use <command>systemd-cryptenroll --fido2-device=list</command> to list all suitable FIDO2
         security tokens currently plugged in, along with their device nodes.</para>
index a47866ba61a8f19fc5fb6ec971dbea4da6f8caa7..eadf5a4acefda529757bb186e5afaf7e3feeda42 100644 (file)
         <filename>/dev/hidraw1</filename>). Alternatively the special value <literal>auto</literal> may be
         specified, in order to automatically determine the device node of a currently plugged in security
         token (of which there must be exactly one). This automatic discovery is unsupported if
-        <option>--fido2-device=</option> option is also specified.</para>
+        <option>--fido2-device=</option> option is also specified. Note that currently FIDO2 devices
+        enrolled without an accompanying LUKS2 token (i.e. <option>--fido2-parameters-in-header=no</option>)
+        cannot be used for unlocking.</para>
 
         <xi:include href="version-info.xml" xpointer="v253"/></listitem>
       </varlistentry>
         <xi:include href="version-info.xml" xpointer="v248"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--fido2-salt-file=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>When enrolling a FIDO2 security token, specifies the path to a file or an
+        <constant>AF_UNIX</constant> socket from which we should read the salt value to be used in the
+        HMAC operation performed by the FIDO2 security token. If this option is not specified, the salt
+        will be randomly generated.</para>
+
+        <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--fido2-parameters-in-header=<replaceable>BOOL</replaceable></option></term>
+
+        <listitem><para>When enrolling a FIDO2 security token, controls whether to store FIDO2
+        parameters in a token in the LUKS2 superblock. Defaults to <literal>yes</literal>.
+        If set to <literal>no</literal>, the <option>fido2-cid=</option> option has to be specified manually
+        in the respective <filename>/etc/crypttab</filename> line along with a key file. See
+        <citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details.</para>
+
+        <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--fido2-with-client-pin=<replaceable>BOOL</replaceable></option></term>
 
index 6b13e58789d492da2305e8b9a928d258de8b8921..7a11a3f3dc7b69f9187835d043224757f87fb991 100644 (file)
@@ -57,6 +57,8 @@ _systemd_cryptenroll() {
                --pkcs11-token-uri
                --fido2-credential-algorithm
                --fido2-device
+               --fido2-salt-file
+               --fido2-parameters-in-header
                --fido2-with-client-pin
                --fido2-with-user-presence
                --fido2-with-user-verification
@@ -76,7 +78,7 @@ _systemd_cryptenroll() {
 
     if __contains_word "$prev" ${OPTS[ARG]}; then
         case $prev in
-            --unlock-key-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock)
+            --unlock-key-file|--fido2-salt-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock)
                 comps=$(compgen -A file -- "$cur")
                 compopt -o filenames
                 ;;
@@ -95,7 +97,7 @@ _systemd_cryptenroll() {
             --fido2-device)
                 comps="auto list $(__get_fido2_devices)"
                 ;;
-            --fido2-with-client-pin|--fido2-with-user-presence|--fido2-with-user-verification|--tpm2-with-pin)
+            --fido2-parameters-in-header|--fido2-with-client-pin|--fido2-with-user-presence|--fido2-with-user-verification|--tpm2-with-pin)
                 comps='yes no'
                 ;;
             --tpm2-device)
index 89986bad95d178e5ac24bffe2b7bbcab5ea95203..8e53b9bb47b448804b152882a590a7747f17ac06 100644 (file)
@@ -3,10 +3,14 @@
 #include "ask-password-api.h"
 #include "cryptenroll-fido2.h"
 #include "cryptsetup-fido2.h"
+#include "fido2-util.h"
+#include "glyph-util.h"
 #include "hexdecoct.h"
+#include "iovec-util.h"
 #include "json-util.h"
 #include "libfido2-util.h"
 #include "memory-util.h"
+#include "pretty-print.h"
 #include "random-util.h"
 
 int load_volume_key_fido2(
@@ -67,13 +71,16 @@ int enroll_fido2(
                 size_t volume_key_size,
                 const char *device,
                 Fido2EnrollFlags lock_with,
-                int cred_alg) {
+                int cred_alg,
+                const char *salt_file,
+                bool parameters_in_header) {
 
-        _cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL;
+        _cleanup_(iovec_done_erase) struct iovec salt = {};
+        _cleanup_(erase_and_freep) void *secret = NULL;
         _cleanup_(erase_and_freep) char *base64_encoded = NULL;
         _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
         _cleanup_free_ char *keyslot_as_string = NULL;
-        size_t cid_size, salt_size, secret_size;
+        size_t cid_size, secret_size;
         _cleanup_free_ void *cid = NULL;
         ssize_t base64_encoded_size;
         const char *node, *un;
@@ -88,6 +95,18 @@ int enroll_fido2(
 
         un = strempty(crypt_get_uuid(cd));
 
+        if (salt_file)
+                r = fido2_read_salt_file(
+                                salt_file,
+                                /* offset= */ UINT64_MAX,
+                                /* client= */ "cryptenroll",
+                                /* node= */ un,
+                                &salt);
+        else
+                r = fido2_generate_salt(&salt);
+        if (r < 0)
+                return r;
+
         r = fido2_generate_hmac_hash(
                         device,
                         /* rp_id= */ "io.systemd.cryptsetup",
@@ -100,8 +119,8 @@ int enroll_fido2(
                         /* askpw_credential= */ "cryptenroll.fido2-pin",
                         lock_with,
                         cred_alg,
+                        &salt,
                         &cid, &cid_size,
-                        &salt, &salt_size,
                         &secret, &secret_size,
                         NULL,
                         &lock_with);
@@ -127,24 +146,61 @@ int enroll_fido2(
         if (keyslot < 0)
                 return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node);
 
-        if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
-                return log_oom();
-
-        r = sd_json_buildo(&v,
-                           SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-fido2")),
-                           SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
-                           SD_JSON_BUILD_PAIR("fido2-credential", SD_JSON_BUILD_BASE64(cid, cid_size)),
-                           SD_JSON_BUILD_PAIR("fido2-salt", SD_JSON_BUILD_BASE64(salt, salt_size)),
-                           SD_JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_CONST_STRING("io.systemd.cryptsetup")),
-                           SD_JSON_BUILD_PAIR("fido2-clientPin-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))),
-                           SD_JSON_BUILD_PAIR("fido2-up-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
-                           SD_JSON_BUILD_PAIR("fido2-uv-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))));
-        if (r < 0)
-                return log_error_errno(r, "Failed to prepare FIDO2 JSON token object: %m");
-
-        r = cryptsetup_add_token_json(cd, v);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m");
+        if (parameters_in_header) {
+                if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
+                        return log_oom();
+
+                r = sd_json_buildo(&v,
+                                SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-fido2")),
+                                SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
+                                SD_JSON_BUILD_PAIR("fido2-credential", SD_JSON_BUILD_BASE64(cid, cid_size)),
+                                SD_JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_IOVEC_BASE64(&salt)),
+                                SD_JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_CONST_STRING("io.systemd.cryptsetup")),
+                                SD_JSON_BUILD_PAIR("fido2-clientPin-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))),
+                                SD_JSON_BUILD_PAIR("fido2-up-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
+                                SD_JSON_BUILD_PAIR("fido2-uv-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to prepare FIDO2 JSON token object: %m");
+
+                r = cryptsetup_add_token_json(cd, v);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m");
+        } else {
+                _cleanup_free_ char *base64_encoded_cid = NULL, *link = NULL;
+
+                r = base64mem(cid, cid_size, &base64_encoded_cid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
+
+                r = terminal_urlify_man("crypttab", "5", &link);
+                if (r < 0)
+                        return log_oom();
+
+                fflush(stdout);
+                fprintf(stderr,
+                        "A FIDO2 credential has been registered for this volume:\n\n"
+                        "    %s%sfido2-cid=%s",
+                        emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "",
+                        emoji_enabled() ? " " : "",
+                        ansi_highlight());
+                fflush(stderr);
+
+                fputs(base64_encoded_cid, stdout);
+                fflush(stdout);
+
+                fputs(ansi_normal(), stderr);
+                fflush(stderr);
+
+                fputc('\n', stdout);
+                fflush(stdout);
+
+                fprintf(stderr,
+                        "\nPlease save this FIDO2 credential ID. It is required when unloocking the volume\n"
+                        "using the associated FIDO2 keyslot which we just created. To configure automatic\n"
+                        "unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n"
+                        "file, see %s for details.\n", link);
+                fflush(stderr);
+        }
 
         log_info("New FIDO2 token enrolled as key slot %i.", keyslot);
         return keyslot;
index 3315308e4d4f6bfc193d979ab01d198fb98e1221..5cb3bf6bfaf031676bf457c7b2b71ef5e893f158 100644 (file)
@@ -9,7 +9,7 @@
 
 #if HAVE_LIBFIDO2
 int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
-int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg);
+int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header);
 
 #else
 static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) {
@@ -17,7 +17,7 @@ static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_
                                "FIDO2 unlocking not supported.");
 }
 
-static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg) {
+static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header) {
         return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                "FIDO2 key enrollment not supported.");
 }
index 04352bfec6ddff55e2774bc8892746c3677ffca7..263b8921b177247138d6b13e1bae7205097cf092 100644 (file)
@@ -39,6 +39,8 @@ static char *arg_unlock_fido2_device = NULL;
 static char *arg_unlock_tpm2_device = NULL;
 static char *arg_pkcs11_token_uri = NULL;
 static char *arg_fido2_device = NULL;
+static char *arg_fido2_salt_file = NULL;
+static bool arg_fido2_parameters_in_header = true;
 static char *arg_tpm2_device = NULL;
 static uint32_t arg_tpm2_seal_key_handle = 0;
 static char *arg_tpm2_device_key = NULL;
@@ -69,6 +71,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fido2_salt_file, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep);
@@ -194,6 +197,10 @@ static int help(void) {
                "\n%3$sFIDO2 Enrollment:%4$s\n"
                "     --fido2-device=PATH\n"
                "                       Enroll a FIDO2-HMAC security token\n"
+               "     --fido2-salt-file=PATH\n"
+               "                       Use salt from a file instead of generating one\n"
+               "     --fido2-parameters-in-header=BOOL\n"
+               "                       Whether to store FIDO2 parameters in the LUKS2 header\n"
                "     --fido2-credential-algorithm=STRING\n"
                "                       Specify COSE algorithm for FIDO2 credential\n"
                "     --fido2-with-client-pin=BOOL\n"
@@ -243,6 +250,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UNLOCK_TPM2_DEVICE,
                 ARG_PKCS11_TOKEN_URI,
                 ARG_FIDO2_DEVICE,
+                ARG_FIDO2_SALT_FILE,
+                ARG_FIDO2_PARAMETERS_IN_HEADER,
                 ARG_TPM2_DEVICE,
                 ARG_TPM2_DEVICE_KEY,
                 ARG_TPM2_SEAL_KEY_HANDLE,
@@ -260,29 +269,31 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         static const struct option options[] = {
-                { "help",                         no_argument,       NULL, 'h'                       },
-                { "version",                      no_argument,       NULL, ARG_VERSION               },
-                { "password",                     no_argument,       NULL, ARG_PASSWORD              },
-                { "recovery-key",                 no_argument,       NULL, ARG_RECOVERY_KEY          },
-                { "unlock-key-file",              required_argument, NULL, ARG_UNLOCK_KEYFILE        },
-                { "unlock-fido2-device",          required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE   },
-                { "unlock-tpm2-device",           required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE    },
-                { "pkcs11-token-uri",             required_argument, NULL, ARG_PKCS11_TOKEN_URI      },
-                { "fido2-credential-algorithm",   required_argument, NULL, ARG_FIDO2_CRED_ALG        },
-                { "fido2-device",                 required_argument, NULL, ARG_FIDO2_DEVICE          },
-                { "fido2-with-client-pin",        required_argument, NULL, ARG_FIDO2_WITH_PIN        },
-                { "fido2-with-user-presence",     required_argument, NULL, ARG_FIDO2_WITH_UP         },
-                { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV         },
-                { "tpm2-device",                  required_argument, NULL, ARG_TPM2_DEVICE           },
-                { "tpm2-device-key",              required_argument, NULL, ARG_TPM2_DEVICE_KEY       },
-                { "tpm2-seal-key-handle",         required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE  },
-                { "tpm2-pcrs",                    required_argument, NULL, ARG_TPM2_PCRS             },
-                { "tpm2-public-key",              required_argument, NULL, ARG_TPM2_PUBLIC_KEY       },
-                { "tpm2-public-key-pcrs",         required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS  },
-                { "tpm2-signature",               required_argument, NULL, ARG_TPM2_SIGNATURE        },
-                { "tpm2-pcrlock",                 required_argument, NULL, ARG_TPM2_PCRLOCK          },
-                { "tpm2-with-pin",                required_argument, NULL, ARG_TPM2_WITH_PIN         },
-                { "wipe-slot",                    required_argument, NULL, ARG_WIPE_SLOT             },
+                { "help",                          no_argument,       NULL, 'h'                            },
+                { "version",                       no_argument,       NULL, ARG_VERSION                    },
+                { "password",                      no_argument,       NULL, ARG_PASSWORD                   },
+                { "recovery-key",                  no_argument,       NULL, ARG_RECOVERY_KEY               },
+                { "unlock-key-file",               required_argument, NULL, ARG_UNLOCK_KEYFILE             },
+                { "unlock-fido2-device",           required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE        },
+                { "unlock-tpm2-device",            required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE         },
+                { "pkcs11-token-uri",              required_argument, NULL, ARG_PKCS11_TOKEN_URI           },
+                { "fido2-credential-algorithm",    required_argument, NULL, ARG_FIDO2_CRED_ALG             },
+                { "fido2-device",                  required_argument, NULL, ARG_FIDO2_DEVICE               },
+                { "fido2-salt-file",               required_argument, NULL, ARG_FIDO2_SALT_FILE            },
+                { "fido2-parameters-in-header",    required_argument, NULL, ARG_FIDO2_PARAMETERS_IN_HEADER },
+                { "fido2-with-client-pin",         required_argument, NULL, ARG_FIDO2_WITH_PIN             },
+                { "fido2-with-user-presence",      required_argument, NULL, ARG_FIDO2_WITH_UP              },
+                { "fido2-with-user-verification",  required_argument, NULL, ARG_FIDO2_WITH_UV              },
+                { "tpm2-device",                   required_argument, NULL, ARG_TPM2_DEVICE                },
+                { "tpm2-device-key",               required_argument, NULL, ARG_TPM2_DEVICE_KEY            },
+                { "tpm2-seal-key-handle",          required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE       },
+                { "tpm2-pcrs",                     required_argument, NULL, ARG_TPM2_PCRS                  },
+                { "tpm2-public-key",               required_argument, NULL, ARG_TPM2_PUBLIC_KEY            },
+                { "tpm2-public-key-pcrs",          required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS       },
+                { "tpm2-signature",                required_argument, NULL, ARG_TPM2_SIGNATURE             },
+                { "tpm2-pcrlock",                  required_argument, NULL, ARG_TPM2_PCRLOCK               },
+                { "tpm2-with-pin",                 required_argument, NULL, ARG_TPM2_WITH_PIN              },
+                { "wipe-slot",                     required_argument, NULL, ARG_WIPE_SLOT                  },
                 {}
         };
 
@@ -449,6 +460,20 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_FIDO2_SALT_FILE:
+                        r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_fido2_salt_file);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case ARG_FIDO2_PARAMETERS_IN_HEADER:
+                        r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
                 case ARG_TPM2_DEVICE: {
                         _cleanup_free_ char *device = NULL;
 
@@ -630,6 +655,10 @@ static int parse_argv(int argc, char *argv[]) {
                                                "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. "
                                                "Please specify device paths for enrolling and unlocking respectively.");
 
+                if (!arg_fido2_parameters_in_header && !arg_fido2_salt_file)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "FIDO2 parameters' storage in the LUKS2 header was disabled, but no salt file provided, refusing.");
+
                 if (!arg_fido2_device) {
                         r = fido2_find_device_auto(&arg_fido2_device);
                         if (r < 0)
@@ -841,7 +870,7 @@ static int run(int argc, char *argv[]) {
                 break;
 
         case ENROLL_FIDO2:
-                slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg);
+                slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg, arg_fido2_salt_file, arg_fido2_parameters_in_header);
                 break;
 
         case ENROLL_TPM2:
index 0f655661a64331b260258f9ac2ce5681f60d8948..73e148ee67ac185e6e0d36d5e3a23923d016fd08 100644 (file)
@@ -1260,6 +1260,10 @@ static bool use_token_plugins(void) {
                 return false;
 #endif
 
+        /* Disable tokens if we're in FIDO2 mode with manual parameters. */
+        if (arg_fido2_cid)
+                return false;
+
 #if HAVE_LIBCRYPTSETUP_PLUGINS
         int r;
 
index b49419664b5c373bf3724409db793afabfa380ed..39c6d8a5451e5e7faf0bb726b94377b8fbebfed3 100644 (file)
@@ -6,10 +6,13 @@
 
 #include "ask-password-api.h"
 #include "errno-util.h"
+#include "fido2-util.h"
 #include "format-table.h"
 #include "hexdecoct.h"
 #include "homectl-fido2.h"
 #include "homectl-pkcs11.h"
+#include "iovec-util.h"
+#include "json-util.h"
 #include "libcrypt-util.h"
 #include "libfido2-util.h"
 #include "locale-util.h"
@@ -66,8 +69,7 @@ static int add_fido2_salt(
                 sd_json_variant **v,
                 const void *cid,
                 size_t cid_size,
-                const void *fido2_salt,
-                size_t fido2_salt_size,
+                const struct iovec *salt,
                 const void *secret,
                 size_t secret_size,
                 Fido2EnrollFlags lock_with) {
@@ -77,6 +79,11 @@ static int add_fido2_salt(
         ssize_t base64_encoded_size;
         int r;
 
+        assert(v);
+        assert(cid);
+        assert(iovec_is_set(salt));
+        assert(secret);
+
         /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
          * expect a NUL terminated string, and we use a binary key */
         base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
@@ -89,7 +96,7 @@ static int add_fido2_salt(
 
         r = sd_json_buildo(&e,
                            SD_JSON_BUILD_PAIR("credential", SD_JSON_BUILD_BASE64(cid, cid_size)),
-                           SD_JSON_BUILD_PAIR("salt", SD_JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
+                           SD_JSON_BUILD_PAIR("salt", JSON_BUILD_IOVEC_BASE64(salt)),
                            SD_JSON_BUILD_PAIR("hashedPassword", SD_JSON_BUILD_STRING(hashed)),
                            SD_JSON_BUILD_PAIR("up", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
                            SD_JSON_BUILD_PAIR("uv", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))),
@@ -103,11 +110,11 @@ static int add_fido2_salt(
 
         r = sd_json_variant_append_array(&l, e);
         if (r < 0)
-                return log_error_errno(r, "Failed append FIDO2 salt: %m");
+                return log_error_errno(r, "Failed to append FIDO2 salt: %m");
 
         r = sd_json_variant_set_field(&w, "fido2HmacSalt", l);
         if (r < 0)
-                return log_error_errno(r, "Failed to set FDO2 salt: %m");
+                return log_error_errno(r, "Failed to set FIDO2 salt: %m");
 
         r = sd_json_variant_set_field(v, "privileged", w);
         if (r < 0)
@@ -125,9 +132,10 @@ int identity_add_fido2_parameters(
 
 #if HAVE_LIBFIDO2
         sd_json_variant *un, *realm, *rn;
-        _cleanup_(erase_and_freep) void *secret = NULL, *salt = NULL;
+        _cleanup_(iovec_done) struct iovec salt = {};
+        _cleanup_(erase_and_freep) void *secret = NULL;
         _cleanup_(erase_and_freep) char *used_pin = NULL;
-        size_t cid_size, salt_size, secret_size;
+        size_t cid_size, secret_size;
         _cleanup_free_ void *cid = NULL;
         const char *fido_un;
         int r;
@@ -158,6 +166,10 @@ int identity_add_fido2_parameters(
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "realName field of user record is not a string");
 
+        r = fido2_generate_salt(&salt);
+        if (r < 0)
+               return r;
+
         r = fido2_generate_hmac_hash(
                         device,
                         /* rp_id= */ "io.systemd.home",
@@ -170,8 +182,8 @@ int identity_add_fido2_parameters(
                         /* askpw_credential= */ "home.token-pin",
                         lock_with,
                         cred_alg,
+                        &salt,
                         &cid, &cid_size,
-                        &salt, &salt_size,
                         &secret, &secret_size,
                         &used_pin,
                         &lock_with);
@@ -189,8 +201,7 @@ int identity_add_fido2_parameters(
                         v,
                         cid,
                         cid_size,
-                        salt,
-                        salt_size,
+                        &salt,
                         secret,
                         secret_size,
                         lock_with);
index ebb1c652164eddebf4b82e149fecf94b7426d658..001285efd1ce879850b137aae52b0761fc5c1741 100644 (file)
@@ -5,8 +5,10 @@
 #include "ask-password-api.h"
 #include "cryptsetup-fido2.h"
 #include "env-util.h"
+#include "fido2-util.h"
 #include "fileio.h"
 #include "hexdecoct.h"
+#include "iovec-util.h"
 #include "libfido2-util.h"
 #include "parse-util.h"
 #include "random-util.h"
@@ -33,10 +35,9 @@ int acquire_fido2_key(
 
         _cleanup_(erase_and_freep) char *envpw = NULL;
         _cleanup_strv_free_erase_ char **pins = NULL;
-        _cleanup_free_ void *loaded_salt = NULL;
+        _cleanup_(iovec_done_erase) struct iovec loaded_salt = {};
         bool device_exists = false;
-        const char *salt;
-        size_t salt_size;
+        struct iovec salt;
         int r;
 
         if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
@@ -48,23 +49,17 @@ int acquire_fido2_key(
         assert(cid);
         assert(key_file || key_data);
 
-        if (key_data) {
-                salt = key_data;
-                salt_size = key_data_size;
-        } else {
-                _cleanup_free_ char *bindname = NULL;
-
-                /* If we read the salt via AF_UNIX, make this client recognizable */
-                if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0)
-                        return log_oom();
-
-                r = read_full_file_full(
-                                AT_FDCWD, key_file,
-                                key_file_offset == 0 ? UINT64_MAX : key_file_offset,
-                                key_file_size == 0 ? SIZE_MAX : key_file_size,
-                                READ_FULL_FILE_CONNECT_SOCKET,
-                                bindname,
-                                (char**) &loaded_salt, &salt_size);
+        if (key_data)
+                salt = IOVEC_MAKE(key_data, key_data_size);
+        else {
+                if (key_file_size > 0)
+                        log_debug("Ignoring 'keyfile-size=' option for a FIDO2 salt file.");
+
+                r = fido2_read_salt_file(
+                                key_file, key_file_offset,
+                                /* client= */ "cryptsetup",
+                                /* node= */ volume_name,
+                                &loaded_salt);
                 if (r < 0)
                         return r;
 
@@ -102,7 +97,7 @@ int acquire_fido2_key(
                 r = fido2_use_hmac_hash(
                                 device,
                                 rp_id ?: "io.systemd.cryptsetup",
-                                salt, salt_size,
+                                salt.iov_base, salt.iov_len,
                                 cid, cid_size,
                                 pins,
                                 required,
diff --git a/src/shared/fido2-util.c b/src/shared/fido2-util.c
new file mode 100644 (file)
index 0000000..1dc57cb
--- /dev/null
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "fido2-util.h"
+#include "fileio.h"
+#include "libfido2-util.h"
+#include "random-util.h"
+
+int fido2_generate_salt(struct iovec *ret_salt) {
+        _cleanup_(iovec_done) struct iovec salt = {};
+        int r;
+
+        r = crypto_random_bytes_allocate_iovec(FIDO2_SALT_SIZE, &salt);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate FIDO2 salt: %m");
+
+        *ret_salt = TAKE_STRUCT(salt);
+        return 0;
+}
+
+int fido2_read_salt_file(const char *filename, uint64_t offset, const char *client, const char *node, struct iovec *ret_salt) {
+        _cleanup_(iovec_done_erase) struct iovec salt = {};
+        _cleanup_free_ char *bind_name = NULL;
+        int r;
+
+        /* If we read the salt via AF_UNIX, make the client recognizable */
+        if (asprintf(&bind_name, "@%" PRIx64"/%s-fido2-salt/%s", random_u64(), client, node) < 0)
+                return log_oom();
+
+        r = read_full_file_full(
+                        AT_FDCWD, filename,
+                        offset == 0 ? UINT64_MAX : offset,
+                        /* size= */ FIDO2_SALT_SIZE,
+                        READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|
+                        READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER,
+                        bind_name, (char**) &salt.iov_base, &salt.iov_len);
+        if (r == -E2BIG || (r >= 0 && salt.iov_len != FIDO2_SALT_SIZE))
+                return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL),
+                                       "FIDO2 salt file must contain exactly %u bytes.", FIDO2_SALT_SIZE);
+        if (r < 0)
+                return log_error_errno(r, "Reading FIDO2 salt file '%s' failed: %m", filename);
+
+        *ret_salt = TAKE_STRUCT(salt);
+        return 0;
+}
diff --git a/src/shared/fido2-util.h b/src/shared/fido2-util.h
new file mode 100644 (file)
index 0000000..73f39b4
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdint.h>
+
+#include "iovec-util.h"
+
+int fido2_generate_salt(struct iovec *ret_salt);
+int fido2_read_salt_file(const char *filename, uint64_t offset, const char *client, const char *node, struct iovec *ret_salt);
index 74f52644a67c06e3b648f9d952be0e9ac3629764..d19018b331b51379edf3f95a58e47017f9084275 100644 (file)
@@ -10,7 +10,6 @@
 #include "glyph-util.h"
 #include "log.h"
 #include "memory-util.h"
-#include "random-util.h"
 #include "strv.h"
 #include "unistd.h"
 
@@ -683,8 +682,6 @@ finish:
         return r;
 }
 
-#define FIDO2_SALT_SIZE 32
-
 int fido2_generate_hmac_hash(
                 const char *device,
                 const char *rp_id,
@@ -697,13 +694,13 @@ int fido2_generate_hmac_hash(
                 const char *askpw_credential,
                 Fido2EnrollFlags lock_with,
                 int cred_alg,
+                const struct iovec *salt,
                 void **ret_cid, size_t *ret_cid_size,
-                void **ret_salt, size_t *ret_salt_size,
                 void **ret_secret, size_t *ret_secret_size,
                 char **ret_usedpin,
                 Fido2EnrollFlags *ret_locked_with) {
 
-        _cleanup_(erase_and_freep) void *salt = NULL, *secret_copy = NULL;
+        _cleanup_(erase_and_freep) void *secret_copy = NULL;
         _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
         _cleanup_(fido_cred_free_wrapper) fido_cred_t *c = NULL;
         _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
@@ -717,12 +714,10 @@ int fido2_generate_hmac_hash(
         assert(device);
         assert(ret_cid);
         assert(ret_cid_size);
-        assert(ret_salt);
-        assert(ret_salt_size);
         assert(ret_secret);
         assert(ret_secret_size);
 
-        /* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
+        /* Construction is like this: we read or generate a salt of 32 bytes. We then ask the FIDO2 device to
          * HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
          * authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
          * device never sees the volume key.
@@ -731,25 +726,18 @@ int fido2_generate_hmac_hash(
          *
          * with: S → LUKS/account authentication key                                         (never stored)
          *       I → internal key on FIDO2 device                              (stored in the FIDO2 device)
-         *       D → salt we generate here               (stored in the privileged part of the JSON record)
+         *       D → salt     (stored in the privileged part of the JSON record or read from a file/socket)
          *
          */
 
         assert(device);
         assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0);
+        assert(iovec_is_set(salt));
 
         r = dlopen_libfido2();
         if (r < 0)
                 return log_error_errno(r, "FIDO2 token support is not installed.");
 
-        salt = malloc(FIDO2_SALT_SIZE);
-        if (!salt)
-                return log_oom();
-
-        r = crypto_random_bytes(salt, FIDO2_SALT_SIZE);
-        if (r < 0)
-                return log_error_errno(r, "Failed to generate salt: %m");
-
         d = sym_fido_dev_new();
         if (!d)
                 return log_oom();
@@ -935,7 +923,7 @@ int fido2_generate_hmac_hash(
                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
                                        "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r));
 
-        r = sym_fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
+        r = sym_fido_assert_set_hmac_salt(a, salt->iov_base, salt->iov_len);
         if (r != FIDO_OK)
                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
                                        "Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
@@ -1072,8 +1060,6 @@ int fido2_generate_hmac_hash(
 
         *ret_cid = TAKE_PTR(cid_copy);
         *ret_cid_size = cid_size;
-        *ret_salt = TAKE_PTR(salt);
-        *ret_salt_size = FIDO2_SALT_SIZE;
         *ret_secret = TAKE_PTR(secret_copy);
         *ret_secret_size = secret_size;
 
index af2a4e7006d77c338bcb159749b1c197a837c15a..c7ed6bac343e9cccc4ce0a28a3ef754432762a3d 100644 (file)
@@ -1,8 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "iovec-util.h"
 #include "macro.h"
 
+#define FIDO2_SALT_SIZE 32U
+
 typedef enum Fido2EnrollFlags {
         FIDO2ENROLL_PIN           = 1 << 0,
         FIDO2ENROLL_UP            = 1 << 1, /* User presence (ie: touching token) */
@@ -116,8 +119,8 @@ int fido2_generate_hmac_hash(
                 const char *askpw_credential,
                 Fido2EnrollFlags lock_with,
                 int cred_alg,
+                const struct iovec *salt,
                 void **ret_cid, size_t *ret_cid_size,
-                void **ret_salt, size_t *ret_salt_size,
                 void **ret_secret, size_t *ret_secret_size,
                 char **ret_usedpin,
                 Fido2EnrollFlags *ret_locked_with);
index 5eef659d1ff5d39af52ff049092ac00bdb9fe8ae..e33e924b5520f69709d62cf500f0ca6a13813f2d 100644 (file)
@@ -69,6 +69,7 @@ shared_sources = files(
         'exit-status.c',
         'extension-util.c',
         'fdset.c',
+        'fido2-util.c',
         'fileio-label.c',
         'find-esp.c',
         'firewall-util-nft.c',