]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fido2: fix enrolling when UV is required ('alwaysUv')
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 24 Nov 2025 20:07:00 +0000 (20:07 +0000)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 18 Dec 2025 09:56:43 +0000 (10:56 +0100)
When a Yubikey or other fido2 device has FIPS mode enabled, UV will
always be required and cannot be disabled. Unhelpfully, when it is not
sent down, the hardware token (not the library) returns a generic
FIDO_ERR_MISSING_PARAMETER:

$ systemd-cryptenroll --fido2-device=auto --fido2-with-client-pin=no foo
<...>
libfido2: rx: payload_len=1
libfido2: fido_rx: buf=0x55c9ce170940, len=1
libfido2: 0000: 14
libfido2: cbor_parse_reply: blob[0]=0x14
libfido2: fido_dev_get_assert_rx: adjust_assert_count
Failed to ask token for assertion: FIDO_ERR_MISSING_PARAMETER

This can be set even when the token doesn't support UV (as in, fingerprint
reader), in which case using the PIN is required.

Check if 'alwaysUv' is set, and if not already configured, force
either UV (if available) or PIN when enrolling.

src/shared/libfido2-util.c

index 5bcd5388b0c23b9353fcd06290ce98bb58249303..338b52881e8ab7d05994038a781005a8559c20ab 100644 (file)
@@ -158,13 +158,14 @@ static int verify_features(
                 bool *ret_has_rk,
                 bool *ret_has_client_pin,
                 bool *ret_has_up,
-                bool *ret_has_uv) {
+                bool *ret_has_uv,
+                bool *ret_has_always_uv) {
 
         _cleanup_(fido_cbor_info_free_wrapper) fido_cbor_info_t *di = NULL;
         bool found_extension = false;
         char **e, **o;
         const bool *b;
-        bool has_rk = false, has_client_pin = false, has_up = true, has_uv = false; /* Defaults are per table in 5.4 in FIDO2 spec */
+        bool has_rk = false, has_client_pin = false, has_up = true, has_uv = false, has_always_uv = false; /* Defaults are per table in 5.4 in FIDO2 spec */
         size_t n;
         int r;
 
@@ -205,6 +206,8 @@ static int verify_features(
                         has_up = b[i];
                 if (streq(o[i], "uv"))
                         has_uv = b[i];
+                if (streq(o[i], "alwaysUv"))
+                        has_always_uv = b[i];
         }
 
         if (!found_extension)
@@ -215,11 +218,13 @@ static int verify_features(
         log_debug("Has rk ('Resident Key') support: %s\n"
                   "Has clientPin support: %s\n"
                   "Has up ('User Presence') support: %s\n"
-                  "Has uv ('User Verification') support: %s\n",
+                  "Has uv ('User Verification') support: %s\n"
+                  "Has alwaysUv ('User Verification' required): %s\n",
                   yes_no(has_rk),
                   yes_no(has_client_pin),
                   yes_no(has_up),
-                  yes_no(has_uv));
+                  yes_no(has_uv),
+                  yes_no(has_always_uv));
 
         if (ret_has_rk)
                 *ret_has_rk = has_rk;
@@ -229,6 +234,8 @@ static int verify_features(
                 *ret_has_up = has_up;
         if (ret_has_uv)
                 *ret_has_uv = has_uv;
+        if (ret_has_always_uv)
+                *ret_has_always_uv = has_always_uv;
 
         return 0;
 }
@@ -320,7 +327,7 @@ static int fido2_is_cred_in_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, NULL, &has_up, &has_uv);
+        r = verify_features(d, path, LOG_ERR, /* ret_has_rk= */ NULL, /* ret_has_client_pin= */ NULL, &has_up, &has_uv, /* ret_has_always_uv= */ NULL);
         if (r == -ENODEV) { /* Not a FIDO2 device or lacking HMAC-SECRET extension */
                 log_debug_errno(r, "%s is not a FIDO2 device, or it lacks the hmac-secret extension", path);
                 return false;
@@ -432,7 +439,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, &has_uv);
+        r = verify_features(d, path, LOG_ERR, /* ret_has_rk= */ NULL, &has_client_pin, &has_up, &has_uv, /* ret_has_always_uv= */ NULL);
         if (r < 0)
                 return r;
 
@@ -745,7 +752,7 @@ int fido2_generate_hmac_hash(
         _cleanup_(fido_cred_free_wrapper) fido_cred_t *c = NULL;
         _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
         _cleanup_(erase_and_freep) char *used_pin = NULL;
-        bool has_rk, has_client_pin, has_up, has_uv;
+        bool has_rk, has_client_pin, has_up, has_uv, has_always_uv;
         _cleanup_free_ char *cid_copy = NULL;
         size_t cid_size, secret_size;
         const void *cid, *secret;
@@ -787,7 +794,7 @@ int fido2_generate_hmac_hash(
                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
                                        "Failed to open FIDO2 device %s: %s", device, sym_fido_strerr(r));
 
-        r = verify_features(d, device, LOG_ERR, &has_rk, &has_client_pin, &has_up, &has_uv);
+        r = verify_features(d, device, LOG_ERR, &has_rk, &has_client_pin, &has_up, &has_uv, &has_always_uv);
         if (r < 0)
                 return r;
 
@@ -807,6 +814,19 @@ int fido2_generate_hmac_hash(
                 lock_with &= ~FIDO2ENROLL_UV;
         }
 
+        if (has_always_uv && !(FLAGS_SET(lock_with, FIDO2ENROLL_PIN) || FLAGS_SET(lock_with, FIDO2ENROLL_UV))) {
+                if (has_uv) {
+                        log_notice("FIDO2 device %s enforces 'always user verification', forcing user verification.", device);
+                        lock_with |= FIDO2ENROLL_UV;
+                } else if (has_client_pin) {
+                        log_notice("FIDO2 device %s enforces 'always user verification', but doesn't support user verification, forcing PIN.", device);
+                        lock_with |= FIDO2ENROLL_PIN;
+                } else
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "FIDO2 device %s enforces 'always user verification', but doesn't support user verification or PIN, cannot proceed.",
+                                               device);
+        }
+
         c = sym_fido_cred_new();
         if (!c)
                 return log_oom();
@@ -1124,7 +1144,8 @@ static int check_device_is_fido2_with_hmac_secret(
                 bool *ret_has_rk,
                 bool *ret_has_client_pin,
                 bool *ret_has_up,
-                bool *ret_has_uv) {
+                bool *ret_has_uv,
+                bool *ret_has_always_uv) {
 
         _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
         int r;
@@ -1138,9 +1159,9 @@ static int check_device_is_fido2_with_hmac_secret(
                 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_DEBUG, ret_has_rk, ret_has_client_pin, ret_has_up, ret_has_uv);
+        r = verify_features(d, path, LOG_DEBUG, ret_has_rk, ret_has_client_pin, ret_has_up, ret_has_uv, ret_has_always_uv);
         if (r == -ENODEV) { /* Not a FIDO2 device, or not implementing 'hmac-secret' */
-                *ret_has_rk = *ret_has_client_pin = *ret_has_up = *ret_has_uv = false;
+                *ret_has_rk = *ret_has_client_pin = *ret_has_up = *ret_has_uv = *ret_has_always_uv = false;
                 return false;
         }
         if (r < 0)
@@ -1178,7 +1199,7 @@ int fido2_list_devices(void) {
                 goto finish;
         }
 
-        t = table_new("path", "manufacturer", "product", "compatible", "rk", "clientpin", "up", "uv");
+        t = table_new("path", "manufacturer", "product", "compatible", "rk", "clientpin", "up", "uv", "alwaysuv");
         if (!t) {
                 r = log_oom();
                 goto finish;
@@ -1186,7 +1207,7 @@ int fido2_list_devices(void) {
 
         for (size_t i = 0; i < found; i++) {
                 const fido_dev_info_t *entry;
-                bool has_rk, has_client_pin, has_up, has_uv;
+                bool has_rk, has_client_pin, has_up, has_uv, has_always_uv;
 
                 entry = sym_fido_dev_info_ptr(di, i);
                 if (!entry) {
@@ -1195,7 +1216,7 @@ int fido2_list_devices(void) {
                         goto finish;
                 }
 
-                r = check_device_is_fido2_with_hmac_secret(sym_fido_dev_info_path(entry), &has_rk, &has_client_pin, &has_up, &has_uv);
+                r = check_device_is_fido2_with_hmac_secret(sym_fido_dev_info_path(entry), &has_rk, &has_client_pin, &has_up, &has_uv, &has_always_uv);
                 if (r < 0)
                         goto finish;
                 bool compatible = r > 0;
@@ -1209,7 +1230,8 @@ int fido2_list_devices(void) {
                                 TABLE_BOOLEAN_CHECKMARK, has_rk,
                                 TABLE_BOOLEAN_CHECKMARK, has_client_pin,
                                 TABLE_BOOLEAN_CHECKMARK, has_up,
-                                TABLE_BOOLEAN_CHECKMARK, has_uv);
+                                TABLE_BOOLEAN_CHECKMARK, has_uv,
+                                TABLE_BOOLEAN_CHECKMARK, has_always_uv);
                 if (r < 0) {
                         table_log_add_error(r);
                         goto finish;
@@ -1227,7 +1249,8 @@ int fido2_list_devices(void) {
                        "%1$sLegend: RK        %2$s Resident key%3$s\n"
                        "%1$s        CLIENTPIN %2$s PIN request%3$s\n"
                        "%1$s        UP        %2$s User presence%3$s\n"
-                       "%1$s        UV        %2$s User verification%3$s\n",
+                       "%1$s        UV        %2$s User verification%3$s\n"
+                       "%1$s        AlwaysUV  %2$s User verification Required%3$s\n",
                        ansi_grey(),
                        glyph(GLYPH_ARROW_RIGHT),
                        ansi_normal());
@@ -1287,7 +1310,8 @@ int fido2_find_device_auto(char **ret) {
                         /* ret_has_rk= */ NULL,
                         /* ret_has_client_pin= */ NULL,
                         /* ret_has_up= */ NULL,
-                        /* ret_has_uv= */ NULL);
+                        /* ret_has_uv= */ NULL,
+                        /* ret_has_always_uv= */ NULL);
         if (r < 0)
                 goto finish;
         if (!r) {