1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "libfido2-util.h"
4 
5 #if HAVE_LIBFIDO2
6 #include "alloc-util.h"
7 #include "ask-password-api.h"
8 #include "dlfcn-util.h"
9 #include "format-table.h"
10 #include "glyph-util.h"
11 #include "log.h"
12 #include "memory-util.h"
13 #include "random-util.h"
14 #include "strv.h"
15 #include "unistd.h"
16 
17 static void *libfido2_dl = NULL;
18 
19 int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t) = NULL;
20 void (*sym_fido_assert_free)(fido_assert_t **) = NULL;
21 size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t) = NULL;
22 const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t) = NULL;
23 fido_assert_t* (*sym_fido_assert_new)(void) = NULL;
24 int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t) = NULL;
25 int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL;
26 int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL;
27 int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL;
28 int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL;
29 int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t) = NULL;
30 size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL;
31 char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL;
32 void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL;
33 fido_cbor_info_t* (*sym_fido_cbor_info_new)(void) = NULL;
34 size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *) = NULL;
35 char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *) = NULL;
36 const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *) = NULL;
37 void (*sym_fido_cred_free)(fido_cred_t **) = NULL;
38 size_t (*sym_fido_cred_id_len)(const fido_cred_t *) = NULL;
39 const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *) = NULL;
40 fido_cred_t* (*sym_fido_cred_new)(void) = NULL;
41 int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t) = NULL;
42 int (*sym_fido_cred_set_extensions)(fido_cred_t *, int) = NULL;
43 int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t) = NULL;
44 int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *) = NULL;
45 int (*sym_fido_cred_set_type)(fido_cred_t *, int) = NULL;
46 int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *) = NULL;
47 int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t) = NULL;
48 void (*sym_fido_dev_free)(fido_dev_t **) = NULL;
49 int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *) = NULL;
50 int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *) = NULL;
51 void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t) = NULL;
52 int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *) = NULL;
53 const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *) = NULL;
54 const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *) = NULL;
55 fido_dev_info_t* (*sym_fido_dev_info_new)(size_t) = NULL;
56 const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *) = NULL;
57 const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t) = NULL;
58 bool (*sym_fido_dev_is_fido2)(const fido_dev_t *) = NULL;
59 int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *) = NULL;
60 fido_dev_t* (*sym_fido_dev_new)(void) = NULL;
61 int (*sym_fido_dev_open)(fido_dev_t *, const char *) = NULL;
62 int (*sym_fido_dev_close)(fido_dev_t *) = NULL;
63 const char* (*sym_fido_strerr)(int) = NULL;
64 
dlopen_libfido2(void)65 int dlopen_libfido2(void) {
66         return dlopen_many_sym_or_warn(
67                         &libfido2_dl, "libfido2.so.1", LOG_DEBUG,
68                         DLSYM_ARG(fido_assert_allow_cred),
69                         DLSYM_ARG(fido_assert_free),
70                         DLSYM_ARG(fido_assert_hmac_secret_len),
71                         DLSYM_ARG(fido_assert_hmac_secret_ptr),
72                         DLSYM_ARG(fido_assert_new),
73                         DLSYM_ARG(fido_assert_set_clientdata_hash),
74                         DLSYM_ARG(fido_assert_set_extensions),
75                         DLSYM_ARG(fido_assert_set_hmac_salt),
76                         DLSYM_ARG(fido_assert_set_rp),
77                         DLSYM_ARG(fido_assert_set_up),
78                         DLSYM_ARG(fido_assert_set_uv),
79                         DLSYM_ARG(fido_cbor_info_extensions_len),
80                         DLSYM_ARG(fido_cbor_info_extensions_ptr),
81                         DLSYM_ARG(fido_cbor_info_free),
82                         DLSYM_ARG(fido_cbor_info_new),
83                         DLSYM_ARG(fido_cbor_info_options_len),
84                         DLSYM_ARG(fido_cbor_info_options_name_ptr),
85                         DLSYM_ARG(fido_cbor_info_options_value_ptr),
86                         DLSYM_ARG(fido_cred_free),
87                         DLSYM_ARG(fido_cred_id_len),
88                         DLSYM_ARG(fido_cred_id_ptr),
89                         DLSYM_ARG(fido_cred_new),
90                         DLSYM_ARG(fido_cred_set_clientdata_hash),
91                         DLSYM_ARG(fido_cred_set_extensions),
92                         DLSYM_ARG(fido_cred_set_rk),
93                         DLSYM_ARG(fido_cred_set_rp),
94                         DLSYM_ARG(fido_cred_set_type),
95                         DLSYM_ARG(fido_cred_set_user),
96                         DLSYM_ARG(fido_cred_set_uv),
97                         DLSYM_ARG(fido_dev_free),
98                         DLSYM_ARG(fido_dev_get_assert),
99                         DLSYM_ARG(fido_dev_get_cbor_info),
100                         DLSYM_ARG(fido_dev_info_free),
101                         DLSYM_ARG(fido_dev_info_manifest),
102                         DLSYM_ARG(fido_dev_info_manufacturer_string),
103                         DLSYM_ARG(fido_dev_info_new),
104                         DLSYM_ARG(fido_dev_info_path),
105                         DLSYM_ARG(fido_dev_info_product_string),
106                         DLSYM_ARG(fido_dev_info_ptr),
107                         DLSYM_ARG(fido_dev_is_fido2),
108                         DLSYM_ARG(fido_dev_make_cred),
109                         DLSYM_ARG(fido_dev_new),
110                         DLSYM_ARG(fido_dev_open),
111                         DLSYM_ARG(fido_dev_close),
112                         DLSYM_ARG(fido_strerr));
113 }
114 
verify_features(fido_dev_t * d,const char * path,int log_level,bool * ret_has_rk,bool * ret_has_client_pin,bool * ret_has_up,bool * ret_has_uv)115 static int verify_features(
116                 fido_dev_t *d,
117                 const char *path,
118                 int log_level, /* the log level to use when device is not FIDO2 with hmac-secret */
119                 bool *ret_has_rk,
120                 bool *ret_has_client_pin,
121                 bool *ret_has_up,
122                 bool *ret_has_uv) {
123 
124         _cleanup_(fido_cbor_info_free_wrapper) fido_cbor_info_t *di = NULL;
125         bool found_extension = false;
126         char **e, **o;
127         const bool *b;
128         bool has_rk = false, has_client_pin = false, has_up = true, has_uv = false; /* Defaults are per table in 5.4 in FIDO2 spec */
129         size_t n;
130         int r;
131 
132         assert(d);
133         assert(path);
134 
135         if (!sym_fido_dev_is_fido2(d))
136                 return log_full_errno(log_level,
137                                       SYNTHETIC_ERRNO(ENODEV),
138                                        "Specified device %s is not a FIDO2 device.", path);
139 
140         di = sym_fido_cbor_info_new();
141         if (!di)
142                 return log_oom();
143 
144         r = sym_fido_dev_get_cbor_info(d, di);
145         if (r != FIDO_OK)
146                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
147                                        "Failed to get CBOR device info for %s: %s", path, sym_fido_strerr(r));
148 
149         e = sym_fido_cbor_info_extensions_ptr(di);
150         n = sym_fido_cbor_info_extensions_len(di);
151         for (size_t i = 0; i < n; i++) {
152                 log_debug("FIDO2 device implements extension: %s", e[i]);
153                 if (streq(e[i], "hmac-secret"))
154                         found_extension = true;
155         }
156 
157         o = sym_fido_cbor_info_options_name_ptr(di);
158         b = sym_fido_cbor_info_options_value_ptr(di);
159         n = sym_fido_cbor_info_options_len(di);
160         for (size_t i = 0; i < n; i++) {
161                 log_debug("FIDO2 device implements option %s: %s", o[i], yes_no(b[i]));
162                 if (streq(o[i], "rk"))
163                         has_rk = b[i];
164                 if (streq(o[i], "clientPin"))
165                         has_client_pin = b[i];
166                 if (streq(o[i], "up"))
167                         has_up = b[i];
168                 if (streq(o[i], "uv"))
169                         has_uv = b[i];
170         }
171 
172         if (!found_extension)
173                 return log_full_errno(log_level,
174                                       SYNTHETIC_ERRNO(ENODEV),
175                                        "Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", path);
176 
177         log_debug("Has rk ('Resident Key') support: %s\n"
178                   "Has clientPin support: %s\n"
179                   "Has up ('User Presence') support: %s\n"
180                   "Has uv ('User Verification') support: %s\n",
181                   yes_no(has_rk),
182                   yes_no(has_client_pin),
183                   yes_no(has_up),
184                   yes_no(has_uv));
185 
186         if (ret_has_rk)
187                 *ret_has_rk = has_rk;
188         if (ret_has_client_pin)
189                 *ret_has_client_pin = has_client_pin;
190         if (ret_has_up)
191                 *ret_has_up = has_up;
192         if (ret_has_uv)
193                 *ret_has_uv = has_uv;
194 
195         return 0;
196 }
197 
fido2_use_hmac_hash_specific_token(const char * path,const char * rp_id,const void * salt,size_t salt_size,const void * cid,size_t cid_size,char ** pins,Fido2EnrollFlags required,void ** ret_hmac,size_t * ret_hmac_size)198 static int fido2_use_hmac_hash_specific_token(
199                 const char *path,
200                 const char *rp_id,
201                 const void *salt,
202                 size_t salt_size,
203                 const void *cid,
204                 size_t cid_size,
205                 char **pins,
206                 Fido2EnrollFlags required, /* client pin/user presence required */
207                 void **ret_hmac,
208                 size_t *ret_hmac_size) {
209 
210         _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
211         _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
212         _cleanup_(erase_and_freep) void *hmac_copy = NULL;
213         bool has_up, has_client_pin, has_uv;
214         size_t hmac_size;
215         const void *hmac;
216         int r;
217 
218         assert(path);
219         assert(rp_id);
220         assert(salt);
221         assert(cid);
222         assert(ret_hmac);
223         assert(ret_hmac_size);
224 
225         d = sym_fido_dev_new();
226         if (!d)
227                 return log_oom();
228 
229         r = sym_fido_dev_open(d, path);
230         if (r != FIDO_OK)
231                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
232                                        "Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r));
233 
234         r = verify_features(d, path, LOG_ERR, NULL, &has_client_pin, &has_up, &has_uv);
235         if (r < 0)
236                 return r;
237 
238         if (!has_client_pin && FLAGS_SET(required, FIDO2ENROLL_PIN))
239                 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
240                                        "PIN required to unlock, but FIDO2 device %s does not support it.",
241                                        path);
242 
243         if (!has_up && FLAGS_SET(required, FIDO2ENROLL_UP))
244                 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
245                                        "User presence test required to unlock, but FIDO2 device %s does not support it.",
246                                        path);
247 
248         if (!has_uv && FLAGS_SET(required, FIDO2ENROLL_UV))
249                 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
250                                        "User verification required to unlock, but FIDO2 device %s does not support it.",
251                                        path);
252 
253         a = sym_fido_assert_new();
254         if (!a)
255                 return log_oom();
256 
257         r = sym_fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
258         if (r != FIDO_OK)
259                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
260                                        "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r));
261 
262         r = sym_fido_assert_set_hmac_salt(a, salt, salt_size);
263         if (r != FIDO_OK)
264                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
265                                        "Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
266 
267         r = sym_fido_assert_set_rp(a, rp_id);
268         if (r != FIDO_OK)
269                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
270                                        "Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
271 
272         r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
273         if (r != FIDO_OK)
274                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
275                                        "Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
276 
277         r = sym_fido_assert_allow_cred(a, cid, cid_size);
278         if (r != FIDO_OK)
279                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
280                                        "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
281 
282         log_info("Asking FIDO2 token for authentication.");
283 
284         if (has_up) {
285                 r = sym_fido_assert_set_up(a, FLAGS_SET(required, FIDO2ENROLL_UP) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
286                 if (r != FIDO_OK)
287                         return log_error_errno(SYNTHETIC_ERRNO(EIO),
288                                                "Failed to %s FIDO2 user presence test: %s",
289                                                enable_disable(FLAGS_SET(required, FIDO2ENROLL_UP)),
290                                                sym_fido_strerr(r));
291 
292                 if (FLAGS_SET(required, FIDO2ENROLL_UP))
293                         log_notice("%s%sPlease confirm presence on security token to unlock.",
294                                    emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
295                                    emoji_enabled() ? " " : "");
296         }
297 
298         if (has_uv && !FLAGS_SET(required, FIDO2ENROLL_UV_OMIT)) {
299                 r = sym_fido_assert_set_uv(a, FLAGS_SET(required, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
300                 if (r != FIDO_OK)
301                         return log_error_errno(SYNTHETIC_ERRNO(EIO),
302                                                "Failed to %s FIDO2 user verification: %s",
303                                                enable_disable(FLAGS_SET(required, FIDO2ENROLL_UV)),
304                                                sym_fido_strerr(r));
305 
306                 if (FLAGS_SET(required, FIDO2ENROLL_UV))
307                         log_notice("%s%sPlease verify user on security token to unlock.",
308                                    emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
309                                    emoji_enabled() ? " " : "");
310         }
311 
312         for (;;) {
313                 bool retry_with_up = false, retry_with_pin = false;
314 
315                 if (FLAGS_SET(required, FIDO2ENROLL_PIN)) {
316                         /* OK, we need a pin, try with all pins in turn */
317                         if (strv_isempty(pins))
318                                 r = FIDO_ERR_PIN_REQUIRED;
319                         else
320                                 STRV_FOREACH(i, pins) {
321                                         r = sym_fido_dev_get_assert(d, a, *i);
322                                         if (r != FIDO_ERR_PIN_INVALID)
323                                                 break;
324                                 }
325 
326                 } else
327                         r = sym_fido_dev_get_assert(d, a, NULL);
328 
329                 /* In some conditions, where a PIN or UP is required we might accept that. Let's check the
330                  * conditions and if so try immediately again. */
331 
332                 switch (r) {
333 
334                 case FIDO_ERR_UP_REQUIRED:
335                         /* So the token asked for "up". Try to turn it on, for compat with systemd 248 and try again. */
336 
337                         if (!has_up)
338                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
339                                                        "Token asks for user presence test but doesn't advertise 'up' feature.");
340 
341                         if (FLAGS_SET(required, FIDO2ENROLL_UP))
342                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
343                                                        "Token asks for user presence test but was already enabled.");
344 
345                         if (FLAGS_SET(required, FIDO2ENROLL_UP_IF_NEEDED)) {
346                                 log_notice("%s%sPlease confirm presence on security to unlock.",
347                                            emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
348                                            emoji_enabled() ? " " : "");
349                                 retry_with_up = true;
350                         }
351 
352                         break;
353 
354                 case FIDO_ERR_UNSUPPORTED_OPTION:
355                         /* AuthenTrend ATKey.Pro returns this instead of FIDO_ERR_UP_REQUIRED, let's handle
356                          * it gracefully (also see below.) */
357 
358                         if (has_up && (required & (FIDO2ENROLL_UP|FIDO2ENROLL_UP_IF_NEEDED)) == FIDO2ENROLL_UP_IF_NEEDED) {
359                                 log_notice("%s%sGot unsupported option error when when user presence test is turned off. Trying with user presence test turned on.",
360                                            emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
361                                            emoji_enabled() ? " " : "");
362                                 retry_with_up = true;
363                         }
364 
365                         break;
366 
367                 case FIDO_ERR_PIN_REQUIRED:
368                         /* A pin was requested. Maybe supply one, if we are configured to do so on request */
369 
370                         if (!has_client_pin)
371                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
372                                                        "Token asks for PIN but doesn't advertise 'clientPin' feature.");
373 
374                         if (FLAGS_SET(required, FIDO2ENROLL_PIN) && !strv_isempty(pins))
375                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
376                                                        "Token asks for PIN but one was already supplied.");
377 
378                         if ((required & (FIDO2ENROLL_PIN|FIDO2ENROLL_PIN_IF_NEEDED)) == FIDO2ENROLL_PIN_IF_NEEDED) {
379                                 /* If a PIN so far wasn't specified but is requested by the device, and
380                                  * FIDO2ENROLL_PIN_IF_NEEDED is set, then provide it */
381                                 log_debug("Retrying to create credential with PIN.");
382                                 retry_with_pin = true;
383                         }
384 
385                         break;
386 
387                 default:
388                         break;
389                 }
390 
391                 if (!retry_with_up && !retry_with_pin)
392                         break;
393 
394                 if (retry_with_up) {
395                         r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE);
396                         if (r != FIDO_OK)
397                                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
398                                                        "Failed to enable FIDO2 user presence test: %s", sym_fido_strerr(r));
399 
400                         required |= FIDO2ENROLL_UP;
401                 }
402 
403                 if (retry_with_pin)
404                         required |= FIDO2ENROLL_PIN;
405         }
406 
407         switch (r) {
408         case FIDO_OK:
409                 break;
410         case FIDO_ERR_NO_CREDENTIALS:
411                 return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
412                                        "Wrong security token; needed credentials not present on token.");
413         case FIDO_ERR_PIN_REQUIRED:
414                 return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
415                                        "Security token requires PIN.");
416         case FIDO_ERR_PIN_AUTH_BLOCKED:
417                 return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
418                                        "PIN of security token is blocked, please remove/reinsert token.");
419 #ifdef FIDO_ERR_UV_BLOCKED
420         case FIDO_ERR_UV_BLOCKED:
421                 return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
422                                        "Verification of security token is blocked, please remove/reinsert token.");
423 #endif
424         case FIDO_ERR_PIN_INVALID:
425                 return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
426                                        "PIN of security token incorrect.");
427         case FIDO_ERR_UP_REQUIRED:
428                 return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
429                                        "User presence required.");
430         case FIDO_ERR_ACTION_TIMEOUT:
431                 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
432                                        "Token action timeout. (User didn't interact with token quickly enough.)");
433         default:
434                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
435                                        "Failed to ask token for assertion: %s", sym_fido_strerr(r));
436         }
437 
438         hmac = sym_fido_assert_hmac_secret_ptr(a, 0);
439         if (!hmac)
440                 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
441 
442         hmac_size = sym_fido_assert_hmac_secret_len(a, 0);
443 
444         hmac_copy = memdup(hmac, hmac_size);
445         if (!hmac_copy)
446                 return log_oom();
447 
448         *ret_hmac = TAKE_PTR(hmac_copy);
449         *ret_hmac_size = hmac_size;
450         return 0;
451 }
452 
453 /* COSE_ECDH_ES256 is not usable with fido_cred_set_type() thus it's not listed here. */
fido2_algorithm_to_string(int alg)454 static const char *fido2_algorithm_to_string(int alg) {
455         switch(alg) {
456                 case COSE_ES256:
457                         return "es256";
458                 case COSE_RS256:
459                         return "rs256";
460                 case COSE_EDDSA:
461                         return "eddsa";
462                 default:
463                         return NULL;
464         }
465 }
466 
fido2_use_hmac_hash(const char * device,const char * rp_id,const void * salt,size_t salt_size,const void * cid,size_t cid_size,char ** pins,Fido2EnrollFlags required,void ** ret_hmac,size_t * ret_hmac_size)467 int fido2_use_hmac_hash(
468                 const char *device,
469                 const char *rp_id,
470                 const void *salt,
471                 size_t salt_size,
472                 const void *cid,
473                 size_t cid_size,
474                 char **pins,
475                 Fido2EnrollFlags required, /* client pin/user presence required */
476                 void **ret_hmac,
477                 size_t *ret_hmac_size) {
478 
479         size_t allocated = 64, found = 0;
480         fido_dev_info_t *di = NULL;
481         int r;
482 
483         r = dlopen_libfido2();
484         if (r < 0)
485                 return log_error_errno(r, "FIDO2 support is not installed.");
486 
487         if (device)
488                 return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size);
489 
490         di = sym_fido_dev_info_new(allocated);
491         if (!di)
492                 return log_oom();
493 
494         r = sym_fido_dev_info_manifest(di, allocated, &found);
495         if (r == FIDO_ERR_INTERNAL) {
496                 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
497                 r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
498                 goto finish;
499         }
500         if (r != FIDO_OK) {
501                 r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r));
502                 goto finish;
503         }
504 
505         for (size_t i = 0; i < found; i++) {
506                 const fido_dev_info_t *entry;
507                 const char *path;
508 
509                 entry = sym_fido_dev_info_ptr(di, i);
510                 if (!entry) {
511                         r = log_error_errno(SYNTHETIC_ERRNO(EIO),
512                                             "Failed to get device information for FIDO device %zu.", i);
513                         goto finish;
514                 }
515 
516                 path = sym_fido_dev_info_path(entry);
517                 if (!path) {
518                         r = log_error_errno(SYNTHETIC_ERRNO(EIO),
519                                             "Failed to query FIDO device path.");
520                         goto finish;
521                 }
522 
523                 r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size);
524                 if (!IN_SET(r,
525                             -EBADSLT, /* device doesn't understand our credential hash */
526                             -ENODEV   /* device is not a FIDO2 device with HMAC-SECRET */))
527                         goto finish;
528         }
529 
530         r = -EAGAIN;
531 
532 finish:
533         sym_fido_dev_info_free(&di, allocated);
534         return r;
535 }
536 
537 #define FIDO2_SALT_SIZE 32
538 
fido2_generate_hmac_hash(const char * device,const char * rp_id,const char * rp_name,const void * user_id,size_t user_id_len,const char * user_name,const char * user_display_name,const char * user_icon,const char * askpw_icon_name,Fido2EnrollFlags lock_with,int cred_alg,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,Fido2EnrollFlags * ret_locked_with)539 int fido2_generate_hmac_hash(
540                 const char *device,
541                 const char *rp_id,
542                 const char *rp_name,
543                 const void *user_id, size_t user_id_len,
544                 const char *user_name,
545                 const char *user_display_name,
546                 const char *user_icon,
547                 const char *askpw_icon_name,
548                 Fido2EnrollFlags lock_with,
549                 int cred_alg,
550                 void **ret_cid, size_t *ret_cid_size,
551                 void **ret_salt, size_t *ret_salt_size,
552                 void **ret_secret, size_t *ret_secret_size,
553                 char **ret_usedpin,
554                 Fido2EnrollFlags *ret_locked_with) {
555 
556         _cleanup_(erase_and_freep) void *salt = NULL, *secret_copy = NULL;
557         _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
558         _cleanup_(fido_cred_free_wrapper) fido_cred_t *c = NULL;
559         _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
560         _cleanup_(erase_and_freep) char *used_pin = NULL;
561         bool has_rk, has_client_pin, has_up, has_uv;
562         _cleanup_free_ char *cid_copy = NULL;
563         size_t cid_size, secret_size;
564         const void *cid, *secret;
565         int r;
566 
567         assert(device);
568         assert(ret_cid);
569         assert(ret_cid_size);
570         assert(ret_salt);
571         assert(ret_salt_size);
572         assert(ret_secret);
573         assert(ret_secret_size);
574 
575         /* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
576          * HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
577          * authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
578          * device never sees the volume key.
579          *
580          * S = HMAC-SHA256(I, D)
581          *
582          * with: S → LUKS/account authentication key                                         (never stored)
583          *       I → internal key on FIDO2 device                              (stored in the FIDO2 device)
584          *       D → salt we generate here               (stored in the privileged part of the JSON record)
585          *
586          */
587 
588         assert(device);
589         assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0);
590 
591         r = dlopen_libfido2();
592         if (r < 0)
593                 return log_error_errno(r, "FIDO2 token support is not installed.");
594 
595         salt = malloc(FIDO2_SALT_SIZE);
596         if (!salt)
597                 return log_oom();
598 
599         r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
600         if (r < 0)
601                 return log_error_errno(r, "Failed to generate salt: %m");
602 
603         d = sym_fido_dev_new();
604         if (!d)
605                 return log_oom();
606 
607         r = sym_fido_dev_open(d, device);
608         if (r != FIDO_OK)
609                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
610                                        "Failed to open FIDO2 device %s: %s", device, sym_fido_strerr(r));
611 
612         r = verify_features(d, device, LOG_ERR, &has_rk, &has_client_pin, &has_up, &has_uv);
613         if (r < 0)
614                 return r;
615 
616         /* While enrolling degrade gracefully if the requested feature set isn't available, but let the user know */
617         if (!has_client_pin && FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) {
618                 log_notice("Requested to lock with PIN, but FIDO2 device %s does not support it, disabling.", device);
619                 lock_with &= ~FIDO2ENROLL_PIN;
620         }
621 
622         if (!has_up && FLAGS_SET(lock_with, FIDO2ENROLL_UP)) {
623                 log_notice("Locking with user presence test requested, but FIDO2 device %s does not support it, disabling.", device);
624                 lock_with &= ~FIDO2ENROLL_UP;
625         }
626 
627         if (!has_uv && FLAGS_SET(lock_with, FIDO2ENROLL_UV)) {
628                 log_notice("Locking with user verification test requested, but FIDO2 device %s does not support it, disabling.", device);
629                 lock_with &= ~FIDO2ENROLL_UV;
630         }
631 
632         c = sym_fido_cred_new();
633         if (!c)
634                 return log_oom();
635 
636         r = sym_fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
637         if (r != FIDO_OK)
638                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
639                                        "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", sym_fido_strerr(r));
640 
641         r = sym_fido_cred_set_rp(c, rp_id, rp_name);
642         if (r != FIDO_OK)
643                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
644                                        "Failed to set FIDO2 credential relying party ID/name: %s", sym_fido_strerr(r));
645 
646         r = sym_fido_cred_set_type(c, cred_alg);
647         if (r != FIDO_OK)
648                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
649                                        "Failed to set FIDO2 credential type to %s: %s", fido2_algorithm_to_string(cred_alg), sym_fido_strerr(r));
650 
651         r = sym_fido_cred_set_user(
652                         c,
653                         user_id, user_id_len,
654                         user_name,
655                         user_display_name,
656                         user_icon);
657         if (r != FIDO_OK)
658                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
659                                        "Failed to set FIDO2 credential user data: %s", sym_fido_strerr(r));
660 
661         r = sym_fido_cred_set_clientdata_hash(c, (const unsigned char[32]) {}, 32);
662         if (r != FIDO_OK)
663                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
664                                        "Failed to set FIDO2 client data hash: %s", sym_fido_strerr(r));
665 
666         if (has_rk) {
667                 r = sym_fido_cred_set_rk(c, FIDO_OPT_FALSE);
668                 if (r != FIDO_OK)
669                         return log_error_errno(SYNTHETIC_ERRNO(EIO),
670                                                "Failed to turn off FIDO2 resident key option of credential: %s", sym_fido_strerr(r));
671         }
672 
673         if (has_uv) {
674                 r = sym_fido_cred_set_uv(c, FIDO_OPT_FALSE);
675                 if (r != FIDO_OK)
676                         return log_error_errno(SYNTHETIC_ERRNO(EIO),
677                                                "Failed to turn off FIDO2 user verification option of credential: %s", sym_fido_strerr(r));
678         }
679 
680         /* As per specification "up" is assumed to be implicit when making credentials, hence we don't
681          * explicitly enable/disable it here */
682 
683         log_info("Initializing FIDO2 credential on security token.");
684 
685         if (has_uv || has_up)
686                 log_notice("%s%s(Hint: This might require confirmation of user presence on security token.)",
687                            emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
688                            emoji_enabled() ? " " : "");
689 
690         r = sym_fido_dev_make_cred(d, c, NULL);
691         if (r == FIDO_ERR_PIN_REQUIRED) {
692 
693                 if (!has_client_pin)
694                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
695                                                "Token asks for PIN but doesn't advertise 'clientPin' feature.");
696 
697                 for (;;) {
698                         _cleanup_(strv_free_erasep) char **pin = NULL;
699 
700                         r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", "fido2-pin", USEC_INFINITY, 0, &pin);
701                         if (r < 0)
702                                 return log_error_errno(r, "Failed to acquire user PIN: %m");
703 
704                         r = FIDO_ERR_PIN_INVALID;
705                         STRV_FOREACH(i, pin) {
706                                 if (isempty(*i)) {
707                                         log_notice("PIN may not be empty.");
708                                         continue;
709                                 }
710 
711                                 r = sym_fido_dev_make_cred(d, c, *i);
712                                 if (r == FIDO_OK) {
713                                         used_pin = strdup(*i);
714                                         if (!used_pin)
715                                                 return log_oom();
716                                         break;
717                                 }
718                                 if (r != FIDO_ERR_PIN_INVALID)
719                                         break;
720                         }
721 
722                         if (r != FIDO_ERR_PIN_INVALID)
723                                 break;
724 
725                         log_notice("PIN incorrect, please try again.");
726                 }
727         }
728         if (r == FIDO_ERR_PIN_AUTH_BLOCKED)
729                 return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
730                                         "Token PIN is currently blocked, please remove and reinsert token.");
731 #ifdef FIDO_ERR_UV_BLOCKED
732         if (r == FIDO_ERR_UV_BLOCKED)
733                 return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
734                                         "Token verification is currently blocked, please remove and reinsert token.");
735 #endif
736         if (r == FIDO_ERR_ACTION_TIMEOUT)
737                 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
738                                        "Token action timeout. (User didn't interact with token quickly enough.)");
739         if (r == FIDO_ERR_UNSUPPORTED_ALGORITHM)
740                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
741                                         "Token doesn't support credential algorithm %s.", fido2_algorithm_to_string(cred_alg));
742         if (r != FIDO_OK)
743                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
744                                        "Failed to generate FIDO2 credential: %s", sym_fido_strerr(r));
745 
746         cid = sym_fido_cred_id_ptr(c);
747         if (!cid)
748                 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get FIDO2 credential ID.");
749 
750         cid_size = sym_fido_cred_id_len(c);
751 
752         a = sym_fido_assert_new();
753         if (!a)
754                 return log_oom();
755 
756         r = sym_fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
757         if (r != FIDO_OK)
758                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
759                                        "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r));
760 
761         r = sym_fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
762         if (r != FIDO_OK)
763                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
764                                        "Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
765 
766         r = sym_fido_assert_set_rp(a, rp_id);
767         if (r != FIDO_OK)
768                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
769                                        "Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
770 
771         r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
772         if (r != FIDO_OK)
773                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
774                                        "Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
775 
776         r = sym_fido_assert_allow_cred(a, cid, cid_size);
777         if (r != FIDO_OK)
778                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
779                                        "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
780 
781         log_info("Generating secret key on FIDO2 security token.");
782 
783         if (has_up) {
784                 r = sym_fido_assert_set_up(a, FLAGS_SET(lock_with, FIDO2ENROLL_UP) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
785                 if (r != FIDO_OK)
786                         return log_error_errno(SYNTHETIC_ERRNO(EIO),
787                                                "Failed to %s FIDO2 user presence test: %s",
788                                                enable_disable(FLAGS_SET(lock_with, FIDO2ENROLL_UP)),
789                                                sym_fido_strerr(r));
790 
791                 if (FLAGS_SET(lock_with, FIDO2ENROLL_UP))
792                         log_notice("%s%sIn order to allow secret key generation, please confirm presence on security token.",
793                                    emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
794                                    emoji_enabled() ? " " : "");
795         }
796 
797         if (has_uv) {
798                 r = sym_fido_assert_set_uv(a, FLAGS_SET(lock_with, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
799                 if (r != FIDO_OK)
800                         return log_error_errno(SYNTHETIC_ERRNO(EIO),
801                                                "Failed to %s FIDO user verification: %s",
802                                                enable_disable(FLAGS_SET(lock_with, FIDO2ENROLL_UV)),
803                                                sym_fido_strerr(r));
804 
805                 if (FLAGS_SET(lock_with, FIDO2ENROLL_UV))
806                         log_notice("%s%sIn order to allow secret key generation, please verify user on security token.",
807                                    emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
808                                    emoji_enabled() ? " " : "");
809         }
810 
811         for (;;) {
812                 bool retry_with_up = false, retry_with_pin = false;
813 
814                 r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL);
815 
816                 switch (r) {
817 
818                 case FIDO_ERR_UP_REQUIRED:
819                         /* If the token asks for "up" when we turn off, then this might be a feature that
820                          * isn't optional. Let's enable it */
821 
822                         if (!has_up)
823                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
824                                                        "Token asks for user presence test but doesn't advertise 'up' feature.");
825 
826                         if (FLAGS_SET(lock_with, FIDO2ENROLL_UP))
827                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
828                                                        "Token asks for user presence test but was already enabled.");
829 
830                         log_notice("%s%sLocking without user presence test requested, but FIDO2 device %s requires it, enabling.",
831                                    emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
832                                    emoji_enabled() ? " " : "",
833                                    device);
834 
835                         retry_with_up = true;
836                         break;
837 
838                 case FIDO_ERR_UNSUPPORTED_OPTION:
839                         /* AuthenTrend ATKey.Pro says it supports "up", but if we disable it it will fail
840                          * with FIDO_ERR_UNSUPPORTED_OPTION, probably because it isn't actually
841                          * optional. Let's see if turning it on works. This is very similar to the
842                          * FIDO_ERR_UP_REQUIRED case, but since the error is so vague we implement it
843                          * slightly more defensively. */
844 
845                         if (has_up && !FLAGS_SET(lock_with, FIDO2ENROLL_UP)) {
846                                 log_notice("%s%sGot unsupported option error when when user presence test is turned off. Trying with user presence test turned on.",
847                                            emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
848                                            emoji_enabled() ? " " : "");
849                                 retry_with_up = true;
850                         }
851 
852                         break;
853 
854                 case FIDO_ERR_PIN_REQUIRED:
855                         if (!has_client_pin)
856                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
857                                                        "Token asks for client PIN check but doesn't advertise 'clientPin' feature.");
858 
859                         if (FLAGS_SET(lock_with, FIDO2ENROLL_PIN))
860                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
861                                                        "Token asks for user client PIN check but was already enabled.");
862 
863                         log_debug("Token requires PIN for assertion, enabling.");
864                         retry_with_pin = true;
865                         break;
866 
867                 default:
868                         break;
869                 }
870 
871                 if (!retry_with_up && !retry_with_pin)
872                         break;
873 
874                 if (retry_with_up) {
875                         r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE);
876                         if (r != FIDO_OK)
877                                 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enable FIDO2 user presence test: %s", sym_fido_strerr(r));
878 
879                         lock_with |= FIDO2ENROLL_UP;
880                 }
881 
882                 if (retry_with_pin)
883                         lock_with |= FIDO2ENROLL_PIN;
884         }
885 
886         if (r == FIDO_ERR_ACTION_TIMEOUT)
887                 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
888                                        "Token action timeout. (User didn't interact with token quickly enough.)");
889         if (r != FIDO_OK)
890                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
891                                        "Failed to ask token for assertion: %s", sym_fido_strerr(r));
892 
893         secret = sym_fido_assert_hmac_secret_ptr(a, 0);
894         if (!secret)
895                 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
896 
897         secret_size = sym_fido_assert_hmac_secret_len(a, 0);
898 
899         secret_copy = memdup(secret, secret_size);
900         if (!secret_copy)
901                 return log_oom();
902 
903         cid_copy = memdup(cid, cid_size);
904         if (!cid_copy)
905                 return log_oom();
906 
907         *ret_cid = TAKE_PTR(cid_copy);
908         *ret_cid_size = cid_size;
909         *ret_salt = TAKE_PTR(salt);
910         *ret_salt_size = FIDO2_SALT_SIZE;
911         *ret_secret = TAKE_PTR(secret_copy);
912         *ret_secret_size = secret_size;
913 
914         if (ret_usedpin)
915                 *ret_usedpin = TAKE_PTR(used_pin);
916 
917         if (ret_locked_with)
918                 *ret_locked_with = lock_with;
919 
920         return 0;
921 }
922 #endif
923 
924 #if HAVE_LIBFIDO2
check_device_is_fido2_with_hmac_secret(const char * path)925 static int check_device_is_fido2_with_hmac_secret(const char *path) {
926         _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
927         int r;
928 
929         d = sym_fido_dev_new();
930         if (!d)
931                 return log_oom();
932 
933         r = sym_fido_dev_open(d, path);
934         if (r != FIDO_OK)
935                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
936                                        "Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r));
937 
938         r = verify_features(d, path, LOG_DEBUG, NULL, NULL, NULL, NULL);
939         if (r == -ENODEV) /* Not a FIDO2 device, or not implementing 'hmac-secret' */
940                 return false;
941         if (r < 0)
942                 return r;
943 
944         return true;
945 }
946 #endif
947 
fido2_list_devices(void)948 int fido2_list_devices(void) {
949 #if HAVE_LIBFIDO2
950         _cleanup_(table_unrefp) Table *t = NULL;
951         size_t allocated = 64, found = 0;
952         fido_dev_info_t *di = NULL;
953         int r;
954 
955         r = dlopen_libfido2();
956         if (r < 0)
957                 return log_error_errno(r, "FIDO2 token support is not installed.");
958 
959         di = sym_fido_dev_info_new(allocated);
960         if (!di)
961                 return log_oom();
962 
963         r = sym_fido_dev_info_manifest(di, allocated, &found);
964         if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
965                 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
966                 log_info("No FIDO2 devices found.");
967                 r = 0;
968                 goto finish;
969         }
970         if (r != FIDO_OK) {
971                 r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r));
972                 goto finish;
973         }
974 
975         t = table_new("path", "manufacturer", "product");
976         if (!t) {
977                 r = log_oom();
978                 goto finish;
979         }
980 
981         for (size_t i = 0; i < found; i++) {
982                 const fido_dev_info_t *entry;
983 
984                 entry = sym_fido_dev_info_ptr(di, i);
985                 if (!entry) {
986                         r = log_error_errno(SYNTHETIC_ERRNO(EIO),
987                                             "Failed to get device information for FIDO device %zu.", i);
988                         goto finish;
989                 }
990 
991                 r = check_device_is_fido2_with_hmac_secret(sym_fido_dev_info_path(entry));
992                 if (r < 0)
993                         goto finish;
994                 if (!r)
995                         continue;
996 
997                 r = table_add_many(
998                                 t,
999                                 TABLE_PATH, sym_fido_dev_info_path(entry),
1000                                 TABLE_STRING, sym_fido_dev_info_manufacturer_string(entry),
1001                                 TABLE_STRING, sym_fido_dev_info_product_string(entry));
1002                 if (r < 0) {
1003                         table_log_add_error(r);
1004                         goto finish;
1005                 }
1006         }
1007 
1008         r = table_print(t, stdout);
1009         if (r < 0) {
1010                 log_error_errno(r, "Failed to show device table: %m");
1011                 goto finish;
1012         }
1013 
1014         r = 0;
1015 
1016 finish:
1017         sym_fido_dev_info_free(&di, allocated);
1018         return r;
1019 #else
1020         return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
1021                                "FIDO2 tokens not supported on this build.");
1022 #endif
1023 }
1024 
fido2_find_device_auto(char ** ret)1025 int fido2_find_device_auto(char **ret) {
1026 #if HAVE_LIBFIDO2
1027         _cleanup_free_ char *copy = NULL;
1028         size_t di_size = 64, found = 0;
1029         const fido_dev_info_t *entry;
1030         fido_dev_info_t *di = NULL;
1031         const char *path;
1032         int r;
1033 
1034         r = dlopen_libfido2();
1035         if (r < 0)
1036                 return log_error_errno(r, "FIDO2 token support is not installed.");
1037 
1038         di = sym_fido_dev_info_new(di_size);
1039         if (!di)
1040                 return log_oom();
1041 
1042         r = sym_fido_dev_info_manifest(di, di_size, &found);
1043         if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
1044                 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
1045                 r = log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No FIDO devices found.");
1046                 goto finish;
1047         }
1048         if (r != FIDO_OK) {
1049                 r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO devices: %s", sym_fido_strerr(r));
1050                 goto finish;
1051         }
1052         if (found > 1) {
1053                 r = log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "More than one FIDO device found.");
1054                 goto finish;
1055         }
1056 
1057         entry = sym_fido_dev_info_ptr(di, 0);
1058         if (!entry) {
1059                 r = log_error_errno(SYNTHETIC_ERRNO(EIO),
1060                                     "Failed to get device information for FIDO device 0.");
1061                 goto finish;
1062         }
1063 
1064         r = check_device_is_fido2_with_hmac_secret(sym_fido_dev_info_path(entry));
1065         if (r < 0)
1066                 goto finish;
1067         if (!r) {
1068                 r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO device discovered does not implement FIDO2 with 'hmac-secret' extension.");
1069                 goto finish;
1070         }
1071 
1072         path = sym_fido_dev_info_path(entry);
1073         if (!path) {
1074                 r = log_error_errno(SYNTHETIC_ERRNO(EIO),
1075                                     "Failed to query FIDO device path.");
1076                 goto finish;
1077         }
1078 
1079         copy = strdup(path);
1080         if (!copy) {
1081                 r = log_oom();
1082                 goto finish;
1083         }
1084 
1085         *ret = TAKE_PTR(copy);
1086         r = 0;
1087 
1088 finish:
1089         sym_fido_dev_info_free(&di, di_size);
1090         return r;
1091 #else
1092         return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
1093                                "FIDO2 tokens not supported on this build.");
1094 #endif
1095 }
1096 
fido2_have_device(const char * device)1097 int fido2_have_device(const char *device) {
1098 #if HAVE_LIBFIDO2
1099         size_t allocated = 64, found = 0;
1100         fido_dev_info_t *di = NULL;
1101         int r;
1102 
1103         /* Return == 0 if not devices are found, > 0 if at least one is found */
1104 
1105         r = dlopen_libfido2();
1106         if (r < 0)
1107                 return log_error_errno(r, "FIDO2 support is not installed.");
1108 
1109         if (device) {
1110                 if (access(device, F_OK) < 0) {
1111                         if (errno == ENOENT)
1112                                 return 0;
1113 
1114                         return log_error_errno(errno, "Failed to determine whether device '%s' exists: %m", device);
1115                 }
1116 
1117                 return 1;
1118         }
1119 
1120         di = sym_fido_dev_info_new(allocated);
1121         if (!di)
1122                 return log_oom();
1123 
1124         r = sym_fido_dev_info_manifest(di, allocated, &found);
1125         if (r == FIDO_ERR_INTERNAL) {
1126                 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
1127                 r = 0;
1128                 goto finish;
1129         }
1130         if (r != FIDO_OK) {
1131                 r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r));
1132                 goto finish;
1133         }
1134 
1135         r = found;
1136 
1137 finish:
1138         sym_fido_dev_info_free(&di, allocated);
1139         return r;
1140 #else
1141         return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
1142                                "FIDO2 tokens not supported on this build.");
1143 #endif
1144 }
1145 
1146 #if HAVE_LIBFIDO2
parse_fido2_algorithm(const char * s,int * ret)1147 int parse_fido2_algorithm(const char *s, int *ret) {
1148         int a;
1149 
1150         assert(s);
1151 
1152         if (streq(s, "es256"))
1153                 a = COSE_ES256;
1154         else if (streq(s, "rs256"))
1155                 a = COSE_RS256;
1156         else if (streq(s, "eddsa"))
1157                 a = COSE_EDDSA;
1158         else
1159                 return -EINVAL;
1160 
1161         if (ret)
1162                 *ret = a;
1163         return 0;
1164 }
1165 #endif
1166