]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/creds/creds.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / creds / creds.c
CommitLineData
5945640e
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
3c9fbb99 4#include <unistd.h>
5945640e 5
d6b4d1c7 6#include "build.h"
5945640e
LP
7#include "creds-util.h"
8#include "dirent-util.h"
9#include "escape.h"
10#include "fileio.h"
11#include "format-table.h"
12#include "hexdecoct.h"
13#include "io-util.h"
14#include "json.h"
15#include "main-func.h"
16#include "memory-util.h"
17#include "missing_magic.h"
18#include "pager.h"
19#include "parse-argument.h"
20#include "pretty-print.h"
21#include "process-util.h"
22#include "stat-util.h"
23#include "string-table.h"
24#include "terminal-util.h"
75ddec93 25#include "tpm-pcr.h"
5945640e
LP
26#include "tpm2-util.h"
27#include "verbs.h"
28
29typedef enum TranscodeMode {
30 TRANSCODE_OFF,
31 TRANSCODE_BASE64,
32 TRANSCODE_UNBASE64,
33 TRANSCODE_HEX,
34 TRANSCODE_UNHEX,
35 _TRANSCODE_MAX,
36 _TRANSCODE_INVALID = -EINVAL,
37} TranscodeMode;
38
39static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
40static PagerFlags arg_pager_flags = 0;
41static bool arg_legend = true;
42static bool arg_system = false;
43static TranscodeMode arg_transcode = TRANSCODE_OFF;
44static int arg_newline = -1;
571d829e 45static sd_id128_t arg_with_key = _CRED_AUTO;
5945640e
LP
46static const char *arg_tpm2_device = NULL;
47static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
75ddec93
LP
48static char *arg_tpm2_public_key = NULL;
49static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
50static char *arg_tpm2_signature = NULL;
5945640e
LP
51static const char *arg_name = NULL;
52static bool arg_name_any = false;
53static usec_t arg_timestamp = USEC_INFINITY;
54static usec_t arg_not_after = USEC_INFINITY;
55static bool arg_pretty = false;
6e0cb815 56static bool arg_quiet = false;
5945640e 57
75ddec93
LP
58STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
59STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
60
5945640e
LP
61static const char* transcode_mode_table[_TRANSCODE_MAX] = {
62 [TRANSCODE_OFF] = "off",
63 [TRANSCODE_BASE64] = "base64",
64 [TRANSCODE_UNBASE64] = "unbase64",
65 [TRANSCODE_HEX] = "hex",
66 [TRANSCODE_UNHEX] = "unhex",
67};
68
69DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode, TranscodeMode);
70
05eb896f 71static int open_credential_directory(
07a442ef 72 bool encrypted,
05eb896f 73 DIR **ret_dir,
07a442ef 74 const char **ret_prefix) {
05eb896f 75
5945640e
LP
76 const char *p;
77 DIR *d;
78 int r;
79
05eb896f
LP
80 assert(ret_dir);
81
82 if (arg_system)
83 /* PID 1 ensures that system credentials are always accessible under the same fixed path. It
84 * will create symlinks if necessary to guarantee that. */
85 p = encrypted ?
86 ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY :
87 SYSTEM_CREDENTIALS_DIRECTORY;
88 else {
89 /* Otherwise take the dirs from the env vars we got passed */
90 r = (encrypted ? get_encrypted_credentials_dir : get_credentials_dir)(&p);
91 if (r == -ENXIO) /* No environment variable? */
92 goto not_found;
5945640e 93 if (r < 0)
05eb896f
LP
94 return log_error_errno(r, "Failed to get credentials directory: %m");
95 }
5945640e 96
05eb896f
LP
97 d = opendir(p);
98 if (!d) {
99 /* No such dir? Then no creds where passed. (We conditionalize this on arg_system, since for
100 * the per-service case a non-existing path would indicate an issue since the env var would
101 * be set incorrectly in that case.) */
102 if (arg_system && errno == ENOENT)
103 goto not_found;
104
105 return log_error_errno(errno, "Failed to open credentials directory '%s': %m", p);
106 }
5945640e 107
05eb896f 108 *ret_dir = d;
5945640e 109
05eb896f
LP
110 if (ret_prefix)
111 *ret_prefix = p;
5945640e 112
05eb896f
LP
113 return 1;
114
115not_found:
116 *ret_dir = NULL;
117
118 if (ret_prefix)
119 *ret_prefix = NULL;
5945640e 120
5945640e
LP
121 return 0;
122}
123
05eb896f 124static int add_credentials_to_table(Table *t, bool encrypted) {
5945640e 125 _cleanup_(closedirp) DIR *d = NULL;
05eb896f 126 const char *prefix;
5945640e
LP
127 int r;
128
05eb896f 129 assert(t);
5945640e 130
07a442ef 131 r = open_credential_directory(encrypted, &d, &prefix);
05eb896f
LP
132 if (r < 0)
133 return r;
134 if (!d)
135 return 0; /* No creds dir set */
5945640e
LP
136
137 for (;;) {
05eb896f 138 _cleanup_free_ char *j = NULL;
5945640e 139 const char *secure, *secure_color = NULL;
254d1313 140 _cleanup_close_ int fd = -EBADF;
5945640e
LP
141 struct dirent *de;
142 struct stat st;
143
144 errno = 0;
145 de = readdir_no_dot(d);
146 if (!de) {
147 if (errno == 0)
148 break;
149
150 return log_error_errno(errno, "Failed to read credentials directory: %m");
151 }
152
153 if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
154 continue;
155
156 if (!credential_name_valid(de->d_name))
157 continue;
158
159 fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
160 if (fd < 0) {
161 if (errno == ENOENT) /* Vanished by now? */
162 continue;
163
164 return log_error_errno(errno, "Failed to open credential '%s': %m", de->d_name);
165 }
166
167 if (fstat(fd, &st) < 0)
168 return log_error_errno(errno, "Failed to stat credential '%s': %m", de->d_name);
169
170 if (!S_ISREG(st.st_mode))
171 continue;
172
05eb896f
LP
173 if (encrypted) {
174 secure = "encrypted";
175 secure_color = ansi_highlight_green();
176 } else if ((st.st_mode & 0377) != 0) {
5945640e
LP
177 secure = "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
178 secure_color = ansi_highlight_red();
179 } else {
180 r = fd_is_fs_type(fd, RAMFS_MAGIC);
181 if (r < 0)
182 return log_error_errno(r, "Failed to determine backing file system of '%s': %m", de->d_name);
183
184 secure = r ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
185 secure_color = r ? ansi_highlight_green() : ansi_highlight_yellow4();
186 }
187
05eb896f
LP
188 j = path_join(prefix, de->d_name);
189 if (!j)
190 return log_oom();
191
5945640e
LP
192 r = table_add_many(
193 t,
194 TABLE_STRING, de->d_name,
195 TABLE_STRING, secure,
196 TABLE_SET_COLOR, secure_color,
05eb896f
LP
197 TABLE_SIZE, (uint64_t) st.st_size,
198 TABLE_STRING, j);
5945640e
LP
199 if (r < 0)
200 return table_log_add_error(r);
201 }
202
05eb896f
LP
203 return 1; /* Creds dir set */
204}
205
206static int verb_list(int argc, char **argv, void *userdata) {
207 _cleanup_(table_unrefp) Table *t = NULL;
208 int r, q;
209
210 t = table_new("name", "secure", "size", "path");
211 if (!t)
212 return log_oom();
213
214 (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
215
216 r = add_credentials_to_table(t, /* encrypted= */ true);
217 if (r < 0)
218 return r;
219
220 q = add_credentials_to_table(t, /* encrypted= */ false);
221 if (q < 0)
222 return q;
223
224 if (r == 0 && q == 0) {
225 if (arg_system)
226 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed to system.");
227
228 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
229 }
230
5945640e
LP
231 if ((arg_json_format_flags & JSON_FORMAT_OFF) && table_get_rows(t) <= 1) {
232 log_info("No credentials");
233 return 0;
234 }
235
236 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
237}
238
239static int transcode(
240 const void *input,
241 size_t input_size,
242 void **ret_output,
243 size_t *ret_output_size) {
244
245 int r;
246
247 assert(input);
248 assert(input_size);
249 assert(ret_output);
250 assert(ret_output_size);
251
252 switch (arg_transcode) {
253
254 case TRANSCODE_BASE64: {
bc1f27ff 255 char *buf;
5945640e
LP
256 ssize_t l;
257
258 l = base64mem_full(input, input_size, 79, &buf);
259 if (l < 0)
260 return l;
261
262 *ret_output = buf;
263 *ret_output_size = l;
264 return 0;
265 }
266
267 case TRANSCODE_UNBASE64:
268 r = unbase64mem_full(input, input_size, true, ret_output, ret_output_size);
269 if (r == -EPIPE) /* Uneven number of chars */
270 return -EINVAL;
271
272 return r;
273
274 case TRANSCODE_HEX: {
275 char *buf;
276
277 buf = hexmem(input, input_size);
278 if (!buf)
279 return -ENOMEM;
280
281 *ret_output = buf;
282 *ret_output_size = input_size * 2;
283 return 0;
284 }
285
286 case TRANSCODE_UNHEX:
287 r = unhexmem_full(input, input_size, true, ret_output, ret_output_size);
288 if (r == -EPIPE) /* Uneven number of chars */
289 return -EINVAL;
290
291 return r;
292
293 default:
04499a70 294 assert_not_reached();
5945640e
LP
295 }
296}
297
298static int print_newline(FILE *f, const char *data, size_t l) {
299 int fd;
300
301 assert(f);
302 assert(data || l == 0);
303
304 /* If turned off explicitly, don't print newline */
305 if (arg_newline == 0)
306 return 0;
307
308 /* If data already has newline, don't print either */
309 if (l > 0 && data[l-1] == '\n')
310 return 0;
311
312 /* Don't bother unless this is a tty */
313 fd = fileno(f);
314 if (fd >= 0 && isatty(fd) <= 0)
315 return 0;
316
317 if (fputc('\n', f) != '\n')
318 return log_error_errno(errno, "Failed to write trailing newline: %m");
319
320 return 1;
321}
322
323static int write_blob(FILE *f, const void *data, size_t size) {
324 _cleanup_(erase_and_freep) void *transcoded = NULL;
325 int r;
326
327 if (arg_transcode == TRANSCODE_OFF &&
328 arg_json_format_flags != JSON_FORMAT_OFF) {
329
330 _cleanup_(erase_and_freep) char *suffixed = NULL;
331 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
332
333 if (memchr(data, 0, size))
334 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential data contains embedded NUL, can't parse as JSON.");
335
336 suffixed = memdup_suffix0(data, size);
337 if (!suffixed)
338 return log_oom();
339
340 r = json_parse(suffixed, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
341 if (r < 0)
342 return log_error_errno(r, "Failed to parse JSON: %m");
343
344 json_variant_dump(v, arg_json_format_flags, f, NULL);
345 return 0;
346 }
347
348 if (arg_transcode != TRANSCODE_OFF) {
349 r = transcode(data, size, &transcoded, &size);
350 if (r < 0)
351 return log_error_errno(r, "Failed to transcode data: %m");
352
353 data = transcoded;
354 }
355
356 if (fwrite(data, 1, size, f) != size)
357 return log_error_errno(errno, "Failed to write credential data: %m");
358
359 r = print_newline(f, data, size);
360 if (r < 0)
361 return r;
362
363 if (fflush(f) != 0)
364 return log_error_errno(errno, "Failed to flush output: %m");
365
366 return 0;
367}
368
369static int verb_cat(int argc, char **argv, void *userdata) {
05eb896f 370 usec_t timestamp;
5945640e 371 int r, ret = 0;
5945640e 372
05eb896f 373 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
5945640e
LP
374
375 STRV_FOREACH(cn, strv_skip(argv, 1)) {
376 _cleanup_(erase_and_freep) void *data = NULL;
377 size_t size = 0;
05eb896f 378 int encrypted;
5945640e
LP
379
380 if (!credential_name_valid(*cn)) {
381 log_error("Credential name '%s' is not valid.", *cn);
382 if (ret >= 0)
383 ret = -EINVAL;
384 continue;
385 }
386
05eb896f 387 /* Look both in regular and in encrypted credentials */
07a442ef 388 for (encrypted = 0; encrypted < 2; encrypted++) {
05eb896f
LP
389 _cleanup_(closedirp) DIR *d = NULL;
390
07a442ef 391 r = open_credential_directory(encrypted, &d, NULL);
05eb896f
LP
392 if (r < 0)
393 return log_error_errno(r, "Failed to open credentials directory: %m");
394 if (!d) /* Not set */
395 continue;
396
397 r = read_full_file_full(
398 dirfd(d), *cn,
399 UINT64_MAX, SIZE_MAX,
400 READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
401 NULL,
402 (char**) &data, &size);
403 if (r == -ENOENT) /* Not found */
404 continue;
405 if (r >= 0) /* Found */
406 break;
407
5945640e
LP
408 log_error_errno(r, "Failed to read credential '%s': %m", *cn);
409 if (ret >= 0)
410 ret = r;
05eb896f
LP
411 }
412
413 if (encrypted >= 2) { /* Found nowhere */
414 log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn);
415 if (ret >= 0)
416 ret = -ENOENT;
417
5945640e
LP
418 continue;
419 }
420
05eb896f
LP
421 if (encrypted) {
422 _cleanup_(erase_and_freep) void *plaintext = NULL;
423 size_t plaintext_size;
424
425 r = decrypt_credential_and_warn(
426 *cn,
427 timestamp,
428 arg_tpm2_device,
75ddec93 429 arg_tpm2_signature,
05eb896f
LP
430 data, size,
431 &plaintext, &plaintext_size);
432 if (r < 0)
433 return r;
434
435 erase_and_free(data);
436 data = TAKE_PTR(plaintext);
437 size = plaintext_size;
438 }
439
5945640e
LP
440 r = write_blob(stdout, data, size);
441 if (r < 0)
442 return r;
443 }
444
445 return ret;
446}
447
448static int verb_encrypt(int argc, char **argv, void *userdata) {
449 _cleanup_free_ char *base64_buf = NULL, *fname = NULL;
450 _cleanup_(erase_and_freep) char *plaintext = NULL;
451 const char *input_path, *output_path, *name;
452 _cleanup_free_ void *output = NULL;
453 size_t plaintext_size, output_size;
454 ssize_t base64_size;
455 usec_t timestamp;
456 int r;
457
458 assert(argc == 3);
459
2f092762 460 input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
5945640e
LP
461
462 if (input_path)
463 r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &plaintext, &plaintext_size);
464 else
465 r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, &plaintext, &plaintext_size);
466 if (r == -E2BIG)
467 return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
468 if (r < 0)
469 return log_error_errno(r, "Failed to read plaintext: %m");
470
2f092762 471 output_path = empty_or_dash(argv[2]) ? NULL : argv[2];
5945640e
LP
472
473 if (arg_name_any)
474 name = NULL;
475 else if (arg_name)
476 name = arg_name;
477 else if (output_path) {
478 r = path_extract_filename(output_path, &fname);
479 if (r < 0)
480 return log_error_errno(r, "Failed to extract filename from '%s': %m", output_path);
481 if (r == O_DIRECTORY)
482 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", output_path);
483
484 name = fname;
485 } else {
486 log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
487 name = NULL;
488 }
489
490 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
491
492 if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp)
493 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid.");
494
495 r = encrypt_credential_and_warn(
496 arg_with_key,
497 name,
498 timestamp,
499 arg_not_after,
500 arg_tpm2_device,
501 arg_tpm2_pcr_mask,
75ddec93
LP
502 arg_tpm2_public_key,
503 arg_tpm2_public_key_pcr_mask,
5945640e
LP
504 plaintext, plaintext_size,
505 &output, &output_size);
506 if (r < 0)
507 return r;
508
509 base64_size = base64mem_full(output, output_size, arg_pretty ? 69 : 79, &base64_buf);
510 if (base64_size < 0)
511 return base64_size;
512
513 if (arg_pretty) {
514 _cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
515
516 if (name) {
517 escaped = cescape(name);
518 if (!escaped)
519 return log_oom();
520 }
521
522 indented = strreplace(base64_buf, "\n", " \\\n ");
523 if (!indented)
524 return log_oom();
525
526 j = strjoin("SetCredentialEncrypted=", name, ": \\\n ", indented, "\n");
527 if (!j)
528 return log_oom();
529
530 free_and_replace(base64_buf, j);
531 }
532
533 if (output_path)
534 r = write_string_file(output_path, base64_buf, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE);
535 else
536 r = write_string_stream(stdout, base64_buf, 0);
537 if (r < 0)
538 return log_error_errno(r, "Failed to write result: %m");
539
540 return EXIT_SUCCESS;
541}
542
543static int verb_decrypt(int argc, char **argv, void *userdata) {
544 _cleanup_(erase_and_freep) void *plaintext = NULL;
545 _cleanup_free_ char *input = NULL, *fname = NULL;
546 _cleanup_fclose_ FILE *output_file = NULL;
547 const char *input_path, *output_path, *name;
548 size_t input_size, plaintext_size;
549 usec_t timestamp;
550 FILE *f;
551 int r;
552
553 assert(IN_SET(argc, 2, 3));
554
2f092762 555 input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
5945640e
LP
556
557 if (input_path)
558 r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &input, &input_size);
559 else
560 r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, &input, &input_size);
561 if (r == -E2BIG)
562 return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
563 if (r < 0)
564 return log_error_errno(r, "Failed to read encrypted credential data: %m");
565
566 output_path = (argc < 3 || isempty(argv[2]) || streq(argv[2], "-")) ? NULL : argv[2];
567
568 if (arg_name_any)
569 name = NULL;
570 else if (arg_name)
571 name = arg_name;
572 else if (input_path) {
573 r = path_extract_filename(input_path, &fname);
574 if (r < 0)
575 return log_error_errno(r, "Failed to extract filename from '%s': %m", input_path);
576 if (r == O_DIRECTORY)
577 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", input_path);
578
579 name = fname;
580 } else {
581 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
582 name = NULL;
583 }
584
585 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
586
587 r = decrypt_credential_and_warn(
588 name,
589 timestamp,
590 arg_tpm2_device,
75ddec93 591 arg_tpm2_signature,
5945640e
LP
592 input, input_size,
593 &plaintext, &plaintext_size);
594 if (r < 0)
595 return r;
596
597 if (output_path) {
598 output_file = fopen(output_path, "we");
599 if (!output_file)
600 return log_error_errno(errno, "Failed to create output file '%s': %m", output_path);
601
602 f = output_file;
603 } else
604 f = stdout;
605
606 r = write_blob(f, plaintext, plaintext_size);
607 if (r < 0)
608 return r;
609
610 return EXIT_SUCCESS;
611}
612
613static int verb_setup(int argc, char **argv, void *userdata) {
614 size_t size;
615 int r;
616
617 r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, NULL, &size);
618 if (r < 0)
619 return log_error_errno(r, "Failed to setup credentials host key: %m");
620
621 log_info("%zu byte credentials host key set up.", size);
622
623 return EXIT_SUCCESS;
624}
625
6e0cb815
LP
626static int verb_has_tpm2(int argc, char **argv, void *userdata) {
627 Tpm2Support s;
628
629 s = tpm2_support();
630
631 if (!arg_quiet) {
632 if (s == TPM2_SUPPORT_FULL)
633 puts("yes");
634 else if (s == TPM2_SUPPORT_NONE)
635 puts("no");
636 else
637 puts("partial");
638
639 printf("%sfirmware\n"
640 "%sdriver\n"
300bba79
DDM
641 "%ssystem\n"
642 "%ssubsystem\n",
6e0cb815
LP
643 plus_minus(s & TPM2_SUPPORT_FIRMWARE),
644 plus_minus(s & TPM2_SUPPORT_DRIVER),
300bba79
DDM
645 plus_minus(s & TPM2_SUPPORT_SYSTEM),
646 plus_minus(s & TPM2_SUPPORT_SUBSYSTEM));
6e0cb815
LP
647 }
648
649 /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values
650 * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than
8ac6b05b 651 * what is there, acknowledging the fact that for process exit statuses it is customary to return
6e0cb815
LP
652 * zero (EXIT_FAILURE) when all is good, instead of all being bad. */
653 return ~s & TPM2_SUPPORT_FULL;
654}
655
5945640e
LP
656static int verb_help(int argc, char **argv, void *userdata) {
657 _cleanup_free_ char *link = NULL;
658 int r;
659
660 r = terminal_urlify_man("systemd-creds", "1", &link);
661 if (r < 0)
662 return log_oom();
663
664 printf("%1$s [OPTIONS...] COMMAND ...\n"
665 "\n%5$sDisplay and Process Credentials.%6$s\n"
666 "\n%3$sCommands:%4$s\n"
667 " list Show installed and available versions\n"
668 " cat CREDENTIAL... Show specified credentials\n"
669 " setup Generate credentials host key, if not existing yet\n"
670 " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
671 " ciphertext credential file\n"
672 " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
673 " plaintext credential file\n"
6e0cb815 674 " has-tpm2 Report whether TPM2 support is available\n"
5945640e
LP
675 " -h --help Show this help\n"
676 " --version Show package version\n"
677 "\n%3$sOptions:%4$s\n"
678 " --no-pager Do not pipe output into a pager\n"
679 " --no-legend Do not show the headers and footers\n"
680 " --json=pretty|short|off\n"
681 " Generate JSON output\n"
682 " --system Show credentials passed to system\n"
683 " --transcode=base64|unbase64|hex|unhex\n"
684 " Transcode credential data\n"
685 " --newline=auto|yes|no\n"
686 " Suffix output with newline\n"
687 " -p --pretty Output as SetCredentialEncrypted= line\n"
688 " --name=NAME Override filename included in encrypted credential\n"
689 " --timestamp=TIME Include specified timestamp in encrypted credential\n"
690 " --not-after=TIME Include specified invalidation time in encrypted\n"
691 " credential\n"
b6553329 692 " --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n"
5945640e
LP
693 " Which keys to encrypt with\n"
694 " -H Shortcut for --with-key=host\n"
695 " -T Shortcut for --with-key=tpm2\n"
696 " --tpm2-device=PATH\n"
697 " Pick TPM2 device\n"
698 " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
75ddec93
LP
699 " Specify TPM2 PCRs to seal against (fixed hash)\n"
700 " --tpm2-public-key=PATH\n"
701 " Specify PEM certificate to seal against\n"
702 " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
703 " Specify TPM2 PCRs to seal against (public key)\n"
704 " --tpm2-signature=PATH\n"
705 " Specify signature for public key PCR policy\n"
6e0cb815 706 " -q --quiet Suppress output for 'has-tpm2' verb\n"
5945640e
LP
707 "\nSee the %2$s for details.\n"
708 , program_invocation_short_name
709 , link
710 , ansi_underline(), ansi_normal()
711 , ansi_highlight(), ansi_normal()
712 );
713
714 return 0;
715}
716
717static int parse_argv(int argc, char *argv[]) {
718
719 enum {
720 ARG_VERSION = 0x100,
721 ARG_NO_PAGER,
722 ARG_NO_LEGEND,
723 ARG_JSON,
724 ARG_SYSTEM,
725 ARG_TRANSCODE,
726 ARG_NEWLINE,
727 ARG_WITH_KEY,
728 ARG_TPM2_DEVICE,
729 ARG_TPM2_PCRS,
75ddec93
LP
730 ARG_TPM2_PUBLIC_KEY,
731 ARG_TPM2_PUBLIC_KEY_PCRS,
732 ARG_TPM2_SIGNATURE,
5945640e
LP
733 ARG_NAME,
734 ARG_TIMESTAMP,
735 ARG_NOT_AFTER,
736 };
737
738 static const struct option options[] = {
75ddec93
LP
739 { "help", no_argument, NULL, 'h' },
740 { "version", no_argument, NULL, ARG_VERSION },
741 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
742 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
743 { "json", required_argument, NULL, ARG_JSON },
744 { "system", no_argument, NULL, ARG_SYSTEM },
745 { "transcode", required_argument, NULL, ARG_TRANSCODE },
746 { "newline", required_argument, NULL, ARG_NEWLINE },
747 { "pretty", no_argument, NULL, 'p' },
748 { "with-key", required_argument, NULL, ARG_WITH_KEY },
749 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
750 { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
751 { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
752 { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
753 { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
754 { "name", required_argument, NULL, ARG_NAME },
755 { "timestamp", required_argument, NULL, ARG_TIMESTAMP },
756 { "not-after", required_argument, NULL, ARG_NOT_AFTER },
757 { "quiet", no_argument, NULL, 'q' },
5945640e
LP
758 {}
759 };
760
761 int c, r;
762
763 assert(argc >= 0);
764 assert(argv);
765
6e0cb815 766 while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) {
5945640e
LP
767
768 switch (c) {
769
770 case 'h':
771 return verb_help(0, NULL, NULL);
772
773 case ARG_VERSION:
774 return version();
775
776 case ARG_NO_PAGER:
777 arg_pager_flags |= PAGER_DISABLE;
778 break;
779
780 case ARG_NO_LEGEND:
781 arg_legend = false;
782 break;
783
784 case ARG_JSON:
785 r = parse_json_argument(optarg, &arg_json_format_flags);
786 if (r <= 0)
787 return r;
788
789 break;
790
791 case ARG_SYSTEM:
792 arg_system = true;
793 break;
794
795 case ARG_TRANSCODE:
796 if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */
797 arg_transcode = TRANSCODE_OFF;
798 else {
799 TranscodeMode m;
800
801 m = transcode_mode_from_string(optarg);
802 if (m < 0)
803 return log_error_errno(m, "Failed to parse transcode mode: %m");
804
805 arg_transcode = m;
806 }
807
808 break;
809
810 case ARG_NEWLINE:
811 if (isempty(optarg) || streq(optarg, "auto"))
812 arg_newline = -1;
813 else {
814 bool b;
815
816 r = parse_boolean_argument("--newline=", optarg, &b);
817 if (r < 0)
818 return r;
819
820 arg_newline = b;
821 }
822 break;
823
824 case 'p':
825 arg_pretty = true;
826 break;
827
828 case ARG_WITH_KEY:
829 if (isempty(optarg) || streq(optarg, "auto"))
571d829e 830 arg_with_key = _CRED_AUTO;
b6553329
LP
831 else if (streq(optarg, "auto-initrd"))
832 arg_with_key = _CRED_AUTO_INITRD;
5945640e
LP
833 else if (streq(optarg, "host"))
834 arg_with_key = CRED_AES256_GCM_BY_HOST;
835 else if (streq(optarg, "tpm2"))
836 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
75ddec93
LP
837 else if (streq(optarg, "tpm2-with-public-key"))
838 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK;
5945640e
LP
839 else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
840 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
75ddec93
LP
841 else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
842 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
b6553329
LP
843 else if (streq(optarg, "tpm2-absent"))
844 arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
5945640e
LP
845 else
846 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
847
848 break;
849
850 case 'H':
851 arg_with_key = CRED_AES256_GCM_BY_HOST;
852 break;
853
854 case 'T':
855 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
856 break;
857
d1829af9 858 case ARG_TPM2_DEVICE:
5945640e
LP
859 if (streq(optarg, "list"))
860 return tpm2_list_devices();
861
d1829af9 862 arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
5945640e 863 break;
5945640e 864
75ddec93 865 case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */
222a951f 866 r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask);
5945640e
LP
867 if (r < 0)
868 return r;
869
5945640e 870 break;
5945640e 871
75ddec93
LP
872 case ARG_TPM2_PUBLIC_KEY:
873 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
874 if (r < 0)
875 return r;
876
877 break;
878
879 case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */
880 r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
881 if (r < 0)
882 return r;
883
884 break;
885
886 case ARG_TPM2_SIGNATURE:
887 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
888 if (r < 0)
889 return r;
890
891 break;
892
5945640e
LP
893 case ARG_NAME:
894 if (isempty(optarg)) {
895 arg_name = NULL;
896 arg_name_any = true;
897 break;
898 }
899
900 if (!credential_name_valid(optarg))
901 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg);
902
903 arg_name = optarg;
904 arg_name_any = false;
905 break;
906
907 case ARG_TIMESTAMP:
908 r = parse_timestamp(optarg, &arg_timestamp);
909 if (r < 0)
910 return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
911
912 break;
913
914 case ARG_NOT_AFTER:
915 r = parse_timestamp(optarg, &arg_not_after);
916 if (r < 0)
917 return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg);
918
919 break;
920
6e0cb815
LP
921 case 'q':
922 arg_quiet = true;
923 break;
924
5945640e
LP
925 case '?':
926 return -EINVAL;
927
928 default:
04499a70 929 assert_not_reached();
5945640e
LP
930 }
931 }
932
933 if (arg_tpm2_pcr_mask == UINT32_MAX)
934 arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
75ddec93
LP
935 if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
936 arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
5945640e
LP
937
938 return 1;
939}
940
941static int creds_main(int argc, char *argv[]) {
942
943 static const Verb verbs[] = {
6e0cb815
LP
944 { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list },
945 { "cat", 2, VERB_ANY, 0, verb_cat },
946 { "encrypt", 3, 3, 0, verb_encrypt },
947 { "decrypt", 2, 3, 0, verb_decrypt },
948 { "setup", VERB_ANY, 1, 0, verb_setup },
949 { "help", VERB_ANY, 1, 0, verb_help },
950 { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 },
5945640e
LP
951 {}
952 };
953
954 return dispatch_verb(argc, argv, verbs, NULL);
955}
956
957static int run(int argc, char *argv[]) {
958 int r;
959
960 log_setup();
961
962 r = parse_argv(argc, argv);
963 if (r <= 0)
964 return r;
965
966 return creds_main(argc, argv);
967}
968
969DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);