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