From: MkfsSion Date: Sat, 29 Oct 2022 18:29:02 +0000 (-0400) Subject: libfido2-util: Perform pre-flight check for credentials in token X-Git-Tag: v253-rc1~543^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=32f57b098636412028ff302d3166bc3713e20e8e;p=thirdparty%2Fsystemd.git libfido2-util: Perform pre-flight check for credentials in token Do not attempt to decrypt using a key slot unless its corresponding credential is found on an available FIDO2 token. Avoids multiple touches / confirmations when unlocking a LUKS2 device with multiple FIDO2 tokens enrolled. Partially fixes #19208 (when the libcryptsetup plugin is in use). --- diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index c246af50829..a2bf14ec487 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -257,6 +257,94 @@ static int fido2_common_assert_error_handle(int r) { } } +static int fido2_is_cred_in_specific_token( + const char *path, + const char *rp_id, + const void *cid, + size_t cid_size, + char **pins, + Fido2EnrollFlags flags) { + + assert(path); + assert(rp_id); + assert(cid); + assert(cid_size); + + _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL; + _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL; + bool has_up = false, has_uv = false; + int r; + + d = sym_fido_dev_new(); + if (!d) + return log_oom(); + + r = sym_fido_dev_open(d, path); + if (r != FIDO_OK) + 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, NULL, &has_up, &has_uv); + if (r < 0) + return r; + + a = sym_fido_assert_new(); + if (!a) + return log_oom(); + + r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size); + if (r < 0) + return r; + + /* According to CTAP 2.1 specification, to do pre-flight we need to set up option to false + * with optionally pinUvAuthParam in assertion[1]. But for authenticator that doesn't support + * user presence, once up option is present, the authenticator may return CTAP2_ERR_UNSUPPORTED_OPTION[2]. + * So we simplely omit the option in that case. + * Reference: + * 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight + * 2: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion (in step 5) + */ + if (has_up) + r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE); + else + r = sym_fido_assert_set_up(a, FIDO_OPT_OMIT); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to set assertion user presence: %s", sym_fido_strerr(r)); + + + /* According to CTAP 2.1 specification, if the credential is marked as userVerificationRequired, + * we may pass pinUvAuthParam parameter or set uv option to true in order to check whether the + * credential is in the token. Here we choose to use PIN (pinUvAuthParam) to achieve this. + * If we don't do that, the authenticator will remove the credential from the applicable + * credentials list, hence CTAP2_ERR_NO_CREDENTIALS error will be returned. + * Please see + * https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-getAssert-authnr-alg + * step 7.4 for detailed information. + */ + if (FLAGS_SET(flags, FIDO2ENROLL_UV) && has_uv) { + 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); + + switch (r) { + case FIDO_OK: + return true; + case FIDO_ERR_NO_CREDENTIALS: + return false; + default: + return fido2_common_assert_error_handle(r); + } +} + static int fido2_use_hmac_hash_specific_token( const char *path, const char *rp_id, @@ -544,6 +632,30 @@ int fido2_use_hmac_hash( goto finish; } + r = fido2_is_cred_in_specific_token(path, rp_id, cid, cid_size, pins, required); + /* We handle PIN related errors here since we have used PIN in the check. + * If PIN error happens, we can avoid pin retries decreased the second time. */ + if (IN_SET(r, -ENOANO, /* → pin required */ + -ENOLCK, /* → pin incorrect */ + -EOWNERDEAD)) { /* → uv blocked */ + /* If it's not the last device, try next one */ + if (i < found - 1) + continue; + + /* -EOWNERDEAD is returned when uv is blocked. Map it to EAGAIN so users can reinsert + * the token and try again. */ + if (r == -EOWNERDEAD) + r = -EAGAIN; + goto finish; + } else if (r == -ENODEV) /* not a FIDO2 device or lacking HMAC-SECRET extension */ + continue; + else if (r < 0) + log_error_errno(r, "Failed to determine whether the credential is in the token, trying anyway: %m"); + else if (r == 0) { + log_debug("The credential is not in the token %s, skipping.", path); + continue; + } + 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 */