]>
Commit | Line | Data |
---|---|---|
8710a681 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <getopt.h> | |
4 | ||
5 | #include "ask-password-api.h" | |
6 | #include "cryptenroll-fido2.h" | |
d2fafc42 | 7 | #include "cryptenroll-list.h" |
8710a681 LP |
8 | #include "cryptenroll-password.h" |
9 | #include "cryptenroll-pkcs11.h" | |
10 | #include "cryptenroll-recovery.h" | |
5e521624 | 11 | #include "cryptenroll-tpm2.h" |
d2fafc42 LP |
12 | #include "cryptenroll-wipe.h" |
13 | #include "cryptenroll.h" | |
8710a681 LP |
14 | #include "cryptsetup-util.h" |
15 | #include "escape.h" | |
16 | #include "libfido2-util.h" | |
17 | #include "main-func.h" | |
18 | #include "memory-util.h" | |
5e521624 | 19 | #include "parse-util.h" |
8710a681 LP |
20 | #include "path-util.h" |
21 | #include "pkcs11-util.h" | |
22 | #include "pretty-print.h" | |
d2fafc42 | 23 | #include "string-table.h" |
8710a681 LP |
24 | #include "strv.h" |
25 | #include "terminal-util.h" | |
5e521624 | 26 | #include "tpm2-util.h" |
8710a681 | 27 | |
8710a681 LP |
28 | static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; |
29 | static char *arg_pkcs11_token_uri = NULL; | |
30 | static char *arg_fido2_device = NULL; | |
31 | static char *arg_tpm2_device = NULL; | |
5e521624 | 32 | static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; |
8710a681 | 33 | static char *arg_node = NULL; |
d2fafc42 LP |
34 | static int *arg_wipe_slots = NULL; |
35 | static size_t arg_n_wipe_slots = 0; | |
36 | static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT; | |
37 | static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */ | |
38 | ||
39 | assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); | |
8710a681 LP |
40 | |
41 | STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); | |
42 | STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); | |
43 | STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); | |
44 | STATIC_DESTRUCTOR_REGISTER(arg_node, freep); | |
45 | ||
d2fafc42 LP |
46 | static bool wipe_requested(void) { |
47 | return arg_n_wipe_slots > 0 || | |
48 | arg_wipe_slots_scope != WIPE_EXPLICIT || | |
49 | arg_wipe_slots_mask != 0; | |
50 | } | |
51 | ||
52 | static const char* const enroll_type_table[_ENROLL_TYPE_MAX] = { | |
53 | [ENROLL_PASSWORD] = "password", | |
54 | [ENROLL_RECOVERY] = "recovery", | |
55 | [ENROLL_PKCS11] = "pkcs11", | |
56 | [ENROLL_FIDO2] = "fido2", | |
57 | [ENROLL_TPM2] = "tpm2", | |
58 | }; | |
59 | ||
60 | DEFINE_STRING_TABLE_LOOKUP(enroll_type, EnrollType); | |
61 | ||
62 | static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = { | |
63 | /* ENROLL_PASSWORD has no entry here, as slots of this type do not have a token in the LUKS2 header */ | |
64 | [ENROLL_RECOVERY] = "systemd-recovery", | |
65 | [ENROLL_PKCS11] = "systemd-pkcs11", | |
66 | [ENROLL_FIDO2] = "systemd-fido2", | |
67 | [ENROLL_TPM2] = "systemd-tpm2", | |
68 | }; | |
69 | ||
70 | DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType); | |
71 | ||
8710a681 LP |
72 | static int help(void) { |
73 | _cleanup_free_ char *link = NULL; | |
74 | int r; | |
75 | ||
76 | r = terminal_urlify_man("systemd-cryptenroll", "1", &link); | |
77 | if (r < 0) | |
78 | return log_oom(); | |
79 | ||
80 | printf("%s [OPTIONS...] BLOCK-DEVICE\n" | |
81 | "\n%sEnroll a security token or authentication credential to a LUKS volume.%s\n\n" | |
82 | " -h --help Show this help\n" | |
83 | " --version Show package version\n" | |
84 | " --password Enroll a user-supplied password\n" | |
85 | " --recovery-key Enroll a recovery key\n" | |
86 | " --pkcs11-token-uri=URI\n" | |
87 | " Specify PKCS#11 security token URI\n" | |
88 | " --fido2-device=PATH\n" | |
89 | " Enroll a FIDO2-HMAC security token\n" | |
5e521624 LP |
90 | " --tpm2-device=PATH\n" |
91 | " Enroll a TPM2 device\n" | |
92 | " --tpm2-pcrs=PCR1,PCR2,PCR3,…\n" | |
45861042 | 93 | " Specify TPM2 PCRs to seal against\n" |
d2fafc42 LP |
94 | " --wipe-slot=SLOT1,SLOT2,…\n" |
95 | " Wipe specified slots\n" | |
bc556335 DDM |
96 | "\nSee the %s for details.\n", |
97 | program_invocation_short_name, | |
98 | ansi_highlight(), | |
99 | ansi_normal(), | |
100 | link); | |
8710a681 LP |
101 | |
102 | return 0; | |
103 | } | |
104 | ||
105 | static int parse_argv(int argc, char *argv[]) { | |
106 | ||
107 | enum { | |
108 | ARG_VERSION = 0x100, | |
109 | ARG_PASSWORD, | |
110 | ARG_RECOVERY_KEY, | |
111 | ARG_PKCS11_TOKEN_URI, | |
112 | ARG_FIDO2_DEVICE, | |
5e521624 LP |
113 | ARG_TPM2_DEVICE, |
114 | ARG_TPM2_PCRS, | |
d2fafc42 | 115 | ARG_WIPE_SLOT, |
8710a681 LP |
116 | }; |
117 | ||
118 | static const struct option options[] = { | |
119 | { "help", no_argument, NULL, 'h' }, | |
120 | { "version", no_argument, NULL, ARG_VERSION }, | |
121 | { "password", no_argument, NULL, ARG_PASSWORD }, | |
122 | { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, | |
123 | { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, | |
124 | { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, | |
5e521624 LP |
125 | { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, |
126 | { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, | |
d2fafc42 | 127 | { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, |
8710a681 LP |
128 | {} |
129 | }; | |
130 | ||
131 | int c, r; | |
132 | ||
133 | assert(argc >= 0); | |
134 | assert(argv); | |
135 | ||
136 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { | |
137 | ||
138 | switch (c) { | |
139 | ||
140 | case 'h': | |
141 | return help(); | |
142 | ||
143 | case ARG_VERSION: | |
144 | return version(); | |
145 | ||
146 | case ARG_PASSWORD: | |
147 | if (arg_enroll_type >= 0) | |
148 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
149 | "Multiple operations specified at once, refusing."); | |
150 | ||
151 | arg_enroll_type = ENROLL_PASSWORD; | |
152 | break; | |
153 | ||
154 | case ARG_RECOVERY_KEY: | |
155 | if (arg_enroll_type >= 0) | |
156 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
157 | "Multiple operations specified at once, refusing."); | |
158 | ||
159 | arg_enroll_type = ENROLL_RECOVERY; | |
160 | break; | |
161 | ||
162 | case ARG_PKCS11_TOKEN_URI: { | |
163 | _cleanup_free_ char *uri = NULL; | |
164 | ||
165 | if (streq(optarg, "list")) | |
166 | return pkcs11_list_tokens(); | |
167 | ||
168 | if (arg_enroll_type >= 0 || arg_pkcs11_token_uri) | |
169 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
170 | "Multiple operations specified at once, refusing."); | |
171 | ||
172 | if (streq(optarg, "auto")) { | |
173 | r = pkcs11_find_token_auto(&uri); | |
174 | if (r < 0) | |
175 | return r; | |
176 | } else { | |
177 | if (!pkcs11_uri_valid(optarg)) | |
178 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg); | |
179 | ||
180 | uri = strdup(optarg); | |
181 | if (!uri) | |
182 | return log_oom(); | |
183 | } | |
184 | ||
185 | arg_enroll_type = ENROLL_PKCS11; | |
186 | arg_pkcs11_token_uri = TAKE_PTR(uri); | |
187 | break; | |
188 | } | |
189 | ||
190 | case ARG_FIDO2_DEVICE: { | |
191 | _cleanup_free_ char *device = NULL; | |
192 | ||
193 | if (streq(optarg, "list")) | |
194 | return fido2_list_devices(); | |
195 | ||
196 | if (arg_enroll_type >= 0 || arg_fido2_device) | |
197 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
198 | "Multiple operations specified at once, refusing."); | |
199 | ||
200 | if (streq(optarg, "auto")) { | |
201 | r = fido2_find_device_auto(&device); | |
202 | if (r < 0) | |
203 | return r; | |
204 | } else { | |
205 | device = strdup(optarg); | |
206 | if (!device) | |
207 | return log_oom(); | |
208 | } | |
209 | ||
210 | arg_enroll_type = ENROLL_FIDO2; | |
211 | arg_fido2_device = TAKE_PTR(device); | |
212 | break; | |
213 | } | |
214 | ||
5e521624 LP |
215 | case ARG_TPM2_DEVICE: { |
216 | _cleanup_free_ char *device = NULL; | |
217 | ||
218 | if (streq(optarg, "list")) | |
219 | return tpm2_list_devices(); | |
220 | ||
221 | if (arg_enroll_type >= 0 || arg_tpm2_device) | |
222 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
223 | "Multiple operations specified at once, refusing."); | |
224 | ||
225 | if (!streq(optarg, "auto")) { | |
226 | device = strdup(optarg); | |
227 | if (!device) | |
228 | return log_oom(); | |
229 | } | |
230 | ||
231 | arg_enroll_type = ENROLL_TPM2; | |
232 | arg_tpm2_device = TAKE_PTR(device); | |
233 | break; | |
234 | } | |
235 | ||
236 | case ARG_TPM2_PCRS: { | |
237 | uint32_t mask; | |
238 | ||
239 | if (isempty(optarg)) { | |
240 | arg_tpm2_pcr_mask = 0; | |
241 | break; | |
242 | } | |
243 | ||
244 | r = tpm2_parse_pcrs(optarg, &mask); | |
245 | if (r < 0) | |
246 | return r; | |
247 | ||
248 | if (arg_tpm2_pcr_mask == UINT32_MAX) | |
249 | arg_tpm2_pcr_mask = mask; | |
250 | else | |
251 | arg_tpm2_pcr_mask |= mask; | |
252 | ||
253 | break; | |
254 | } | |
255 | ||
d2fafc42 LP |
256 | case ARG_WIPE_SLOT: { |
257 | const char *p = optarg; | |
258 | ||
259 | if (isempty(optarg)) { | |
260 | arg_wipe_slots_mask = 0; | |
261 | arg_wipe_slots_scope = WIPE_EXPLICIT; | |
262 | break; | |
263 | } | |
264 | ||
265 | for (;;) { | |
266 | _cleanup_free_ char *slot = NULL; | |
267 | unsigned n; | |
268 | ||
269 | r = extract_first_word(&p, &slot, ",", EXTRACT_DONT_COALESCE_SEPARATORS); | |
270 | if (r == 0) | |
271 | break; | |
272 | if (r < 0) | |
273 | return log_error_errno(r, "Failed to parse slot list: %s", optarg); | |
274 | ||
275 | if (streq(slot, "all")) | |
276 | arg_wipe_slots_scope = WIPE_ALL; | |
277 | else if (streq(slot, "empty")) { | |
278 | if (arg_wipe_slots_scope != WIPE_ALL) /* if "all" was specified before, that wins */ | |
279 | arg_wipe_slots_scope = WIPE_EMPTY_PASSPHRASE; | |
280 | } else if (streq(slot, "password")) | |
281 | arg_wipe_slots_mask = 1U << ENROLL_PASSWORD; | |
282 | else if (streq(slot, "recovery")) | |
283 | arg_wipe_slots_mask = 1U << ENROLL_RECOVERY; | |
284 | else if (streq(slot, "pkcs11")) | |
285 | arg_wipe_slots_mask = 1U << ENROLL_PKCS11; | |
286 | else if (streq(slot, "fido2")) | |
287 | arg_wipe_slots_mask = 1U << ENROLL_FIDO2; | |
288 | else if (streq(slot, "tpm2")) | |
289 | arg_wipe_slots_mask = 1U << ENROLL_TPM2; | |
290 | else { | |
291 | int *a; | |
292 | ||
293 | r = safe_atou(slot, &n); | |
294 | if (r < 0) | |
295 | return log_error_errno(r, "Failed to parse slot index: %s", slot); | |
296 | if (n > INT_MAX) | |
297 | return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Slot index out of range: %u", n); | |
298 | ||
299 | a = reallocarray(arg_wipe_slots, sizeof(int), arg_n_wipe_slots + 1); | |
300 | if (!a) | |
301 | return log_oom(); | |
302 | ||
303 | arg_wipe_slots = a; | |
304 | arg_wipe_slots[arg_n_wipe_slots++] = (int) n; | |
305 | } | |
306 | } | |
307 | break; | |
308 | } | |
309 | ||
8710a681 LP |
310 | case '?': |
311 | return -EINVAL; | |
312 | ||
313 | default: | |
314 | assert_not_reached("Unhandled option"); | |
315 | } | |
316 | } | |
317 | ||
8710a681 LP |
318 | if (optind >= argc) |
319 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
320 | "No block device node specified, refusing."); | |
321 | ||
322 | if (argc > optind+1) | |
323 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
324 | "Too many arguments, refusing."); | |
325 | ||
326 | r = parse_path_argument_and_warn(argv[optind], false, &arg_node); | |
327 | if (r < 0) | |
328 | return r; | |
329 | ||
5e521624 LP |
330 | if (arg_tpm2_pcr_mask == UINT32_MAX) |
331 | arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; | |
332 | ||
8710a681 LP |
333 | return 1; |
334 | } | |
335 | ||
336 | static int prepare_luks( | |
337 | struct crypt_device **ret_cd, | |
338 | void **ret_volume_key, | |
339 | size_t *ret_volume_key_size) { | |
340 | ||
341 | _cleanup_(crypt_freep) struct crypt_device *cd = NULL; | |
342 | _cleanup_(erase_and_freep) void *vk = NULL; | |
343 | char *e = NULL; | |
344 | size_t vks; | |
345 | int r; | |
346 | ||
347 | assert(ret_cd); | |
348 | assert(!ret_volume_key == !ret_volume_key_size); | |
349 | ||
350 | r = crypt_init(&cd, arg_node); | |
351 | if (r < 0) | |
352 | return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); | |
353 | ||
354 | cryptsetup_enable_logging(cd); | |
355 | ||
356 | r = crypt_load(cd, CRYPT_LUKS2, NULL); | |
357 | if (r < 0) | |
358 | return log_error_errno(r, "Failed to load LUKS2 superblock: %m"); | |
359 | ||
360 | if (!ret_volume_key) { | |
361 | *ret_cd = TAKE_PTR(cd); | |
362 | return 0; | |
363 | } | |
364 | ||
365 | r = crypt_get_volume_key_size(cd); | |
366 | if (r <= 0) | |
367 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); | |
368 | vks = (size_t) r; | |
369 | ||
370 | vk = malloc(vks); | |
371 | if (!vk) | |
372 | return log_oom(); | |
373 | ||
374 | e = getenv("PASSWORD"); | |
375 | if (e) { | |
376 | _cleanup_(erase_and_freep) char *password = NULL; | |
377 | ||
378 | password = strdup(e); | |
379 | if (!password) | |
380 | return log_oom(); | |
381 | ||
382 | string_erase(e); | |
383 | assert_se(unsetenv("PASSWORD") >= 0); | |
384 | ||
385 | r = crypt_volume_key_get( | |
386 | cd, | |
387 | CRYPT_ANY_SLOT, | |
388 | vk, | |
389 | &vks, | |
390 | password, | |
391 | strlen(password)); | |
392 | if (r < 0) | |
45861042 | 393 | return log_error_errno(r, "Password from environment variable $PASSWORD did not work."); |
8710a681 LP |
394 | } else { |
395 | AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED; | |
396 | _cleanup_free_ char *question = NULL, *disk_path = NULL; | |
397 | unsigned i = 5; | |
398 | const char *id; | |
399 | ||
400 | question = strjoin("Please enter current passphrase for disk ", arg_node, ":"); | |
401 | if (!question) | |
402 | return log_oom(); | |
403 | ||
404 | disk_path = cescape(arg_node); | |
405 | if (!disk_path) | |
406 | return log_oom(); | |
407 | ||
408 | id = strjoina("cryptsetup:", disk_path); | |
409 | ||
410 | for (;;) { | |
411 | _cleanup_strv_free_erase_ char **passwords = NULL; | |
412 | char **p; | |
413 | ||
414 | if (--i == 0) | |
415 | return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), | |
416 | "Too many attempts, giving up:"); | |
417 | ||
418 | r = ask_password_auto( | |
419 | question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, | |
420 | ask_password_flags, | |
421 | &passwords); | |
422 | if (r < 0) | |
423 | return log_error_errno(r, "Failed to query password: %m"); | |
424 | ||
425 | r = -EPERM; | |
426 | STRV_FOREACH(p, passwords) { | |
427 | r = crypt_volume_key_get( | |
428 | cd, | |
429 | CRYPT_ANY_SLOT, | |
430 | vk, | |
431 | &vks, | |
432 | *p, | |
433 | strlen(*p)); | |
434 | if (r >= 0) | |
435 | break; | |
436 | } | |
437 | if (r >= 0) | |
438 | break; | |
439 | ||
440 | log_error_errno(r, "Password not correct, please try again."); | |
441 | ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; | |
442 | } | |
443 | } | |
444 | ||
445 | *ret_cd = TAKE_PTR(cd); | |
446 | *ret_volume_key = TAKE_PTR(vk); | |
447 | *ret_volume_key_size = vks; | |
448 | ||
449 | return 0; | |
450 | } | |
451 | ||
452 | static int run(int argc, char *argv[]) { | |
453 | _cleanup_(crypt_freep) struct crypt_device *cd = NULL; | |
454 | _cleanup_(erase_and_freep) void *vk = NULL; | |
455 | size_t vks; | |
d2fafc42 | 456 | int slot, r; |
8710a681 LP |
457 | |
458 | log_show_color(true); | |
459 | log_parse_environment(); | |
460 | log_open(); | |
461 | ||
462 | r = parse_argv(argc, argv); | |
463 | if (r <= 0) | |
464 | return r; | |
465 | ||
d2fafc42 LP |
466 | if (arg_enroll_type < 0) |
467 | r = prepare_luks(&cd, NULL, NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */ | |
468 | else | |
469 | r = prepare_luks(&cd, &vk, &vks); | |
8710a681 LP |
470 | if (r < 0) |
471 | return r; | |
472 | ||
473 | switch (arg_enroll_type) { | |
474 | ||
475 | case ENROLL_PASSWORD: | |
d2fafc42 | 476 | slot = enroll_password(cd, vk, vks); |
8710a681 LP |
477 | break; |
478 | ||
479 | case ENROLL_RECOVERY: | |
d2fafc42 | 480 | slot = enroll_recovery(cd, vk, vks); |
8710a681 LP |
481 | break; |
482 | ||
483 | case ENROLL_PKCS11: | |
d2fafc42 | 484 | slot = enroll_pkcs11(cd, vk, vks, arg_pkcs11_token_uri); |
8710a681 LP |
485 | break; |
486 | ||
487 | case ENROLL_FIDO2: | |
d2fafc42 | 488 | slot = enroll_fido2(cd, vk, vks, arg_fido2_device); |
8710a681 LP |
489 | break; |
490 | ||
5e521624 | 491 | case ENROLL_TPM2: |
d2fafc42 | 492 | slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask); |
5e521624 LP |
493 | break; |
494 | ||
d2fafc42 LP |
495 | case _ENROLL_TYPE_INVALID: |
496 | /* List enrolled slots if we are called without anything to enroll or wipe */ | |
497 | if (!wipe_requested()) | |
498 | return list_enrolled(cd); | |
499 | ||
500 | /* Only slot wiping selected */ | |
501 | return wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, -1); | |
502 | ||
8710a681 LP |
503 | default: |
504 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet."); | |
505 | } | |
d2fafc42 LP |
506 | if (slot < 0) |
507 | return slot; | |
8710a681 | 508 | |
d2fafc42 LP |
509 | /* After we completed enrolling, remove user selected slots */ |
510 | r = wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, slot); | |
511 | if (r < 0) | |
512 | return r; | |
513 | ||
514 | return 0; | |
8710a681 LP |
515 | } |
516 | ||
517 | DEFINE_MAIN_FUNCTION(run); |