From cd5f57bda71dc9485d7eddf6cfcbfba843f5126c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 9 Apr 2021 20:43:10 +0100 Subject: [PATCH 1/5] cryptsetup: add 'headless' parameter to skip password/pin query On headless setups, in case other methods fail, asking for a password/pin is not useful as there are no users on the terminal, and generates unwanted noise. Add a parameter to /etc/crypttab to skip it. --- man/crypttab.xml | 7 +++++++ man/systemd-cryptsetup@.service.xml | 3 ++- src/cryptsetup/cryptsetup-fido2.c | 4 ++++ src/cryptsetup/cryptsetup-fido2.h | 2 ++ src/cryptsetup/cryptsetup-pkcs11.c | 4 ++++ src/cryptsetup/cryptsetup-pkcs11.h | 2 ++ src/cryptsetup/cryptsetup.c | 17 +++++++++++++++++ src/shared/pkcs11-util.c | 7 +++++-- src/shared/pkcs11-util.h | 2 +- 9 files changed, 44 insertions(+), 4 deletions(-) diff --git a/man/crypttab.xml b/man/crypttab.xml index 72fe2e692da..d3aa3b8fc1f 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -514,6 +514,13 @@ user is queried for a password indefinitely. + + + + Takes a boolean argument, defaults to false. If true, never query interactively + for the password/PIN. Useful for headless systems. + + diff --git a/man/systemd-cryptsetup@.service.xml b/man/systemd-cryptsetup@.service.xml index 668208a01d3..1697ccc0f3c 100644 --- a/man/systemd-cryptsetup@.service.xml +++ b/man/systemd-cryptsetup@.service.xml @@ -73,7 +73,8 @@ 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. + Finally, the user is queried for a password, possibly multiple times, unless + the headless option is set. If no suitable key may be acquired via any of the mechanisms describes above, volume activation fails. diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index 82b83ee4770..8238e823cdf 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -23,6 +23,7 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -88,6 +89,9 @@ int acquire_fido2_key( pins = strv_free_erase(pins); + if (headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); if (r < 0) return log_error_errno(r, "Failed to ask for user password: %m"); diff --git a/src/cryptsetup/cryptsetup-fido2.h b/src/cryptsetup/cryptsetup-fido2.h index 92093ba38bb..d5c5ce59268 100644 --- a/src/cryptsetup/cryptsetup-fido2.h +++ b/src/cryptsetup/cryptsetup-fido2.h @@ -22,6 +22,7 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -49,6 +50,7 @@ static inline int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index 6d7b01176cb..67adf923cc0 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -32,6 +32,7 @@ struct pkcs11_callback_data { void *decrypted_key; size_t decrypted_key_size; bool free_encrypted_key; + bool headless; }; static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) { @@ -72,6 +73,7 @@ static int pkcs11_callback( "pkcs11-pin", "cryptsetup.pkcs11-pin", data->until, + data->headless, NULL); if (r < 0) return r; @@ -109,12 +111,14 @@ int decrypt_pkcs11_key( const void *key_data, /* … or key_data and key_data_size (for literal keys) */ size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { .friendly_name = friendly_name, .until = until, + .headless = headless, }; int r; diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h index 4cd82e0215f..256c09a9b68 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.h +++ b/src/cryptsetup/cryptsetup-pkcs11.h @@ -19,6 +19,7 @@ int decrypt_pkcs11_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -41,6 +42,7 @@ static inline int decrypt_pkcs11_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index c9cebb46378..7902377a25a 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -79,6 +79,7 @@ static char *arg_fido2_rp_id = NULL; static char *arg_tpm2_device = NULL; static bool arg_tpm2_device_auto = false; static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; +static bool arg_headless = false; STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); @@ -381,6 +382,17 @@ static int parse_one_option(const char *option) { } else if (streq(option, "try-empty-password")) arg_try_empty_password = true; + else if ((val = startswith(option, "headless="))) { + + r = parse_boolean(val); + if (r < 0) { + log_error_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + arg_headless = r; + } else if (streq(option, "headless")) + arg_headless = true; else if (!streq(option, "x-initrd.attach")) log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); @@ -532,6 +544,9 @@ static int get_password( assert(src); assert(ret); + if (arg_headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Password querying disabled via 'headless' option."); + friendly = friendly_disk_name(src, vol); if (!friendly) return log_oom(); @@ -775,6 +790,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, until, + arg_headless, &decrypted_key, &decrypted_key_size); if (r >= 0) break; @@ -895,6 +911,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, until, + arg_headless, &decrypted_key, &decrypted_key_size); if (r >= 0) break; diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index aff45ed868c..9fc577ca3cf 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -184,6 +184,7 @@ int pkcs11_token_login( const char *key_name, const char *credential_name, usec_t until, + bool headless, char **ret_used_pin) { _cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL; @@ -247,7 +248,9 @@ int pkcs11_token_login( string_erase(e); if (unsetenv("PIN") < 0) return log_error_errno(errno, "Failed to unset $PIN: %m"); - } else { + } else if (headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the 'PIN' environment variable."); + else { _cleanup_free_ char *text = NULL; if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY)) @@ -960,7 +963,7 @@ static int pkcs11_acquire_certificate_callback( /* Called for every token matching our URI */ - r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", "pkcs11-pin", UINT64_MAX, &pin_used); + r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", "pkcs11-pin", UINT64_MAX, false, &pin_used); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index c2c852f0ebe..f7f32d34d71 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -30,7 +30,7 @@ char *pkcs11_token_label(const CK_TOKEN_INFO *token_info); char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info); char *pkcs11_token_model(const CK_TOKEN_INFO *token_info); -int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, char **ret_used_pin); +int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, bool headless, char **ret_used_pin); int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object); #if HAVE_OPENSSL From cde2f8605e0c3842f9a87785dd758f955f2d04ba Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 12 Apr 2021 21:06:59 +0100 Subject: [PATCH 2/5] FIDO2: support pin-less LUKS enroll/unlock Closes: https://github.com/systemd/systemd/issues/19246 Some FIDO2 devices allow the user to choose whether to use a PIN or not and will HMAC with a different secret depending on the choice. Some other devices (or some device-specific configuration) can instead make it mandatory. Allow the cryptenroll user to choose whether to use a PIN or not, but fail immediately if it is a hard requirement. Record the choice in the JSON-encoded LUKS header metadata so that the right set of options can be used on unlock. --- man/systemd-cryptenroll.xml | 7 +++++ src/cryptenroll/cryptenroll-fido2.c | 7 +++-- src/cryptenroll/cryptenroll-fido2.h | 5 +-- src/cryptenroll/cryptenroll.c | 37 +++++++++++++++++------ src/cryptsetup/cryptsetup-fido2.c | 47 ++++++++++++++++++++--------- src/cryptsetup/cryptsetup-fido2.h | 9 ++++-- src/cryptsetup/cryptsetup.c | 9 +++++- src/home/homectl-fido2.c | 1 + src/home/homework-fido2.c | 1 + src/shared/libfido2-util.c | 23 +++++++++++--- src/shared/libfido2-util.h | 8 +++++ 11 files changed, 118 insertions(+), 36 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 9751444e508..e1c5a41aacf 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -125,6 +125,13 @@ /etc/crypttab line. + + BOOL + + When enrolling a FIDO2 security token, controls whether to require the user to + enter a PIN when unlocking the volume. Defaults to yes. + + PATH diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 1b3ae8d67ce..213b7795b6d 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -11,7 +11,8 @@ int enroll_fido2( struct crypt_device *cd, const void *volume_key, size_t volume_key_size, - const char *device) { + const char *device, + Fido2EnrollFlags lock_with) { _cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -40,6 +41,7 @@ int enroll_fido2( /* user_display_name= */ node, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "drive-harddisk", + lock_with, &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, @@ -75,7 +77,8 @@ int enroll_fido2( JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), JSON_BUILD_PAIR("fido2-credential", JSON_BUILD_BASE64(cid, cid_size)), JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_BASE64(salt, salt_size)), - JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup")))); + JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup")), + JSON_BUILD_PAIR("fido2-clientPin-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h index 936792071f3..b82a9ca842c 100644 --- a/src/cryptenroll/cryptenroll-fido2.h +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -4,12 +4,13 @@ #include #include "cryptsetup-util.h" +#include "libfido2-util.h" #include "log.h" #if HAVE_LIBFIDO2 -int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device); +int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with); #else -static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device) { +static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with) { 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 7d12c427b30..ef6031cb1fb 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -36,6 +36,7 @@ static int *arg_wipe_slots = NULL; static size_t arg_n_wipe_slots = 0; static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT; static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */ +static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN; assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); @@ -88,6 +89,8 @@ static int help(void) { " Specify PKCS#11 security token URI\n" " --fido2-device=PATH\n" " Enroll a FIDO2-HMAC security token\n" + " --fido2-with-client-pin=BOOL\n" + " Whether to require entering a PIN to unlock the volume\n" " --tpm2-device=PATH\n" " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1,PCR2,PCR3,…\n" @@ -114,18 +117,20 @@ static int parse_argv(int argc, char *argv[]) { ARG_TPM2_DEVICE, ARG_TPM2_PCRS, ARG_WIPE_SLOT, + ARG_FIDO2_WITH_PIN, }; 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 }, - { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, - { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "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 }, + { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, + { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, + { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, + { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, {} }; @@ -144,6 +149,18 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case ARG_FIDO2_WITH_PIN: { + bool lock_with_pin; + + r = parse_boolean_argument("--fido2-with-client-pin=", optarg, &lock_with_pin); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, lock_with_pin); + + break; + } + case ARG_PASSWORD: if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -486,7 +503,7 @@ static int run(int argc, char *argv[]) { break; case ENROLL_FIDO2: - slot = enroll_fido2(cd, vk, vks, arg_fido2_device); + slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with); break; case ENROLL_TPM2: diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index 8238e823cdf..339e245575f 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -24,6 +24,7 @@ int acquire_fido2_key( size_t key_data_size, usec_t until, bool headless, + Fido2EnrollFlags required, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -73,19 +74,22 @@ int acquire_fido2_key( } for (;;) { - r = fido2_use_hmac_hash( - device, - rp_id ?: "io.systemd.cryptsetup", - salt, salt_size, - cid, cid_size, - pins, - /* up= */ true, - ret_decrypted_key, - ret_decrypted_key_size); - if (!IN_SET(r, - -ENOANO, /* needs pin */ - -ENOLCK)) /* pin incorrect */ - return r; + if (!FLAGS_SET(required, FIDO2ENROLL_PIN) || pins) { + r = fido2_use_hmac_hash( + device, + rp_id ?: "io.systemd.cryptsetup", + salt, salt_size, + cid, cid_size, + pins, + /* up= */ true, + required, + ret_decrypted_key, + ret_decrypted_key_size); + if (!IN_SET(r, + -ENOANO, /* needs pin */ + -ENOLCK)) /* pin incorrect */ + return r; + } pins = strv_free_erase(pins); @@ -107,12 +111,14 @@ int find_fido2_auto_data( size_t *ret_salt_size, void **ret_cid, size_t *ret_cid_size, - int *ret_keyslot) { + int *ret_keyslot, + Fido2EnrollFlags *ret_required) { _cleanup_free_ void *cid = NULL, *salt = NULL; size_t cid_size = 0, salt_size = 0; _cleanup_free_ char *rp = NULL; int r, keyslot = -1; + Fido2EnrollFlags required = FIDO2ENROLL_PIN; /* For backward compatibility, require pin by default */ assert(cd); assert(ret_salt); @@ -120,6 +126,7 @@ int find_fido2_auto_data( assert(ret_cid); assert(ret_cid_size); assert(ret_keyslot); + assert(ret_required); /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ @@ -176,6 +183,17 @@ int find_fido2_auto_data( if (!rp) return log_oom(); } + + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w) { + /* The "fido2-clientPin-required" field is optional. */ + + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); + + SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); + } } if (!cid) @@ -190,5 +208,6 @@ int find_fido2_auto_data( *ret_salt = TAKE_PTR(salt); *ret_salt_size = salt_size; *ret_keyslot = keyslot; + *ret_required = required; return 0; } diff --git a/src/cryptsetup/cryptsetup-fido2.h b/src/cryptsetup/cryptsetup-fido2.h index d5c5ce59268..76ec28b3b18 100644 --- a/src/cryptsetup/cryptsetup-fido2.h +++ b/src/cryptsetup/cryptsetup-fido2.h @@ -4,6 +4,7 @@ #include #include "cryptsetup-util.h" +#include "libfido2-util.h" #include "log.h" #include "time-util.h" @@ -23,6 +24,7 @@ int acquire_fido2_key( size_t key_data_size, usec_t until, bool headless, + Fido2EnrollFlags required, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -33,7 +35,8 @@ int find_fido2_auto_data( size_t *ret_salt_size, void **ret_cid, size_t *ret_cid_size, - int *ret_keyslot); + int *ret_keyslot, + Fido2EnrollFlags *ret_required); #else @@ -51,6 +54,7 @@ static inline int acquire_fido2_key( size_t key_data_size, usec_t until, bool headless, + Fido2EnrollFlags required, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -65,7 +69,8 @@ static inline int find_fido2_auto_data( size_t *ret_salt_size, void **ret_cid, size_t *ret_cid_size, - int *ret_keyslot) { + int *ret_keyslot, + Fido2EnrollFlags *ret_required) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 token support not available."); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 7902377a25a..9ab42eacb9f 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -739,6 +739,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( int keyslot = arg_key_slot, r; const char *rp_id; const void *cid; + Fido2EnrollFlags required; assert(cd); assert(name); @@ -759,7 +760,8 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( &discovered_salt_size, &discovered_cid, &discovered_cid_size, - &keyslot); + &keyslot, + &required); if (IN_SET(r, -ENOTUNIQ, -ENXIO)) return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), @@ -767,6 +769,10 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (r < 0) return r; + if (FLAGS_SET(required, FIDO2ENROLL_PIN) && arg_headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "A PIN is required to unlock this volume, but the 'headless' parameter was set."); + rp_id = discovered_rp_id; key_data = discovered_salt; key_data_size = discovered_salt_size; @@ -791,6 +797,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( key_data, key_data_size, until, arg_headless, + required, &decrypted_key, &decrypted_key_size); if (r >= 0) break; diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index d5edec1bc45..76775ee6bd8 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -158,6 +158,7 @@ int identity_add_fido2_parameters( /* user_display_name= */ rn ? json_variant_string(rn) : NULL, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "user-home", + FIDO2ENROLL_PIN, // FIXME: add a --lock-with-pin parameter like cryptenroll &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, diff --git a/src/home/homework-fido2.c b/src/home/homework-fido2.c index 87d301c5b43..818f2a5d16c 100644 --- a/src/home/homework-fido2.c +++ b/src/home/homework-fido2.c @@ -29,6 +29,7 @@ int fido2_use_token( salt->credential.id, salt->credential.size, secret->token_pin, h->fido2_user_presence_permitted > 0, + FIDO2ENROLL_PIN, // FIXME: add a --lock-with-pin parameter like cryptenroll &hmac, &hmac_size); if (r < 0) diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 951ed09899c..66a312bfb6a 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -219,6 +219,7 @@ static int fido2_use_hmac_hash_specific_token( size_t cid_size, char **pins, bool up, /* user presence permitted */ + Fido2EnrollFlags required, /* client pin required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -250,6 +251,11 @@ static int fido2_use_hmac_hash_specific_token( if (r < 0) return r; + if (!has_client_pin && FLAGS_SET(required, FIDO2ENROLL_PIN)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "PIN required to unlock, but FIDO2 device %s does not support it.", + path); + a = sym_fido_assert_new(); if (!a) return log_oom(); @@ -303,7 +309,7 @@ static int fido2_use_hmac_hash_specific_token( r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin but with up now */ } - if (r == FIDO_ERR_PIN_REQUIRED) { + if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { char **i; if (!has_client_pin) @@ -367,6 +373,7 @@ int fido2_use_hmac_hash( size_t cid_size, char **pins, bool up, /* user presence permitted */ + Fido2EnrollFlags required, /* client pin required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -379,7 +386,7 @@ int fido2_use_hmac_hash( return log_error_errno(r, "FIDO2 support is not installed."); if (device) - return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, up, ret_hmac, ret_hmac_size); + return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, up, required, ret_hmac, ret_hmac_size); di = sym_fido_dev_info_new(allocated); if (!di) @@ -414,7 +421,7 @@ int fido2_use_hmac_hash( goto finish; } - r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, up, ret_hmac, ret_hmac_size); + r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, up, required, ret_hmac, ret_hmac_size); if (!IN_SET(r, -EBADSLT, /* device doesn't understand our credential hash */ -ENODEV /* device is not a FIDO2 device with HMAC-SECRET */)) @@ -439,6 +446,7 @@ int fido2_generate_hmac_hash( const char *user_display_name, const char *user_icon, const char *askpw_icon_name, + Fido2EnrollFlags lock_with, void **ret_cid, size_t *ret_cid_size, void **ret_salt, size_t *ret_salt_size, void **ret_secret, size_t *ret_secret_size, @@ -503,6 +511,11 @@ int fido2_generate_hmac_hash( if (r < 0) return r; + if (!has_client_pin && FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Requested to lock with PIN, but FIDO2 device %s does not support it.", + device); + c = sym_fido_cred_new(); if (!c) return log_oom(); @@ -648,7 +661,7 @@ int fido2_generate_hmac_hash( log_info("Generating secret key on FIDO2 security token."); - r = sym_fido_dev_get_assert(d, a, used_pin); + r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); if (r == FIDO_ERR_UP_REQUIRED) { if (!has_up) @@ -663,7 +676,7 @@ int fido2_generate_hmac_hash( emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", emoji_enabled() ? " " : ""); - r = sym_fido_dev_get_assert(d, a, used_pin); + r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); } if (r == FIDO_ERR_ACTION_TIMEOUT) return log_error_errno(SYNTHETIC_ERRNO(ENOSTR), diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 3648ea44c71..c22deebfcc0 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -3,6 +3,12 @@ #include "macro.h" +typedef enum Fido2EnrollFlags { + FIDO2ENROLL_PIN = 1 << 0, + _FIDO2ENROLL_TYPE_MAX, + _FIDO2ENROLL_TYPE_INVALID = -EINVAL, +} Fido2EnrollFlags; + #if HAVE_LIBFIDO2 #include @@ -81,6 +87,7 @@ int fido2_use_hmac_hash( size_t cid_size, char **pins, bool up, /* user presence permitted */ + Fido2EnrollFlags required, void **ret_hmac, size_t *ret_hmac_size); @@ -93,6 +100,7 @@ int fido2_generate_hmac_hash( const char *user_display_name, const char *user_icon, const char *askpw_icon_name, + Fido2EnrollFlags lock_with, void **ret_cid, size_t *ret_cid_size, void **ret_salt, size_t *ret_salt_size, void **ret_secret, size_t *ret_secret_size, From 06f087192d27d6bbb237f8966c2fa2d6b790f7f2 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 12 Apr 2021 22:48:05 +0100 Subject: [PATCH 3/5] FIDO2: ask and record whether user presence was used to lock the volume In some cases user presence might not be required to get _a_ secret out of a FIDO2 device, but it might be required to the get actual secret that was used to lock the volume. Record whether we used it in the LUKS header JSON metadata. Let the cryptenroll user ask for the feature, but bail out if it is required by the token and the user disabled it. Enabled by default. --- man/systemd-cryptenroll.xml | 9 ++++ src/cryptenroll/cryptenroll-fido2.c | 3 +- src/cryptenroll/cryptenroll.c | 18 ++++++- src/cryptsetup/cryptsetup-fido2.c | 15 +++++- src/cryptsetup/cryptsetup.c | 4 +- src/home/homectl-fido2.c | 2 +- src/home/homework-fido2.c | 3 +- src/shared/libfido2-util.c | 84 ++++++++++++++--------------- src/shared/libfido2-util.h | 2 +- 9 files changed, 85 insertions(+), 55 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index e1c5a41aacf..5b1b60db645 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -132,6 +132,15 @@ enter a PIN when unlocking the volume. Defaults to yes. + + BOOL + + When enrolling a FIDO2 security token, controls whether to require the user to + verify presence (tap the token, the FIDO2 up feature) when unlocking the volume. + Defaults to yes. + + + PATH diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 213b7795b6d..eab8f220e4d 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -78,7 +78,8 @@ int enroll_fido2( JSON_BUILD_PAIR("fido2-credential", JSON_BUILD_BASE64(cid, cid_size)), JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_BASE64(salt, salt_size)), JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup")), - JSON_BUILD_PAIR("fido2-clientPin-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))))); + JSON_BUILD_PAIR("fido2-clientPin-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))), + JSON_BUILD_PAIR("fido2-up-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index ef6031cb1fb..5eca69f8516 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -36,7 +36,7 @@ static int *arg_wipe_slots = NULL; static size_t arg_n_wipe_slots = 0; static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT; static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */ -static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN; +static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); @@ -91,6 +91,8 @@ static int help(void) { " Enroll a FIDO2-HMAC security token\n" " --fido2-with-client-pin=BOOL\n" " Whether to require entering a PIN to unlock the volume\n" + " --fido2-with-user-presence=BOOL\n" + " Whether to require user presence to unlock the volume\n" " --tpm2-device=PATH\n" " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1,PCR2,PCR3,…\n" @@ -118,6 +120,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_TPM2_PCRS, ARG_WIPE_SLOT, ARG_FIDO2_WITH_PIN, + ARG_FIDO2_WITH_UP, }; static const struct option options[] = { @@ -128,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) { { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, { "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 }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, @@ -161,6 +165,18 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_FIDO2_WITH_UP: { + bool lock_with_up; + + r = parse_boolean_argument("--fido2-with-user-presence=", optarg, &lock_with_up); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, lock_with_up); + + break; + } + case ARG_PASSWORD: if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index 339e245575f..9a3af2d8ff3 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -81,7 +81,6 @@ int acquire_fido2_key( salt, salt_size, cid, cid_size, pins, - /* up= */ true, required, ret_decrypted_key, ret_decrypted_key_size); @@ -118,7 +117,8 @@ int find_fido2_auto_data( size_t cid_size = 0, salt_size = 0; _cleanup_free_ char *rp = NULL; int r, keyslot = -1; - Fido2EnrollFlags required = FIDO2ENROLL_PIN; /* For backward compatibility, require pin by default */ + /* For backward compatibility, require pin and presence by default */ + Fido2EnrollFlags required = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; assert(cd); assert(ret_salt); @@ -194,6 +194,17 @@ int find_fido2_auto_data( SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); } + + w = json_variant_by_key(v, "fido2-up-required"); + if (w) { + /* The "fido2-up-required" field is optional. */ + + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-up-required' field is not a boolean."); + + SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); + } } if (!cid) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 9ab42eacb9f..d47e758cd7f 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -769,9 +769,9 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (r < 0) return r; - if (FLAGS_SET(required, FIDO2ENROLL_PIN) && arg_headless) + if (FLAGS_SET(required, FIDO2ENROLL_PIN | FIDO2ENROLL_UP) && arg_headless) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), - "A PIN is required to unlock this volume, but the 'headless' parameter was set."); + "Local verification is required to unlock this volume, but the 'headless' parameter was set."); rp_id = discovered_rp_id; key_data = discovered_salt; diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index 76775ee6bd8..a2054fcf73c 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -158,7 +158,7 @@ int identity_add_fido2_parameters( /* user_display_name= */ rn ? json_variant_string(rn) : NULL, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "user-home", - FIDO2ENROLL_PIN, // FIXME: add a --lock-with-pin parameter like cryptenroll + FIDO2ENROLL_PIN | FIDO2ENROLL_UP, // FIXME: add a --lock-with-pin/up parameter like cryptenroll &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, diff --git a/src/home/homework-fido2.c b/src/home/homework-fido2.c index 818f2a5d16c..8811c00550d 100644 --- a/src/home/homework-fido2.c +++ b/src/home/homework-fido2.c @@ -28,8 +28,7 @@ int fido2_use_token( salt->salt, salt->salt_size, salt->credential.id, salt->credential.size, secret->token_pin, - h->fido2_user_presence_permitted > 0, - FIDO2ENROLL_PIN, // FIXME: add a --lock-with-pin parameter like cryptenroll + FIDO2ENROLL_PIN | (h->fido2_user_presence_permitted > 0 ? FIDO2ENROLL_UP : 0), // FIXME: add a --lock-with-pin parameter like cryptenroll &hmac, &hmac_size); if (r < 0) diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 66a312bfb6a..50e1efb2dce 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -218,8 +218,7 @@ static int fido2_use_hmac_hash_specific_token( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ - Fido2EnrollFlags required, /* client pin required */ + Fido2EnrollFlags required, /* client pin/user presence required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -256,6 +255,11 @@ static int fido2_use_hmac_hash_specific_token( "PIN required to unlock, but FIDO2 device %s does not support it.", path); + if (!has_up && FLAGS_SET(required, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "User presence test required to unlock, but FIDO2 device %s does not support it.", + path); + a = sym_fido_assert_new(); if (!a) return log_oom(); @@ -285,30 +289,20 @@ static int fido2_use_hmac_hash_specific_token( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r)); - if (has_up) { - r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE); - if (r != FIDO_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to set FIDO2 assertion user presence: %s", sym_fido_strerr(r)); - } - log_info("Asking FIDO2 token for authentication."); - r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin and without up first */ - if (r == FIDO_ERR_UP_REQUIRED && up) { - - if (!has_up) - log_warning("Weird, device asked for User Presence check, but does not advertise it as feature. Ignoring."); - - r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE); + if (has_up) { + r = sym_fido_assert_set_up(a, FLAGS_SET(required, FIDO2ENROLL_UP) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to set FIDO2 assertion user presence: %s", sym_fido_strerr(r)); + "Failed to %s FIDO2 user presence test: %s", + enable_disable(FLAGS_SET(required, FIDO2ENROLL_UP)), + sym_fido_strerr(r)); - log_info("Security token requires user presence."); - - r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin but with up now */ + if (FLAGS_SET(required, FIDO2ENROLL_UP)) + log_info("User presence required to unlock."); } + if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { char **i; @@ -321,7 +315,8 @@ static int fido2_use_hmac_hash_specific_token( if (r != FIDO_ERR_PIN_INVALID) break; } - } + } else + r = sym_fido_dev_get_assert(d, a, NULL); switch (r) { case FIDO_OK: @@ -372,8 +367,7 @@ int fido2_use_hmac_hash( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ - Fido2EnrollFlags required, /* client pin required */ + Fido2EnrollFlags required, /* client pin/user presence required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -386,7 +380,7 @@ int fido2_use_hmac_hash( return log_error_errno(r, "FIDO2 support is not installed."); if (device) - return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, up, required, ret_hmac, ret_hmac_size); + return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size); di = sym_fido_dev_info_new(allocated); if (!di) @@ -421,7 +415,7 @@ int fido2_use_hmac_hash( goto finish; } - r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, up, required, ret_hmac, ret_hmac_size); + r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size); if (!IN_SET(r, -EBADSLT, /* device doesn't understand our credential hash */ -ENODEV /* device is not a FIDO2 device with HMAC-SECRET */)) @@ -516,6 +510,11 @@ int fido2_generate_hmac_hash( "Requested to lock with PIN, but FIDO2 device %s does not support it.", device); + if (!has_up && FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Locking with user presence test requested, but FIDO2 device %s does not support it.", + device); + c = sym_fido_cred_new(); if (!c) return log_oom(); @@ -652,32 +651,27 @@ int fido2_generate_hmac_hash( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r)); - if (has_up) { - r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE); - if (r != FIDO_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to turn off FIDO2 assertion user presence: %s", sym_fido_strerr(r)); - } - log_info("Generating secret key on FIDO2 security token."); - r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); - if (r == FIDO_ERR_UP_REQUIRED) { - - if (!has_up) - log_warning("Weird, device asked for User Presence check, but does not advertise it as feature. Ignoring."); - - r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE); + if (has_up) { + r = sym_fido_assert_set_up(a, FLAGS_SET(lock_with, FIDO2ENROLL_UP) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to turn on FIDO2 assertion user presence: %s", sym_fido_strerr(r)); + "Failed to %s FIDO2 user presence test: %s", + enable_disable(FLAGS_SET(lock_with, FIDO2ENROLL_UP)), + sym_fido_strerr(r)); - log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.", - emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", - emoji_enabled() ? " " : ""); - - r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); + if (FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + log_notice("%s%sIn order to allow secret key generation, please confirm presence on security token.", + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", + emoji_enabled() ? " " : ""); } + + r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); + if (r == FIDO_ERR_UP_REQUIRED && !FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Locking without user presence test requested, but FIDO2 device %s requires it.", + device); if (r == FIDO_ERR_ACTION_TIMEOUT) return log_error_errno(SYNTHETIC_ERRNO(ENOSTR), "Token action timeout. (User didn't interact with token quickly enough.)"); diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index c22deebfcc0..9eddf5ca78e 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -5,6 +5,7 @@ typedef enum Fido2EnrollFlags { FIDO2ENROLL_PIN = 1 << 0, + FIDO2ENROLL_UP = 1 << 1, /* User presence (ie: touching token) */ _FIDO2ENROLL_TYPE_MAX, _FIDO2ENROLL_TYPE_INVALID = -EINVAL, } Fido2EnrollFlags; @@ -86,7 +87,6 @@ int fido2_use_hmac_hash( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ Fido2EnrollFlags required, void **ret_hmac, size_t *ret_hmac_size); From 896cc0da986f85980c4377d3f7073ce1f1cae778 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 13 Apr 2021 13:12:46 +0100 Subject: [PATCH 4/5] FIDO2: ask and record whether user verification was used to lock the volume Some tokens support authorization via fingerprint or other biometric ID. Add support for "user verification" to cryptenroll and cryptsetup. Disable by default, as it is still quite uncommon. --- man/systemd-cryptenroll.xml | 8 ++++++ src/cryptenroll/cryptenroll-fido2.c | 3 ++- src/cryptenroll/cryptenroll.c | 16 +++++++++++ src/cryptsetup/cryptsetup-fido2.c | 11 ++++++++ src/cryptsetup/cryptsetup.c | 2 +- src/shared/libfido2-util.c | 42 +++++++++++++++++++++++++++-- src/shared/libfido2-util.h | 2 ++ 7 files changed, 80 insertions(+), 4 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 5b1b60db645..c7f4e63f600 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -141,6 +141,14 @@ + + BOOL + + When enrolling a FIDO2 security token, controls whether to require user verification + when unlocking the volume (the FIDO2 uv feature)). Defaults to no. + + + PATH diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index eab8f220e4d..3ba7866738e 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -79,7 +79,8 @@ int enroll_fido2( JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_BASE64(salt, salt_size)), JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup")), JSON_BUILD_PAIR("fido2-clientPin-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))), - JSON_BUILD_PAIR("fido2-up-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))))); + JSON_BUILD_PAIR("fido2-up-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))), + JSON_BUILD_PAIR("fido2-uv-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 5eca69f8516..559a3468043 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -93,6 +93,8 @@ static int help(void) { " Whether to require entering a PIN to unlock the volume\n" " --fido2-with-user-presence=BOOL\n" " Whether to require user presence to unlock the volume\n" + " --fido2-with-user-verification=BOOL\n" + " Whether to require user verification to unlock the volume\n" " --tpm2-device=PATH\n" " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1,PCR2,PCR3,…\n" @@ -121,6 +123,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_WIPE_SLOT, ARG_FIDO2_WITH_PIN, ARG_FIDO2_WITH_UP, + ARG_FIDO2_WITH_UV, }; static const struct option options[] = { @@ -132,6 +135,7 @@ static int parse_argv(int argc, char *argv[]) { { "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-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, @@ -177,6 +181,18 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_FIDO2_WITH_UV: { + bool lock_with_uv; + + r = parse_boolean_argument("--fido2-with-user-verification=", optarg, &lock_with_uv); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, lock_with_uv); + + break; + } + case ARG_PASSWORD: if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index 9a3af2d8ff3..b21f970db77 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -205,6 +205,17 @@ int find_fido2_auto_data( SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); } + + w = json_variant_by_key(v, "fido2-uv-required"); + if (w) { + /* The "fido2-uv-required" field is optional. */ + + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); + + SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); + } } if (!cid) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index d47e758cd7f..e8e5b6dbfc2 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -769,7 +769,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (r < 0) return r; - if (FLAGS_SET(required, FIDO2ENROLL_PIN | FIDO2ENROLL_UP) && arg_headless) + if (FLAGS_SET(required, FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV) && arg_headless) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Local verification is required to unlock this volume, but the 'headless' parameter was set."); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 50e1efb2dce..ec69793f7cf 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -25,6 +25,7 @@ int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL; int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL; int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL; int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL; +int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t) = NULL; size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL; char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL; void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL; @@ -84,6 +85,7 @@ int dlopen_libfido2(void) { DLSYM_ARG(fido_assert_set_hmac_salt), DLSYM_ARG(fido_assert_set_rp), DLSYM_ARG(fido_assert_set_up), + DLSYM_ARG(fido_assert_set_uv), DLSYM_ARG(fido_cbor_info_extensions_len), DLSYM_ARG(fido_cbor_info_extensions_ptr), DLSYM_ARG(fido_cbor_info_free), @@ -225,7 +227,7 @@ static int fido2_use_hmac_hash_specific_token( _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL; _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL; _cleanup_(erase_and_freep) void *hmac_copy = NULL; - bool has_up, has_client_pin; + bool has_up, has_client_pin, has_uv; size_t hmac_size; const void *hmac; int r; @@ -246,7 +248,7 @@ static int fido2_use_hmac_hash_specific_token( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r)); - r = verify_features(d, path, LOG_ERR, NULL, &has_client_pin, &has_up, NULL); + r = verify_features(d, path, LOG_ERR, NULL, &has_client_pin, &has_up, &has_uv); if (r < 0) return r; @@ -260,6 +262,11 @@ static int fido2_use_hmac_hash_specific_token( "User presence test required to unlock, but FIDO2 device %s does not support it.", path); + if (!has_uv && FLAGS_SET(required, FIDO2ENROLL_UV)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "User verification required to unlock, but FIDO2 device %s does not support it.", + path); + a = sym_fido_assert_new(); if (!a) return log_oom(); @@ -303,6 +310,18 @@ static int fido2_use_hmac_hash_specific_token( log_info("User presence required to unlock."); } + if (has_uv) { + r = sym_fido_assert_set_uv(a, FLAGS_SET(required, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to %s FIDO2 user verification: %s", + enable_disable(FLAGS_SET(required, FIDO2ENROLL_UV)), + sym_fido_strerr(r)); + + if (FLAGS_SET(required, FIDO2ENROLL_UV)) + log_info("User verification required to unlock."); + } + if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { char **i; @@ -515,6 +534,11 @@ int fido2_generate_hmac_hash( "Locking with user presence test requested, but FIDO2 device %s does not support it.", device); + if (!has_uv && FLAGS_SET(lock_with, FIDO2ENROLL_UV)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Locking with user verification requested, but FIDO2 device %s does not support it.", + device); + c = sym_fido_cred_new(); if (!c) return log_oom(); @@ -667,6 +691,20 @@ int fido2_generate_hmac_hash( emoji_enabled() ? " " : ""); } + if (has_uv) { + r = sym_fido_assert_set_uv(a, FLAGS_SET(lock_with, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to %s FIDO user verification: %s", + enable_disable(FLAGS_SET(lock_with, FIDO2ENROLL_UV)), + sym_fido_strerr(r)); + + if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) + log_notice("%s%sIn order to allow secret key generation, please verify user on security token.", + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", + emoji_enabled() ? " " : ""); + } + r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); if (r == FIDO_ERR_UP_REQUIRED && !FLAGS_SET(lock_with, FIDO2ENROLL_UP)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 9eddf5ca78e..1b31577e06c 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -6,6 +6,7 @@ typedef enum Fido2EnrollFlags { FIDO2ENROLL_PIN = 1 << 0, FIDO2ENROLL_UP = 1 << 1, /* User presence (ie: touching token) */ + FIDO2ENROLL_UV = 1 << 2, /* User verification (ie: fingerprint) */ _FIDO2ENROLL_TYPE_MAX, _FIDO2ENROLL_TYPE_INVALID = -EINVAL, } Fido2EnrollFlags; @@ -23,6 +24,7 @@ extern int (*sym_fido_assert_set_extensions)(fido_assert_t *, int); extern int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t); extern int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *); extern int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t); +extern int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t); extern size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *); extern char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *); extern void (*sym_fido_cbor_info_free)(fido_cbor_info_t **); From 8f214355c6da8f28d7e742253bbe5ff56742d439 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 21 Apr 2021 23:01:47 +0100 Subject: [PATCH 5/5] FIDO2: if defined, check for FIDO_ERR_UV_BLOCKED Newer libfido versions added this error, so check for it since it can help the user with a more specific message --- src/shared/libfido2-util.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index ec69793f7cf..24c9b76b7ad 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -349,6 +349,11 @@ static int fido2_use_hmac_hash_specific_token( case FIDO_ERR_PIN_AUTH_BLOCKED: return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD), "PIN of security token is blocked, please remove/reinsert token."); +#ifdef FIDO_ERR_UV_BLOCKED + case FIDO_ERR_UV_BLOCKED: + return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD), + "Verification of security token is blocked, please remove/reinsert token."); +#endif case FIDO_ERR_PIN_INVALID: return log_error_errno(SYNTHETIC_ERRNO(ENOLCK), "PIN of security token incorrect."); @@ -633,6 +638,11 @@ int fido2_generate_hmac_hash( if (r == FIDO_ERR_PIN_AUTH_BLOCKED) return log_notice_errno(SYNTHETIC_ERRNO(EPERM), "Token PIN is currently blocked, please remove and reinsert token."); +#ifdef FIDO_ERR_UV_BLOCKED + if (r == FIDO_ERR_UV_BLOCKED) + return log_notice_errno(SYNTHETIC_ERRNO(EPERM), + "Token verification is currently blocked, please remove and reinsert token."); +#endif if (r == FIDO_ERR_ACTION_TIMEOUT) return log_error_errno(SYNTHETIC_ERRNO(ENOSTR), "Token action timeout. (User didn't interact with token quickly enough.)");