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