]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/homectl-fido2.c
Merge pull request #15442 from poettering/fido2
[thirdparty/systemd.git] / src / home / homectl-fido2.c
CommitLineData
1c0c4a43
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#if HAVE_LIBFIDO2
4#include <fido.h>
5#endif
6
7#include "ask-password-api.h"
8#include "errno-util.h"
9#include "format-table.h"
10#include "hexdecoct.h"
11#include "homectl-fido2.h"
2af3966a 12#include "homectl-pkcs11.h"
1c0c4a43
LP
13#include "libcrypt-util.h"
14#include "locale-util.h"
15#include "memory-util.h"
16#include "random-util.h"
17#include "strv.h"
18
19#if HAVE_LIBFIDO2
20static int add_fido2_credential_id(
21 JsonVariant **v,
22 const void *cid,
23 size_t cid_size) {
24
25 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
26 _cleanup_strv_free_ char **l = NULL;
27 _cleanup_free_ char *escaped = NULL;
28 int r;
29
30 assert(v);
31 assert(cid);
32
33 r = base64mem(cid, cid_size, &escaped);
34 if (r < 0)
35 return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
36
37 w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential"));
38 if (w) {
39 r = json_variant_strv(w, &l);
40 if (r < 0)
41 return log_error_errno(r, "Failed to parse FIDO2 credential ID list: %m");
42
43 if (strv_contains(l, escaped))
44 return 0;
45 }
46
47 r = strv_extend(&l, escaped);
48 if (r < 0)
49 return log_oom();
50
51 w = json_variant_unref(w);
52 r = json_variant_new_array_strv(&w, l);
53 if (r < 0)
54 return log_error_errno(r, "Failed to create FIDO2 credential ID JSON: %m");
55
56 r = json_variant_set_field(v, "fido2HmacCredential", w);
57 if (r < 0)
58 return log_error_errno(r, "Failed to update FIDO2 credential ID: %m");
59
60 return 0;
61}
62
63static int add_fido2_salt(
64 JsonVariant **v,
65 const void *cid,
66 size_t cid_size,
67 const void *fido2_salt,
68 size_t fido2_salt_size,
69 const void *secret,
70 size_t secret_size) {
71
72 _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
73 _cleanup_(erase_and_freep) char *base64_encoded = NULL;
74 _cleanup_free_ char *unix_salt = NULL;
75 struct crypt_data cd = {};
76 char *k;
77 int r;
78
79 r = make_salt(&unix_salt);
80 if (r < 0)
81 return log_error_errno(r, "Failed to generate salt: %m");
82
83 /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
84 * expect a NUL terminated string, and we use a binary key */
85 r = base64mem(secret, secret_size, &base64_encoded);
86 if (r < 0)
87 return log_error_errno(r, "Failed to base64 encode secret key: %m");
88
89 errno = 0;
90 k = crypt_r(base64_encoded, unix_salt, &cd);
91 if (!k)
92 return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
93
94 r = json_build(&e, JSON_BUILD_OBJECT(
95 JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
96 JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
97 JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
98 if (r < 0)
99 return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");
100
101 w = json_variant_ref(json_variant_by_key(*v, "privileged"));
102 l = json_variant_ref(json_variant_by_key(w, "fido2HmacSalt"));
103
104 r = json_variant_append_array(&l, e);
105 if (r < 0)
106 return log_error_errno(r, "Failed append FIDO2 salt: %m");
107
108 r = json_variant_set_field(&w, "fido2HmacSalt", l);
109 if (r < 0)
110 return log_error_errno(r, "Failed to set FDO2 salt: %m");
111
112 r = json_variant_set_field(v, "privileged", w);
113 if (r < 0)
114 return log_error_errno(r, "Failed to update privileged field: %m");
115
116 return 0;
117}
118#endif
119
120#define FIDO2_SALT_SIZE 32
121
122int identity_add_fido2_parameters(
123 JsonVariant **v,
124 const char *device) {
125
126#if HAVE_LIBFIDO2
127 _cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
128 _cleanup_(fido_assert_free) fido_assert_t *a = NULL;
129 _cleanup_(erase_and_freep) char *used_pin = NULL;
130 _cleanup_(fido_cred_free) fido_cred_t *c = NULL;
131 _cleanup_(fido_dev_free) fido_dev_t *d = NULL;
132 _cleanup_(erase_and_freep) void *salt = NULL;
133 JsonVariant *un, *realm, *rn;
134 bool found_extension = false;
135 const void *cid, *secret;
136 const char *fido_un;
137 size_t n, cid_size, secret_size;
138 char **e;
139 int r;
140
141 /* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
142 * HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
143 * authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
144 * device never sees the volume key.
145 *
146 * S = HMAC-SHA256(I, D)
147 *
148 * with: S → LUKS/account authentication key (never stored)
149 * I → internal key on FIDO2 device (stored in the FIDO2 device)
150 * D → salt we generate here (stored in the privileged part of the JSON record)
151 *
152 */
153
154 assert(v);
155 assert(device);
156
157 salt = malloc(FIDO2_SALT_SIZE);
158 if (!salt)
159 return log_oom();
160
161 r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
162 if (r < 0)
163 return log_error_errno(r, "Failed to generate salt: %m");
164
165 d = fido_dev_new();
166 if (!d)
167 return log_oom();
168
169 r = fido_dev_open(d, device);
170 if (r != FIDO_OK)
171 return log_error_errno(SYNTHETIC_ERRNO(EIO),
172 "Failed to open FIDO2 device %s: %s", device, fido_strerr(r));
173
174 if (!fido_dev_is_fido2(d))
175 return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
176 "Specified device %s is not a FIDO2 device.", device);
177
178 di = fido_cbor_info_new();
179 if (!di)
180 return log_oom();
181
182 r = fido_dev_get_cbor_info(d, di);
183 if (r != FIDO_OK)
184 return log_error_errno(SYNTHETIC_ERRNO(EIO),
185 "Failed to get CBOR device info for %s: %s", device, fido_strerr(r));
186
187 e = fido_cbor_info_extensions_ptr(di);
188 n = fido_cbor_info_extensions_len(di);
189
190 for (size_t i = 0; i < n; i++)
191 if (streq(e[i], "hmac-secret")) {
192 found_extension = true;
193 break;
194 }
195
196 if (!found_extension)
197 return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
198 "Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", device);
199
200 c = fido_cred_new();
201 if (!c)
202 return log_oom();
203
204 r = fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
205 if (r != FIDO_OK)
206 return log_error_errno(SYNTHETIC_ERRNO(EIO),
207 "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", fido_strerr(r));
208
209 r = fido_cred_set_rp(c, "io.systemd.home", "Home Directory");
210 if (r != FIDO_OK)
211 return log_error_errno(SYNTHETIC_ERRNO(EIO),
212 "Failed to set FIDO2 credential relying party ID/name: %s", fido_strerr(r));
213
214 r = fido_cred_set_type(c, COSE_ES256);
215 if (r != FIDO_OK)
216 return log_error_errno(SYNTHETIC_ERRNO(EIO),
217 "Failed to set FIDO2 credential type to ES256: %s", fido_strerr(r));
218
219 un = json_variant_by_key(*v, "userName");
220 if (!un)
221 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
222 "userName field of user record is missing");
223 if (!json_variant_is_string(un))
224 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
225 "userName field of user record is not a string");
226
227 realm = json_variant_by_key(*v, "realm");
228 if (realm) {
229 if (!json_variant_is_string(realm))
230 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
231 "realm field of user record is not a string");
232
233 fido_un = strjoina(json_variant_string(un), json_variant_string(realm));
234 } else
235 fido_un = json_variant_string(un);
236
237 rn = json_variant_by_key(*v, "realName");
238 if (rn && !json_variant_is_string(rn))
239 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
240 "realName field of user record is not a string");
241
242 r = fido_cred_set_user(c,
243 (const unsigned char*) fido_un, strlen(fido_un), /* We pass the user ID and name as the same */
244 fido_un,
245 rn ? json_variant_string(rn) : NULL,
246 NULL /* icon URL */);
247 if (r != FIDO_OK)
248 return log_error_errno(SYNTHETIC_ERRNO(EIO),
249 "Failed to set FIDO2 credential user data: %s", fido_strerr(r));
250
251 r = fido_cred_set_clientdata_hash(c, (const unsigned char[32]) {}, 32);
252 if (r != FIDO_OK)
253 return log_error_errno(SYNTHETIC_ERRNO(EIO),
254 "Failed to set FIDO2 client data hash: %s", fido_strerr(r));
255
256 r = fido_cred_set_rk(c, FIDO_OPT_FALSE);
257 if (r != FIDO_OK)
258 return log_error_errno(SYNTHETIC_ERRNO(EIO),
259 "Failed to turn off FIDO2 resident key option of credential: %s", fido_strerr(r));
260
261 r = fido_cred_set_uv(c, FIDO_OPT_FALSE);
262 if (r != FIDO_OK)
263 return log_error_errno(SYNTHETIC_ERRNO(EIO),
264 "Failed to turn off FIDO2 user verification option of credential: %s", fido_strerr(r));
265
266 log_info("Initializing FIDO2 credential on security token.");
267
268 log_notice("%s%s(Hint: This might require verification of user presence on security token.)",
269 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
270 emoji_enabled() ? " " : "");
271
272 r = fido_dev_make_cred(d, c, NULL);
273 if (r == FIDO_ERR_PIN_REQUIRED) {
274 _cleanup_free_ char *text = NULL;
275
276 if (asprintf(&text, "Please enter security token PIN:") < 0)
277 return log_oom();
278
279 for (;;) {
280 _cleanup_(strv_free_erasep) char **pin = NULL;
281 char **i;
282
283 r = ask_password_auto(text, "user-home", NULL, "fido2-pin", USEC_INFINITY, 0, &pin);
284 if (r < 0)
285 return log_error_errno(r, "Failed to acquire user PIN: %m");
286
287 r = FIDO_ERR_PIN_INVALID;
288 STRV_FOREACH(i, pin) {
289 if (isempty(*i)) {
290 log_info("PIN may not be empty.");
291 continue;
292 }
293
294 r = fido_dev_make_cred(d, c, *i);
295 if (r == FIDO_OK) {
296 used_pin = strdup(*i);
297 if (!used_pin)
298 return log_oom();
299 break;
300 }
301 if (r != FIDO_ERR_PIN_INVALID)
302 break;
303 }
304
305 if (r != FIDO_ERR_PIN_INVALID)
306 break;
307
308 log_notice("PIN incorrect, please try again.");
309 }
310 }
311 if (r == FIDO_ERR_PIN_AUTH_BLOCKED)
312 return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
313 "Token PIN is currently blocked, please remove and reinsert token.");
314 if (r == FIDO_ERR_ACTION_TIMEOUT)
315 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
316 "Token action timeout. (User didn't interact with token quickly enough.)");
317 if (r != FIDO_OK)
318 return log_error_errno(SYNTHETIC_ERRNO(EIO),
319 "Failed to generate FIDO2 credential: %s", fido_strerr(r));
320
321 cid = fido_cred_id_ptr(c);
322 if (!cid)
323 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get FIDO2 credential ID.");
324
325 cid_size = fido_cred_id_len(c);
326
327 a = fido_assert_new();
328 if (!a)
329 return log_oom();
330
331 r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
332 if (r != FIDO_OK)
333 return log_error_errno(SYNTHETIC_ERRNO(EIO),
334 "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
335
336 r = fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
337 if (r != FIDO_OK)
338 return log_error_errno(SYNTHETIC_ERRNO(EIO),
339 "Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
340
341 r = fido_assert_set_rp(a, "io.systemd.home");
342 if (r != FIDO_OK)
343 return log_error_errno(SYNTHETIC_ERRNO(EIO),
344 "Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
345
346 r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
347 if (r != FIDO_OK)
348 return log_error_errno(SYNTHETIC_ERRNO(EIO),
349 "Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
350
351 r = fido_assert_allow_cred(a, cid, cid_size);
352 if (r != FIDO_OK)
353 return log_error_errno(SYNTHETIC_ERRNO(EIO),
354 "Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
355
356 r = fido_assert_set_up(a, FIDO_OPT_FALSE);
357 if (r != FIDO_OK)
358 return log_error_errno(SYNTHETIC_ERRNO(EIO),
359 "Failed to turn off FIDO2 assertion user presence: %s", fido_strerr(r));
360
361 log_info("Generating secret key on FIDO2 security token.");
362
363 r = fido_dev_get_assert(d, a, used_pin);
364 if (r == FIDO_ERR_UP_REQUIRED) {
365 r = fido_assert_set_up(a, FIDO_OPT_TRUE);
366 if (r != FIDO_OK)
367 return log_error_errno(SYNTHETIC_ERRNO(EIO),
368 "Failed to turn on FIDO2 assertion user presence: %s", fido_strerr(r));
369
370 log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.",
371 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
372 emoji_enabled() ? " " : "");
373
374 r = fido_dev_get_assert(d, a, used_pin);
375 }
376 if (r == FIDO_ERR_ACTION_TIMEOUT)
377 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
378 "Token action timeout. (User didn't interact with token quickly enough.)");
379 if (r != FIDO_OK)
380 return log_error_errno(SYNTHETIC_ERRNO(EIO),
381 "Failed to ask token for assertion: %s", fido_strerr(r));
382
383 secret = fido_assert_hmac_secret_ptr(a, 0);
384 if (!secret)
385 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
386
387 secret_size = fido_assert_hmac_secret_len(a, 0);
388
389 r = add_fido2_credential_id(v, cid, cid_size);
390 if (r < 0)
391 return r;
392
393 r = add_fido2_salt(v,
394 cid,
395 cid_size,
396 salt,
397 FIDO2_SALT_SIZE,
398 secret,
399 secret_size);
400 if (r < 0)
401 return r;
402
2af3966a
LP
403 /* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
404 * can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
405 * fscrypt. */
406 r = identity_add_token_pin(v, used_pin);
407 if (r < 0)
408 return r;
409
1c0c4a43
LP
410 return 0;
411#else
412 return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
413#endif
414}
415
416int list_fido2_devices(void) {
417#if HAVE_LIBFIDO2
418 _cleanup_(table_unrefp) Table *t = NULL;
419 size_t allocated = 64, found = 0;
420 fido_dev_info_t *di = NULL;
421 int r;
422
423 di = fido_dev_info_new(allocated);
424 if (!di)
425 return log_oom();
426
427 r = fido_dev_info_manifest(di, allocated, &found);
428 if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
429 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
430 log_info("No FIDO2 devices found.");
431 r = 0;
432 goto finish;
433 }
434 if (r != FIDO_OK) {
435 r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
436 goto finish;
437 }
438
439 t = table_new("path", "manufacturer", "product");
440 if (!t) {
441 r = log_oom();
442 goto finish;
443 }
444
445 for (size_t i = 0; i < found; i++) {
446 const fido_dev_info_t *entry;
447
448 entry = fido_dev_info_ptr(di, i);
449 if (!entry) {
450 r = log_error_errno(SYNTHETIC_ERRNO(EIO),
451 "Failed to get device information for FIDO device %zu.", i);
452 goto finish;
453 }
454
455 r = table_add_many(
456 t,
457 TABLE_PATH, fido_dev_info_path(entry),
458 TABLE_STRING, fido_dev_info_manufacturer_string(entry),
459 TABLE_STRING, fido_dev_info_product_string(entry));
460 if (r < 0) {
461 table_log_add_error(r);
462 goto finish;
463 }
464 }
465
466 r = table_print(t, stdout);
467 if (r < 0) {
468 log_error_errno(r, "Failed to show device table: %m");
469 goto finish;
470 }
471
472 r = 0;
473
474finish:
475 fido_dev_info_free(&di, allocated);
476 return r;
477#else
478 return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
479#endif
480}
481
482int find_fido2_auto(char **ret) {
483#if HAVE_LIBFIDO2
484 _cleanup_free_ char *copy = NULL;
485 size_t di_size = 64, found = 0;
486 const fido_dev_info_t *entry;
487 fido_dev_info_t *di = NULL;
488 const char *path;
489 int r;
490
491 di = fido_dev_info_new(di_size);
492 if (!di)
493 return log_oom();
494
495 r = fido_dev_info_manifest(di, di_size, &found);
496 if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
497 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
498 r = log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No FIDO2 devices found.");
499 goto finish;
500 }
501 if (r != FIDO_OK) {
502 r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
503 goto finish;
504 }
505 if (found > 1) {
506 r = log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "More than one FIDO2 device found.");
507 goto finish;
508 }
509
510 entry = fido_dev_info_ptr(di, 0);
511 if (!entry) {
512 r = log_error_errno(SYNTHETIC_ERRNO(EIO),
513 "Failed to get device information for FIDO device 0.");
514 goto finish;
515 }
516
517 path = fido_dev_info_path(entry);
518 if (!path) {
519 r = log_error_errno(SYNTHETIC_ERRNO(EIO),
520 "Failed to query FIDO device path.");
521 goto finish;
522 }
523
524 copy = strdup(path);
525 if (!copy) {
526 r = log_oom();
527 goto finish;
528 }
529
530 *ret = TAKE_PTR(copy);
531 r = 0;
532
533finish:
534 fido_dev_info_free(&di, di_size);
535 return r;
536#else
537 return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
538#endif
539}