]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/homectl-fido2.c
homectl: split out pkcs#11 related code bits into own .c/.h file
[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"
12#include "libcrypt-util.h"
13#include "locale-util.h"
14#include "memory-util.h"
15#include "random-util.h"
16#include "strv.h"
17
18#if HAVE_LIBFIDO2
19static int add_fido2_credential_id(
20 JsonVariant **v,
21 const void *cid,
22 size_t cid_size) {
23
24 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
25 _cleanup_strv_free_ char **l = NULL;
26 _cleanup_free_ char *escaped = NULL;
27 int r;
28
29 assert(v);
30 assert(cid);
31
32 r = base64mem(cid, cid_size, &escaped);
33 if (r < 0)
34 return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
35
36 w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential"));
37 if (w) {
38 r = json_variant_strv(w, &l);
39 if (r < 0)
40 return log_error_errno(r, "Failed to parse FIDO2 credential ID list: %m");
41
42 if (strv_contains(l, escaped))
43 return 0;
44 }
45
46 r = strv_extend(&l, escaped);
47 if (r < 0)
48 return log_oom();
49
50 w = json_variant_unref(w);
51 r = json_variant_new_array_strv(&w, l);
52 if (r < 0)
53 return log_error_errno(r, "Failed to create FIDO2 credential ID JSON: %m");
54
55 r = json_variant_set_field(v, "fido2HmacCredential", w);
56 if (r < 0)
57 return log_error_errno(r, "Failed to update FIDO2 credential ID: %m");
58
59 return 0;
60}
61
62static int add_fido2_salt(
63 JsonVariant **v,
64 const void *cid,
65 size_t cid_size,
66 const void *fido2_salt,
67 size_t fido2_salt_size,
68 const void *secret,
69 size_t secret_size) {
70
71 _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
72 _cleanup_(erase_and_freep) char *base64_encoded = NULL;
73 _cleanup_free_ char *unix_salt = NULL;
74 struct crypt_data cd = {};
75 char *k;
76 int r;
77
78 r = make_salt(&unix_salt);
79 if (r < 0)
80 return log_error_errno(r, "Failed to generate salt: %m");
81
82 /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
83 * expect a NUL terminated string, and we use a binary key */
84 r = base64mem(secret, secret_size, &base64_encoded);
85 if (r < 0)
86 return log_error_errno(r, "Failed to base64 encode secret key: %m");
87
88 errno = 0;
89 k = crypt_r(base64_encoded, unix_salt, &cd);
90 if (!k)
91 return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
92
93 r = json_build(&e, JSON_BUILD_OBJECT(
94 JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
95 JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
96 JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
97 if (r < 0)
98 return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");
99
100 w = json_variant_ref(json_variant_by_key(*v, "privileged"));
101 l = json_variant_ref(json_variant_by_key(w, "fido2HmacSalt"));
102
103 r = json_variant_append_array(&l, e);
104 if (r < 0)
105 return log_error_errno(r, "Failed append FIDO2 salt: %m");
106
107 r = json_variant_set_field(&w, "fido2HmacSalt", l);
108 if (r < 0)
109 return log_error_errno(r, "Failed to set FDO2 salt: %m");
110
111 r = json_variant_set_field(v, "privileged", w);
112 if (r < 0)
113 return log_error_errno(r, "Failed to update privileged field: %m");
114
115 return 0;
116}
117#endif
118
119#define FIDO2_SALT_SIZE 32
120
121int identity_add_fido2_parameters(
122 JsonVariant **v,
123 const char *device) {
124
125#if HAVE_LIBFIDO2
126 _cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
127 _cleanup_(fido_assert_free) fido_assert_t *a = NULL;
128 _cleanup_(erase_and_freep) char *used_pin = NULL;
129 _cleanup_(fido_cred_free) fido_cred_t *c = NULL;
130 _cleanup_(fido_dev_free) fido_dev_t *d = NULL;
131 _cleanup_(erase_and_freep) void *salt = NULL;
132 JsonVariant *un, *realm, *rn;
133 bool found_extension = false;
134 const void *cid, *secret;
135 const char *fido_un;
136 size_t n, cid_size, secret_size;
137 char **e;
138 int r;
139
140 /* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
141 * HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
142 * authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
143 * device never sees the volume key.
144 *
145 * S = HMAC-SHA256(I, D)
146 *
147 * with: S → LUKS/account authentication key (never stored)
148 * I → internal key on FIDO2 device (stored in the FIDO2 device)
149 * D → salt we generate here (stored in the privileged part of the JSON record)
150 *
151 */
152
153 assert(v);
154 assert(device);
155
156 salt = malloc(FIDO2_SALT_SIZE);
157 if (!salt)
158 return log_oom();
159
160 r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
161 if (r < 0)
162 return log_error_errno(r, "Failed to generate salt: %m");
163
164 d = fido_dev_new();
165 if (!d)
166 return log_oom();
167
168 r = fido_dev_open(d, device);
169 if (r != FIDO_OK)
170 return log_error_errno(SYNTHETIC_ERRNO(EIO),
171 "Failed to open FIDO2 device %s: %s", device, fido_strerr(r));
172
173 if (!fido_dev_is_fido2(d))
174 return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
175 "Specified device %s is not a FIDO2 device.", device);
176
177 di = fido_cbor_info_new();
178 if (!di)
179 return log_oom();
180
181 r = fido_dev_get_cbor_info(d, di);
182 if (r != FIDO_OK)
183 return log_error_errno(SYNTHETIC_ERRNO(EIO),
184 "Failed to get CBOR device info for %s: %s", device, fido_strerr(r));
185
186 e = fido_cbor_info_extensions_ptr(di);
187 n = fido_cbor_info_extensions_len(di);
188
189 for (size_t i = 0; i < n; i++)
190 if (streq(e[i], "hmac-secret")) {
191 found_extension = true;
192 break;
193 }
194
195 if (!found_extension)
196 return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
197 "Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", device);
198
199 c = fido_cred_new();
200 if (!c)
201 return log_oom();
202
203 r = fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
204 if (r != FIDO_OK)
205 return log_error_errno(SYNTHETIC_ERRNO(EIO),
206 "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", fido_strerr(r));
207
208 r = fido_cred_set_rp(c, "io.systemd.home", "Home Directory");
209 if (r != FIDO_OK)
210 return log_error_errno(SYNTHETIC_ERRNO(EIO),
211 "Failed to set FIDO2 credential relying party ID/name: %s", fido_strerr(r));
212
213 r = fido_cred_set_type(c, COSE_ES256);
214 if (r != FIDO_OK)
215 return log_error_errno(SYNTHETIC_ERRNO(EIO),
216 "Failed to set FIDO2 credential type to ES256: %s", fido_strerr(r));
217
218 un = json_variant_by_key(*v, "userName");
219 if (!un)
220 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
221 "userName field of user record is missing");
222 if (!json_variant_is_string(un))
223 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
224 "userName field of user record is not a string");
225
226 realm = json_variant_by_key(*v, "realm");
227 if (realm) {
228 if (!json_variant_is_string(realm))
229 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
230 "realm field of user record is not a string");
231
232 fido_un = strjoina(json_variant_string(un), json_variant_string(realm));
233 } else
234 fido_un = json_variant_string(un);
235
236 rn = json_variant_by_key(*v, "realName");
237 if (rn && !json_variant_is_string(rn))
238 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
239 "realName field of user record is not a string");
240
241 r = fido_cred_set_user(c,
242 (const unsigned char*) fido_un, strlen(fido_un), /* We pass the user ID and name as the same */
243 fido_un,
244 rn ? json_variant_string(rn) : NULL,
245 NULL /* icon URL */);
246 if (r != FIDO_OK)
247 return log_error_errno(SYNTHETIC_ERRNO(EIO),
248 "Failed to set FIDO2 credential user data: %s", fido_strerr(r));
249
250 r = fido_cred_set_clientdata_hash(c, (const unsigned char[32]) {}, 32);
251 if (r != FIDO_OK)
252 return log_error_errno(SYNTHETIC_ERRNO(EIO),
253 "Failed to set FIDO2 client data hash: %s", fido_strerr(r));
254
255 r = fido_cred_set_rk(c, FIDO_OPT_FALSE);
256 if (r != FIDO_OK)
257 return log_error_errno(SYNTHETIC_ERRNO(EIO),
258 "Failed to turn off FIDO2 resident key option of credential: %s", fido_strerr(r));
259
260 r = fido_cred_set_uv(c, FIDO_OPT_FALSE);
261 if (r != FIDO_OK)
262 return log_error_errno(SYNTHETIC_ERRNO(EIO),
263 "Failed to turn off FIDO2 user verification option of credential: %s", fido_strerr(r));
264
265 log_info("Initializing FIDO2 credential on security token.");
266
267 log_notice("%s%s(Hint: This might require verification of user presence on security token.)",
268 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
269 emoji_enabled() ? " " : "");
270
271 r = fido_dev_make_cred(d, c, NULL);
272 if (r == FIDO_ERR_PIN_REQUIRED) {
273 _cleanup_free_ char *text = NULL;
274
275 if (asprintf(&text, "Please enter security token PIN:") < 0)
276 return log_oom();
277
278 for (;;) {
279 _cleanup_(strv_free_erasep) char **pin = NULL;
280 char **i;
281
282 r = ask_password_auto(text, "user-home", NULL, "fido2-pin", USEC_INFINITY, 0, &pin);
283 if (r < 0)
284 return log_error_errno(r, "Failed to acquire user PIN: %m");
285
286 r = FIDO_ERR_PIN_INVALID;
287 STRV_FOREACH(i, pin) {
288 if (isempty(*i)) {
289 log_info("PIN may not be empty.");
290 continue;
291 }
292
293 r = fido_dev_make_cred(d, c, *i);
294 if (r == FIDO_OK) {
295 used_pin = strdup(*i);
296 if (!used_pin)
297 return log_oom();
298 break;
299 }
300 if (r != FIDO_ERR_PIN_INVALID)
301 break;
302 }
303
304 if (r != FIDO_ERR_PIN_INVALID)
305 break;
306
307 log_notice("PIN incorrect, please try again.");
308 }
309 }
310 if (r == FIDO_ERR_PIN_AUTH_BLOCKED)
311 return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
312 "Token PIN is currently blocked, please remove and reinsert token.");
313 if (r == FIDO_ERR_ACTION_TIMEOUT)
314 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
315 "Token action timeout. (User didn't interact with token quickly enough.)");
316 if (r != FIDO_OK)
317 return log_error_errno(SYNTHETIC_ERRNO(EIO),
318 "Failed to generate FIDO2 credential: %s", fido_strerr(r));
319
320 cid = fido_cred_id_ptr(c);
321 if (!cid)
322 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get FIDO2 credential ID.");
323
324 cid_size = fido_cred_id_len(c);
325
326 a = fido_assert_new();
327 if (!a)
328 return log_oom();
329
330 r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
331 if (r != FIDO_OK)
332 return log_error_errno(SYNTHETIC_ERRNO(EIO),
333 "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
334
335 r = fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
336 if (r != FIDO_OK)
337 return log_error_errno(SYNTHETIC_ERRNO(EIO),
338 "Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
339
340 r = fido_assert_set_rp(a, "io.systemd.home");
341 if (r != FIDO_OK)
342 return log_error_errno(SYNTHETIC_ERRNO(EIO),
343 "Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
344
345 r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
346 if (r != FIDO_OK)
347 return log_error_errno(SYNTHETIC_ERRNO(EIO),
348 "Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
349
350 r = fido_assert_allow_cred(a, cid, cid_size);
351 if (r != FIDO_OK)
352 return log_error_errno(SYNTHETIC_ERRNO(EIO),
353 "Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
354
355 r = fido_assert_set_up(a, FIDO_OPT_FALSE);
356 if (r != FIDO_OK)
357 return log_error_errno(SYNTHETIC_ERRNO(EIO),
358 "Failed to turn off FIDO2 assertion user presence: %s", fido_strerr(r));
359
360 log_info("Generating secret key on FIDO2 security token.");
361
362 r = fido_dev_get_assert(d, a, used_pin);
363 if (r == FIDO_ERR_UP_REQUIRED) {
364 r = fido_assert_set_up(a, FIDO_OPT_TRUE);
365 if (r != FIDO_OK)
366 return log_error_errno(SYNTHETIC_ERRNO(EIO),
367 "Failed to turn on FIDO2 assertion user presence: %s", fido_strerr(r));
368
369 log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.",
370 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
371 emoji_enabled() ? " " : "");
372
373 r = fido_dev_get_assert(d, a, used_pin);
374 }
375 if (r == FIDO_ERR_ACTION_TIMEOUT)
376 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
377 "Token action timeout. (User didn't interact with token quickly enough.)");
378 if (r != FIDO_OK)
379 return log_error_errno(SYNTHETIC_ERRNO(EIO),
380 "Failed to ask token for assertion: %s", fido_strerr(r));
381
382 secret = fido_assert_hmac_secret_ptr(a, 0);
383 if (!secret)
384 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
385
386 secret_size = fido_assert_hmac_secret_len(a, 0);
387
388 r = add_fido2_credential_id(v, cid, cid_size);
389 if (r < 0)
390 return r;
391
392 r = add_fido2_salt(v,
393 cid,
394 cid_size,
395 salt,
396 FIDO2_SALT_SIZE,
397 secret,
398 secret_size);
399 if (r < 0)
400 return r;
401
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
529 return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
530#endif
531}