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