]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
libfido2-util: Perform pre-flight check for credentials in token 25268/head
authorMkfsSion <mkfssion@mkfssion.com>
Sat, 29 Oct 2022 18:29:02 +0000 (14:29 -0400)
committerPeter Cai <peter@typeblog.net>
Fri, 11 Nov 2022 23:51:07 +0000 (18:51 -0500)
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).

src/shared/libfido2-util.c

index c246af508291bfedab18b196a9e1921537d66b1e..a2bf14ec487299fc450491422fb3b2277f13b94b 100644 (file)
@@ -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 */