1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 "homectl-pkcs11.h"
13 #include "libcrypt-util.h"
14 #include "libfido2-util.h"
15 #include "locale-util.h"
16 #include "memory-util.h"
17 #include "random-util.h"
21 static int add_fido2_credential_id(
26 _cleanup_(json_variant_unrefp
) JsonVariant
*w
= NULL
;
27 _cleanup_strv_free_
char **l
= NULL
;
28 _cleanup_free_
char *escaped
= NULL
;
34 r
= base64mem(cid
, cid_size
, &escaped
);
36 return log_error_errno(r
, "Failed to base64 encode FIDO2 credential ID: %m");
38 w
= json_variant_ref(json_variant_by_key(*v
, "fido2HmacCredential"));
40 r
= json_variant_strv(w
, &l
);
42 return log_error_errno(r
, "Failed to parse FIDO2 credential ID list: %m");
44 if (strv_contains(l
, escaped
))
48 r
= strv_extend(&l
, escaped
);
52 w
= json_variant_unref(w
);
53 r
= json_variant_new_array_strv(&w
, l
);
55 return log_error_errno(r
, "Failed to create FIDO2 credential ID JSON: %m");
57 r
= json_variant_set_field(v
, "fido2HmacCredential", w
);
59 return log_error_errno(r
, "Failed to update FIDO2 credential ID: %m");
64 static int add_fido2_salt(
68 const void *fido2_salt
,
69 size_t fido2_salt_size
,
73 _cleanup_(json_variant_unrefp
) JsonVariant
*l
= NULL
, *w
= NULL
, *e
= NULL
;
74 _cleanup_(erase_and_freep
) char *base64_encoded
= NULL
, *hashed
= NULL
;
77 /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
78 * expect a NUL terminated string, and we use a binary key */
79 r
= base64mem(secret
, secret_size
, &base64_encoded
);
81 return log_error_errno(r
, "Failed to base64 encode secret key: %m");
83 r
= hash_password(base64_encoded
, &hashed
);
85 return log_error_errno(errno_or_else(EINVAL
), "Failed to UNIX hash secret key: %m");
87 r
= json_build(&e
, JSON_BUILD_OBJECT(
88 JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid
, cid_size
)),
89 JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt
, fido2_salt_size
)),
90 JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed
))));
92 return log_error_errno(r
, "Failed to build FIDO2 salt JSON key object: %m");
94 w
= json_variant_ref(json_variant_by_key(*v
, "privileged"));
95 l
= json_variant_ref(json_variant_by_key(w
, "fido2HmacSalt"));
97 r
= json_variant_append_array(&l
, e
);
99 return log_error_errno(r
, "Failed append FIDO2 salt: %m");
101 r
= json_variant_set_field(&w
, "fido2HmacSalt", l
);
103 return log_error_errno(r
, "Failed to set FDO2 salt: %m");
105 r
= json_variant_set_field(v
, "privileged", w
);
107 return log_error_errno(r
, "Failed to update privileged field: %m");
113 #define FIDO2_SALT_SIZE 32
115 int identity_add_fido2_parameters(
117 const char *device
) {
120 _cleanup_(fido_cbor_info_free_wrapper
) fido_cbor_info_t
*di
= NULL
;
121 _cleanup_(fido_assert_free_wrapper
) fido_assert_t
*a
= NULL
;
122 _cleanup_(fido_cred_free_wrapper
) fido_cred_t
*c
= NULL
;
123 _cleanup_(fido_dev_free_wrapper
) fido_dev_t
*d
= NULL
;
124 _cleanup_(erase_and_freep
) char *used_pin
= NULL
;
125 _cleanup_(erase_and_freep
) void *salt
= NULL
;
126 JsonVariant
*un
, *realm
, *rn
;
127 bool found_extension
= false;
128 const void *cid
, *secret
;
130 size_t n
, cid_size
, secret_size
;
134 /* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
135 * HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
136 * authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
137 * device never sees the volume key.
139 * S = HMAC-SHA256(I, D)
141 * with: S → LUKS/account authentication key (never stored)
142 * I → internal key on FIDO2 device (stored in the FIDO2 device)
143 * D → salt we generate here (stored in the privileged part of the JSON record)
150 r
= dlopen_libfido2();
152 return log_error_errno(r
, "FIDO2 token support is not installed.");
154 salt
= malloc(FIDO2_SALT_SIZE
);
158 r
= genuine_random_bytes(salt
, FIDO2_SALT_SIZE
, RANDOM_BLOCK
);
160 return log_error_errno(r
, "Failed to generate salt: %m");
162 d
= sym_fido_dev_new();
166 r
= sym_fido_dev_open(d
, device
);
168 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
169 "Failed to open FIDO2 device %s: %s", device
, sym_fido_strerr(r
));
171 if (!sym_fido_dev_is_fido2(d
))
172 return log_error_errno(SYNTHETIC_ERRNO(ENODEV
),
173 "Specified device %s is not a FIDO2 device.", device
);
175 di
= sym_fido_cbor_info_new();
179 r
= sym_fido_dev_get_cbor_info(d
, di
);
181 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
182 "Failed to get CBOR device info for %s: %s", device
, sym_fido_strerr(r
));
184 e
= sym_fido_cbor_info_extensions_ptr(di
);
185 n
= sym_fido_cbor_info_extensions_len(di
);
187 for (size_t i
= 0; i
< n
; i
++)
188 if (streq(e
[i
], "hmac-secret")) {
189 found_extension
= true;
193 if (!found_extension
)
194 return log_error_errno(SYNTHETIC_ERRNO(ENODEV
),
195 "Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", device
);
197 c
= sym_fido_cred_new();
201 r
= sym_fido_cred_set_extensions(c
, FIDO_EXT_HMAC_SECRET
);
203 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
204 "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", sym_fido_strerr(r
));
206 r
= sym_fido_cred_set_rp(c
, "io.systemd.home", "Home Directory");
208 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
209 "Failed to set FIDO2 credential relying party ID/name: %s", sym_fido_strerr(r
));
211 r
= sym_fido_cred_set_type(c
, COSE_ES256
);
213 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
214 "Failed to set FIDO2 credential type to ES256: %s", sym_fido_strerr(r
));
216 un
= json_variant_by_key(*v
, "userName");
218 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
219 "userName field of user record is missing");
220 if (!json_variant_is_string(un
))
221 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
222 "userName field of user record is not a string");
224 realm
= json_variant_by_key(*v
, "realm");
226 if (!json_variant_is_string(realm
))
227 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
228 "realm field of user record is not a string");
230 fido_un
= strjoina(json_variant_string(un
), json_variant_string(realm
));
232 fido_un
= json_variant_string(un
);
234 rn
= json_variant_by_key(*v
, "realName");
235 if (rn
&& !json_variant_is_string(rn
))
236 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
237 "realName field of user record is not a string");
239 r
= sym_fido_cred_set_user(c
,
240 (const unsigned char*) fido_un
, strlen(fido_un
), /* We pass the user ID and name as the same */
242 rn
? json_variant_string(rn
) : NULL
,
243 NULL
/* icon URL */);
245 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
246 "Failed to set FIDO2 credential user data: %s", sym_fido_strerr(r
));
248 r
= sym_fido_cred_set_clientdata_hash(c
, (const unsigned char[32]) {}, 32);
250 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
251 "Failed to set FIDO2 client data hash: %s", sym_fido_strerr(r
));
253 r
= sym_fido_cred_set_rk(c
, FIDO_OPT_FALSE
);
255 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
256 "Failed to turn off FIDO2 resident key option of credential: %s", sym_fido_strerr(r
));
258 r
= sym_fido_cred_set_uv(c
, FIDO_OPT_FALSE
);
260 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
261 "Failed to turn off FIDO2 user verification option of credential: %s", sym_fido_strerr(r
));
263 log_info("Initializing FIDO2 credential on security token.");
265 log_notice("%s%s(Hint: This might require verification of user presence on security token.)",
266 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH
) : "",
267 emoji_enabled() ? " " : "");
269 r
= sym_fido_dev_make_cred(d
, c
, NULL
);
270 if (r
== FIDO_ERR_PIN_REQUIRED
) {
271 _cleanup_free_
char *text
= NULL
;
273 if (asprintf(&text
, "Please enter security token PIN:") < 0)
277 _cleanup_(strv_free_erasep
) char **pin
= NULL
;
280 r
= ask_password_auto(text
, "user-home", NULL
, "fido2-pin", USEC_INFINITY
, 0, &pin
);
282 return log_error_errno(r
, "Failed to acquire user PIN: %m");
284 r
= FIDO_ERR_PIN_INVALID
;
285 STRV_FOREACH(i
, pin
) {
287 log_info("PIN may not be empty.");
291 r
= sym_fido_dev_make_cred(d
, c
, *i
);
293 used_pin
= strdup(*i
);
298 if (r
!= FIDO_ERR_PIN_INVALID
)
302 if (r
!= FIDO_ERR_PIN_INVALID
)
305 log_notice("PIN incorrect, please try again.");
308 if (r
== FIDO_ERR_PIN_AUTH_BLOCKED
)
309 return log_notice_errno(SYNTHETIC_ERRNO(EPERM
),
310 "Token PIN is currently blocked, please remove and reinsert token.");
311 if (r
== FIDO_ERR_ACTION_TIMEOUT
)
312 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR
),
313 "Token action timeout. (User didn't interact with token quickly enough.)");
315 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
316 "Failed to generate FIDO2 credential: %s", sym_fido_strerr(r
));
318 cid
= sym_fido_cred_id_ptr(c
);
320 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to get FIDO2 credential ID.");
322 cid_size
= sym_fido_cred_id_len(c
);
324 a
= sym_fido_assert_new();
328 r
= sym_fido_assert_set_extensions(a
, FIDO_EXT_HMAC_SECRET
);
330 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
331 "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r
));
333 r
= sym_fido_assert_set_hmac_salt(a
, salt
, FIDO2_SALT_SIZE
);
335 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
336 "Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r
));
338 r
= sym_fido_assert_set_rp(a
, "io.systemd.home");
340 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
341 "Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r
));
343 r
= sym_fido_assert_set_clientdata_hash(a
, (const unsigned char[32]) {}, 32);
345 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
346 "Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r
));
348 r
= sym_fido_assert_allow_cred(a
, cid
, cid_size
);
350 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
351 "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r
));
353 r
= sym_fido_assert_set_up(a
, FIDO_OPT_FALSE
);
355 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
356 "Failed to turn off FIDO2 assertion user presence: %s", sym_fido_strerr(r
));
358 log_info("Generating secret key on FIDO2 security token.");
360 r
= sym_fido_dev_get_assert(d
, a
, used_pin
);
361 if (r
== FIDO_ERR_UP_REQUIRED
) {
362 r
= sym_fido_assert_set_up(a
, FIDO_OPT_TRUE
);
364 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
365 "Failed to turn on FIDO2 assertion user presence: %s", sym_fido_strerr(r
));
367 log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.",
368 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH
) : "",
369 emoji_enabled() ? " " : "");
371 r
= sym_fido_dev_get_assert(d
, a
, used_pin
);
373 if (r
== FIDO_ERR_ACTION_TIMEOUT
)
374 return log_error_errno(SYNTHETIC_ERRNO(ENOSTR
),
375 "Token action timeout. (User didn't interact with token quickly enough.)");
377 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
378 "Failed to ask token for assertion: %s", sym_fido_strerr(r
));
380 secret
= sym_fido_assert_hmac_secret_ptr(a
, 0);
382 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to retrieve HMAC secret.");
384 secret_size
= sym_fido_assert_hmac_secret_len(a
, 0);
386 r
= add_fido2_credential_id(v
, cid
, cid_size
);
390 r
= add_fido2_salt(v
,
400 /* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
401 * can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
403 r
= identity_add_token_pin(v
, used_pin
);
409 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
410 "FIDO2 tokens not supported on this build.");
414 int list_fido2_devices(void) {
416 _cleanup_(table_unrefp
) Table
*t
= NULL
;
417 size_t allocated
= 64, found
= 0;
418 fido_dev_info_t
*di
= NULL
;
421 r
= dlopen_libfido2();
423 return log_error_errno(r
, "FIDO2 token support is not installed.");
425 di
= sym_fido_dev_info_new(allocated
);
429 r
= sym_fido_dev_info_manifest(di
, allocated
, &found
);
430 if (r
== FIDO_ERR_INTERNAL
|| (r
== FIDO_OK
&& found
== 0)) {
431 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
432 log_info("No FIDO2 devices found.");
437 r
= log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r
));
441 t
= table_new("path", "manufacturer", "product");
447 for (size_t i
= 0; i
< found
; i
++) {
448 const fido_dev_info_t
*entry
;
450 entry
= sym_fido_dev_info_ptr(di
, i
);
452 r
= log_error_errno(SYNTHETIC_ERRNO(EIO
),
453 "Failed to get device information for FIDO device %zu.", i
);
459 TABLE_PATH
, sym_fido_dev_info_path(entry
),
460 TABLE_STRING
, sym_fido_dev_info_manufacturer_string(entry
),
461 TABLE_STRING
, sym_fido_dev_info_product_string(entry
));
463 table_log_add_error(r
);
468 r
= table_print(t
, stdout
);
470 log_error_errno(r
, "Failed to show device table: %m");
477 sym_fido_dev_info_free(&di
, allocated
);
480 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
481 "FIDO2 tokens not supported on this build.");
485 int find_fido2_auto(char **ret
) {
487 _cleanup_free_
char *copy
= NULL
;
488 size_t di_size
= 64, found
= 0;
489 const fido_dev_info_t
*entry
;
490 fido_dev_info_t
*di
= NULL
;
494 r
= dlopen_libfido2();
496 return log_error_errno(r
, "FIDO2 token support is not installed.");
498 di
= sym_fido_dev_info_new(di_size
);
502 r
= sym_fido_dev_info_manifest(di
, di_size
, &found
);
503 if (r
== FIDO_ERR_INTERNAL
|| (r
== FIDO_OK
&& found
== 0)) {
504 /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
505 r
= log_error_errno(SYNTHETIC_ERRNO(ENODEV
), "No FIDO2 devices found.");
509 r
= log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r
));
513 r
= log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ
), "More than one FIDO2 device found.");
517 entry
= sym_fido_dev_info_ptr(di
, 0);
519 r
= log_error_errno(SYNTHETIC_ERRNO(EIO
),
520 "Failed to get device information for FIDO device 0.");
524 path
= sym_fido_dev_info_path(entry
);
526 r
= log_error_errno(SYNTHETIC_ERRNO(EIO
),
527 "Failed to query FIDO device path.");
537 *ret
= TAKE_PTR(copy
);
541 sym_fido_dev_info_free(&di
, di_size
);
544 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
545 "FIDO2 tokens not supported on this build.");