From: Lennart Poettering Date: Thu, 27 May 2021 20:55:39 +0000 (+0200) Subject: cryptsetup: revert to systemd 248 up/pin/uv FIDO2 settings when we don't have LUKS2... X-Git-Tag: v249-rc1~129^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3cc00ba60594cbce0aa416e54b846988376685f8;p=thirdparty%2Fsystemd.git cryptsetup: revert to systemd 248 up/pin/uv FIDO2 settings when we don't have LUKS2 JSON data telling us the precise configuration Let's improve compatibility with systemd 248 enrollments of FIDO2 keys: if we have no information about the up/uv/pin settings, let's try to determine them automatically, i.e. use up and pin if needed. This only has an effect on LUKS2 volumes where a FIDO2 key was enrolled with systemd 248 and thus the JSON data lacks the up/uv/pin fields. It also matters if the user configured FIDO2 parameters explicitly via crypttab options, so that the JSON data is not used. For newer enrollments we'll stick to the explicit settings, as that's generally much safer and robust. --- diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index b21f970db77..6e400e44e37 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 74bd071c10e..f0826c47e56 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/shared/libfido2-util.c b/src/shared/libfido2-util.c index 3da64b549a5..0272d0bde7e 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; - /* 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; + if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { + char **i; + + /* 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; } - } else - r = sym_fido_dev_get_assert(d, a, NULL); + + 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; + } + + 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: diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index bcf3c73706d..5640cca5e39 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;