diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 097cf7518b..d5719bc463 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -128,8 +128,11 @@ BOOL - When enrolling a FIDO2 security token, controls whether to require the user to - enter a PIN when unlocking the volume. Defaults to yes. + When enrolling a FIDO2 security token, controls whether to require the user to enter + a PIN when unlocking the volume (the FIDO2 clientPin feature). Defaults to + yes. (Note: this setting is without effect if the security token does not support + the clientPin feature at all, or does not allow enabling or disabling + it.) @@ -137,7 +140,8 @@ 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. + Defaults to yes. (Note: this setting is without effect if the security token does not support + the up feature at all, or does not allow enabling or disabling it.) @@ -145,8 +149,9 @@ BOOL When enrolling a FIDO2 security token, controls whether to require user verification - when unlocking the volume (the FIDO2 uv feature)). Defaults to no. - + when unlocking the volume (the FIDO2 uv feature). Defaults to + no. (Note: this setting is without effect if the security token does not support + the uv feature at all, or does not allow enabling or disabling it.) diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 3ba7866738..fbf76ee586 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -45,7 +45,8 @@ int enroll_fido2( &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, - NULL); + NULL, + &lock_with); if (r < 0) return r; diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index b21f970db7..6e400e44e3 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -117,8 +117,7 @@ int find_fido2_auto_data( size_t cid_size = 0, salt_size = 0; _cleanup_free_ char *rp = NULL; int r, keyslot = -1; - /* For backward compatibility, require pin and presence by default */ - Fido2EnrollFlags required = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; + Fido2EnrollFlags required = 0; assert(cd); assert(ret_salt); @@ -193,7 +192,8 @@ int find_fido2_auto_data( "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); - } + } else + required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */ w = json_variant_by_key(v, "fido2-up-required"); if (w) { @@ -204,7 +204,8 @@ int find_fido2_auto_data( "FIDO2 token data's 'fido2-up-required' field is not a boolean."); SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); - } + } else + required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */ w = json_variant_by_key(v, "fido2-uv-required"); if (w) { @@ -215,7 +216,8 @@ int find_fido2_auto_data( "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); - } + } else + required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */ } if (!cid) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 74bd071c10..f0826c47e5 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -758,7 +758,11 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( cid = arg_fido2_cid; cid_size = arg_fido2_cid_size; - required = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; /* For backwards compatibility, PIN+presence is required by default. */ + /* For now and for compatibility, if the user explicitly configured FIDO2 support and we do + * not read FIDO2 metadata off the LUKS2 header, default to the systemd 248 logic, where we + * use PIN + UP when needed, and do not configure UV at all. Eventually, we should make this + * explicitly configurable. */ + required = FIDO2ENROLL_PIN_IF_NEEDED | FIDO2ENROLL_UP_IF_NEEDED | FIDO2ENROLL_UV_OMIT; } else { r = find_fido2_auto_data( cd, diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index a2054fcf73..e8e9826bb9 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -162,7 +162,8 @@ int identity_add_fido2_parameters( &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, - &used_pin); + &used_pin, + NULL); if (r < 0) return r; diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 24c9b76b7a..0272d0bde7 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -310,7 +310,7 @@ static int fido2_use_hmac_hash_specific_token( log_info("User presence required to unlock."); } - if (has_uv) { + if (has_uv && !FLAGS_SET(required, FIDO2ENROLL_UV_OMIT)) { 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), @@ -322,20 +322,98 @@ static int fido2_use_hmac_hash_specific_token( log_info("User verification required to unlock."); } - if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { - char **i; + for (;;) { + bool retry_with_up = false, retry_with_pin = false; - if (!has_client_pin) - log_warning("Weird, device asked for client PIN, but does not advertise it as feature. Ignoring."); + if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { + char **i; - /* OK, we needed a pin, try with all pins in turn */ - STRV_FOREACH(i, pins) { - r = sym_fido_dev_get_assert(d, a, *i); - if (r != FIDO_ERR_PIN_INVALID) - break; + /* OK, we need a pin, try with all pins in turn */ + if (strv_isempty(pins)) + r = FIDO_ERR_PIN_REQUIRED; + else + STRV_FOREACH(i, pins) { + r = sym_fido_dev_get_assert(d, a, *i); + if (r != FIDO_ERR_PIN_INVALID) + break; + } + + } else + r = sym_fido_dev_get_assert(d, a, NULL); + + /* In some conditions, where a PIN or UP is required we might accept that. Let's check the + * conditions and if so try immediately again. */ + + switch (r) { + + case FIDO_ERR_UP_REQUIRED: + /* So the token asked for "up". Try to turn it on, for compat with systemd 248 and try again. */ + + if (!has_up) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for user presence check but doesn't advertise 'up' feature."); + + if (FLAGS_SET(required, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for user presence check but was already enabled."); + + if (FLAGS_SET(required, FIDO2ENROLL_UP_IF_NEEDED)) { + log_info("User presence required to unlock."); + retry_with_up = true; + } + + break; + + case FIDO_ERR_UNSUPPORTED_OPTION: + /* AuthenTrend ATKey.Pro returns this instead of FIDO_ERR_UP_REQUIRED, let's handle + * it gracefully (also see below.) */ + + if (has_up && (required & (FIDO2ENROLL_UP|FIDO2ENROLL_UP_IF_NEEDED)) == FIDO2ENROLL_UP_IF_NEEDED) { + log_notice("Got unsupported option error when when user presence test is turned off. Trying with user presence test turned on."); + retry_with_up = true; + } + + break; + + case FIDO_ERR_PIN_REQUIRED: + /* A pin was requested. Maybe supply one, if we are configured to do so on request */ + + if (!has_client_pin) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for PIN but doesn't advertise 'clientPin' feature."); + + if (FLAGS_SET(required, FIDO2ENROLL_PIN) && !strv_isempty(pins)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for PIN but one was already supplied."); + + if ((required & (FIDO2ENROLL_PIN|FIDO2ENROLL_PIN_IF_NEEDED)) == FIDO2ENROLL_PIN_IF_NEEDED) { + /* If a PIN so far wasn't specified but is requested by the device, and + * FIDO2ENROLL_PIN_IF_NEEDED is set, then provide it */ + log_debug("Retrying to create credential with PIN."); + retry_with_pin = true; + } + + break; + + default: + break; } - } else - r = sym_fido_dev_get_assert(d, a, NULL); + + if (!retry_with_up && !retry_with_pin) + break; + + if (retry_with_up) { + r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to enable FIDO2 user presence test: %s", sym_fido_strerr(r)); + + required |= FIDO2ENROLL_UP; + } + + if (retry_with_pin) + required |= FIDO2ENROLL_PIN; + } switch (r) { case FIDO_OK: @@ -468,7 +546,8 @@ int fido2_generate_hmac_hash( 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) { + char **ret_usedpin, + Fido2EnrollFlags *ret_locked_with) { _cleanup_(erase_and_freep) void *salt = NULL, *secret_copy = NULL; _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL; @@ -503,6 +582,7 @@ int fido2_generate_hmac_hash( */ assert(device); + assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0); r = dlopen_libfido2(); if (r < 0) @@ -529,20 +609,21 @@ 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); + /* While enrolling degrade gracefully if the requested feature set isn't available, but let the user know */ + if (!has_client_pin && FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) { + log_notice("Requested to lock with PIN, but FIDO2 device %s does not support it, disabling.", device); + lock_with &= ~FIDO2ENROLL_PIN; + } - 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); + if (!has_up && FLAGS_SET(lock_with, FIDO2ENROLL_UP)) { + log_notice("Locking with user presence test requested, but FIDO2 device %s does not support it, disabling.", device); + lock_with &= ~FIDO2ENROLL_UP; + } - 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); + if (!has_uv && FLAGS_SET(lock_with, FIDO2ENROLL_UV)) { + log_notice("Locking with user verification requested, but FIDO2 device %s does not support it, disabling.", device); + lock_with &= ~FIDO2ENROLL_UV; + } c = sym_fido_cred_new(); if (!c) @@ -592,6 +673,9 @@ int fido2_generate_hmac_hash( "Failed to turn off FIDO2 user verification option of credential: %s", sym_fido_strerr(r)); } + /* As per specification "up" is assumed to be implicit when making credentials, hence we don't + * explicitly enable/disable it here */ + log_info("Initializing FIDO2 credential on security token."); log_notice("%s%s(Hint: This might require verification of user presence on security token.)", @@ -600,13 +684,15 @@ int fido2_generate_hmac_hash( r = sym_fido_dev_make_cred(d, c, NULL); if (r == FIDO_ERR_PIN_REQUIRED) { + + if (!has_client_pin) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for PIN but doesn't advertise 'clientPin' feature."); + for (;;) { _cleanup_(strv_free_erasep) char **pin = NULL; char **i; - if (!has_client_pin) - log_warning("Weird, device asked for client PIN, but does not advertise it as feature. Ignoring."); - r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", "fido2-pin", USEC_INFINITY, 0, &pin); if (r < 0) return log_error_errno(r, "Failed to acquire user PIN: %m"); @@ -715,11 +801,75 @@ int fido2_generate_hmac_hash( 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); + for (;;) { + bool retry_with_up = false, retry_with_pin = false; + + r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); + + switch (r) { + + case FIDO_ERR_UP_REQUIRED: + /* If the token asks for "up" when we turn off, then this might be a feature that + * isn't optional. Let's enable it */ + + if (!has_up) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for user presence check but doesn't advertise 'up' feature."); + + if (FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for user presence check but was already enabled."); + + log_notice("Locking without user presence test requested, but FIDO2 device %s requires it, enabling.", device); + retry_with_up = true; + break; + + case FIDO_ERR_UNSUPPORTED_OPTION: + /* AuthenTrend ATKey.Pro says it supports "up", but if we disable it it will fail + * with FIDO_ERR_UNSUPPORTED_OPTION, probably because it isn't actually + * optional. Let's see if turning it on works. This is very similar to the + * FIDO_ERR_UP_REQUIRED case, but since the error is so vague we implement it + * slightly more defensively. */ + + if (has_up && !FLAGS_SET(lock_with, FIDO2ENROLL_UP)) { + log_notice("Got unsupported option error when when user presence test is turned off. Trying with user presence test turned on."); + retry_with_up = true; + } + + break; + + case FIDO_ERR_PIN_REQUIRED: + if (!has_client_pin) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for client PIN check but doesn't advertise 'clientPin' feature."); + + if (FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Token asks for user client PIN check but was already enabled."); + + log_debug("Token requires PIN for assertion, enabling."); + retry_with_pin = true; + break; + + default: + break; + } + + if (!retry_with_up && !retry_with_pin) + break; + + if (retry_with_up) { + r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enable FIDO2 user presence test: %s", sym_fido_strerr(r)); + + lock_with |= FIDO2ENROLL_UP; + } + + if (retry_with_pin) + lock_with |= FIDO2ENROLL_PIN; + } + if (r == FIDO_ERR_ACTION_TIMEOUT) return log_error_errno(SYNTHETIC_ERRNO(ENOSTR), "Token action timeout. (User didn't interact with token quickly enough.)"); @@ -751,6 +901,9 @@ int fido2_generate_hmac_hash( if (ret_usedpin) *ret_usedpin = TAKE_PTR(used_pin); + if (ret_locked_with) + *ret_locked_with = lock_with; + return 0; } #endif diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 1b31577e06..5640cca5e3 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -7,6 +7,9 @@ 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_PIN_IF_NEEDED = 1 << 3, /* If auth doesn't work without PIN ask for one, as in systemd 248 */ + FIDO2ENROLL_UP_IF_NEEDED = 1 << 4, /* If auth doesn't work without UP, enable it, as in systemd 248 */ + FIDO2ENROLL_UV_OMIT = 1 << 5, /* Leave "uv" untouched, as in systemd 248 */ _FIDO2ENROLL_TYPE_MAX, _FIDO2ENROLL_TYPE_INVALID = -EINVAL, } Fido2EnrollFlags; @@ -106,7 +109,8 @@ int fido2_generate_hmac_hash( 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); + char **ret_usedpin, + Fido2EnrollFlags *ret_locked_with); #endif