From e262205eb728de09ec1e669d239275d605f200b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Szcz=C4=99k?= Date: Fri, 7 Jun 2024 13:22:49 +0200 Subject: [PATCH] cryptenroll: support for enrolling FIDO2 tokens in manual mode 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. --- man/crypttab.xml | 10 ++- man/systemd-cryptenroll.xml | 28 ++++++- shell-completion/bash/systemd-cryptenroll | 6 +- src/cryptenroll/cryptenroll-fido2.c | 96 ++++++++++++++++++----- src/cryptenroll/cryptenroll-fido2.h | 4 +- src/cryptenroll/cryptenroll.c | 77 ++++++++++++------ src/cryptsetup/cryptsetup.c | 4 + src/home/homectl-fido2.c | 31 +++++--- src/shared/cryptsetup-fido2.c | 35 ++++----- src/shared/fido2-util.c | 44 +++++++++++ src/shared/fido2-util.h | 9 +++ src/shared/libfido2-util.c | 26 ++---- src/shared/libfido2-util.h | 5 +- src/shared/meson.build | 1 + 14 files changed, 272 insertions(+), 104 deletions(-) create mode 100644 src/shared/fido2-util.c create mode 100644 src/shared/fido2-util.h diff --git a/man/crypttab.xml b/man/crypttab.xml index 3aa809e667..39acaa1ac9 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -215,8 +215,11 @@ from the key file. See cryptsetup8 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. + 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. @@ -724,8 +727,7 @@ (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 systemd-cryptenroll1 - as simple tool for enrolling FIDO2 security tokens, compatible with this automatic mode, which is - only available for LUKS2 volumes. + as simple tool for enrolling FIDO2 security tokens for LUKS2 volumes. Use systemd-cryptenroll --fido2-device=list to list all suitable FIDO2 security tokens currently plugged in, along with their device nodes. diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index a47866ba61..eadf5a4ace 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -310,7 +310,9 @@ /dev/hidraw1). Alternatively the special value auto 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 is also specified. + option is also specified. Note that currently FIDO2 devices + enrolled without an accompanying LUKS2 token (i.e. ) + cannot be used for unlocking. @@ -401,6 +403,30 @@ + + + + When enrolling a FIDO2 security token, specifies the path to a file or an + AF_UNIX 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. + + + + + + + + When enrolling a FIDO2 security token, controls whether to store FIDO2 + parameters in a token in the LUKS2 superblock. Defaults to yes. + If set to no, the option has to be specified manually + in the respective /etc/crypttab line along with a key file. See + crypttab5 + for details. + + + + diff --git a/shell-completion/bash/systemd-cryptenroll b/shell-completion/bash/systemd-cryptenroll index 6b13e58789..7a11a3f3dc 100644 --- a/shell-completion/bash/systemd-cryptenroll +++ b/shell-completion/bash/systemd-cryptenroll @@ -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) diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 89986bad95..8e53b9bb47 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -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(); + 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", 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 = 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"); + 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; diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h index 3315308e4d..5cb3bf6bfa 100644 --- a/src/cryptenroll/cryptenroll-fido2.h +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -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."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 04352bfec6..263b8921b1 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -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: diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 0f655661a6..73e148ee67 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -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; diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index b49419664b..39c6d8a545 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -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); diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c index ebb1c65216..001285efd1 100644 --- a/src/shared/cryptsetup-fido2.c +++ b/src/shared/cryptsetup-fido2.c @@ -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 (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."); - /* 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); + 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 index 0000000000..1dc57cbd42 --- /dev/null +++ b/src/shared/fido2-util.c @@ -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 index 0000000000..73f39b43ca --- /dev/null +++ b/src/shared/fido2-util.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#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); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 74f52644a6..d19018b331 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -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; diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index af2a4e7006..c7ed6bac34 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -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); diff --git a/src/shared/meson.build b/src/shared/meson.build index 5eef659d1f..e33e924b55 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -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',