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