]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/creds/creds.c
creds: update --help text regarding tpm2-absent → null rename
[thirdparty/systemd.git] / src / creds / creds.c
CommitLineData
5945640e
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
3c9fbb99 4#include <unistd.h>
5945640e 5
d6b4d1c7 6#include "build.h"
caef0bc3 7#include "bus-polkit.h"
5945640e
LP
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"
e0e1f4f7 26#include "tpm2-pcr.h"
5945640e 27#include "tpm2-util.h"
644f19c7
LP
28#include "user-util.h"
29#include "varlink.h"
30#include "varlink-io.systemd.Credentials.h"
5945640e
LP
31#include "verbs.h"
32
33typedef 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
43static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
44static PagerFlags arg_pager_flags = 0;
45static bool arg_legend = true;
46static bool arg_system = false;
47static TranscodeMode arg_transcode = TRANSCODE_OFF;
48static int arg_newline = -1;
571d829e 49static sd_id128_t arg_with_key = _CRED_AUTO;
5945640e
LP
50static const char *arg_tpm2_device = NULL;
51static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
75ddec93
LP
52static char *arg_tpm2_public_key = NULL;
53static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
54static char *arg_tpm2_signature = NULL;
5945640e
LP
55static const char *arg_name = NULL;
56static bool arg_name_any = false;
57static usec_t arg_timestamp = USEC_INFINITY;
58static usec_t arg_not_after = USEC_INFINITY;
59static bool arg_pretty = false;
6e0cb815 60static bool arg_quiet = false;
644f19c7 61static bool arg_varlink = false;
c85b68f6 62static uid_t arg_uid = UID_INVALID;
aadbe559 63static bool arg_allow_null = false;
5945640e 64
75ddec93
LP
65STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
66STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
67
5945640e
LP
68static const char* transcode_mode_table[_TRANSCODE_MAX] = {
69 [TRANSCODE_OFF] = "off",
70 [TRANSCODE_BASE64] = "base64",
71 [TRANSCODE_UNBASE64] = "unbase64",
72 [TRANSCODE_HEX] = "hex",
73 [TRANSCODE_UNHEX] = "unhex",
74};
75
76DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode, TranscodeMode);
77
05eb896f 78static int open_credential_directory(
07a442ef 79 bool encrypted,
05eb896f 80 DIR **ret_dir,
07a442ef 81 const char **ret_prefix) {
05eb896f 82
5945640e
LP
83 const char *p;
84 DIR *d;
85 int r;
86
05eb896f
LP
87 assert(ret_dir);
88
89 if (arg_system)
90 /* PID 1 ensures that system credentials are always accessible under the same fixed path. It
91 * will create symlinks if necessary to guarantee that. */
92 p = encrypted ?
93 ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY :
94 SYSTEM_CREDENTIALS_DIRECTORY;
95 else {
96 /* Otherwise take the dirs from the env vars we got passed */
97 r = (encrypted ? get_encrypted_credentials_dir : get_credentials_dir)(&p);
98 if (r == -ENXIO) /* No environment variable? */
99 goto not_found;
5945640e 100 if (r < 0)
05eb896f
LP
101 return log_error_errno(r, "Failed to get credentials directory: %m");
102 }
5945640e 103
05eb896f
LP
104 d = opendir(p);
105 if (!d) {
106 /* No such dir? Then no creds where passed. (We conditionalize this on arg_system, since for
107 * the per-service case a non-existing path would indicate an issue since the env var would
108 * be set incorrectly in that case.) */
109 if (arg_system && errno == ENOENT)
110 goto not_found;
111
112 return log_error_errno(errno, "Failed to open credentials directory '%s': %m", p);
113 }
5945640e 114
05eb896f 115 *ret_dir = d;
5945640e 116
05eb896f
LP
117 if (ret_prefix)
118 *ret_prefix = p;
5945640e 119
05eb896f
LP
120 return 1;
121
122not_found:
123 *ret_dir = NULL;
124
125 if (ret_prefix)
126 *ret_prefix = NULL;
5945640e 127
5945640e
LP
128 return 0;
129}
130
05eb896f 131static int add_credentials_to_table(Table *t, bool encrypted) {
5d2a48da 132 _cleanup_closedir_ DIR *d = NULL;
05eb896f 133 const char *prefix;
5945640e
LP
134 int r;
135
05eb896f 136 assert(t);
5945640e 137
07a442ef 138 r = open_credential_directory(encrypted, &d, &prefix);
05eb896f
LP
139 if (r < 0)
140 return r;
141 if (!d)
142 return 0; /* No creds dir set */
5945640e
LP
143
144 for (;;) {
05eb896f 145 _cleanup_free_ char *j = NULL;
5945640e 146 const char *secure, *secure_color = NULL;
254d1313 147 _cleanup_close_ int fd = -EBADF;
5945640e
LP
148 struct dirent *de;
149 struct stat st;
150
151 errno = 0;
152 de = readdir_no_dot(d);
153 if (!de) {
154 if (errno == 0)
155 break;
156
157 return log_error_errno(errno, "Failed to read credentials directory: %m");
158 }
159
160 if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
161 continue;
162
163 if (!credential_name_valid(de->d_name))
164 continue;
165
166 fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
167 if (fd < 0) {
168 if (errno == ENOENT) /* Vanished by now? */
169 continue;
170
171 return log_error_errno(errno, "Failed to open credential '%s': %m", de->d_name);
172 }
173
174 if (fstat(fd, &st) < 0)
175 return log_error_errno(errno, "Failed to stat credential '%s': %m", de->d_name);
176
177 if (!S_ISREG(st.st_mode))
178 continue;
179
05eb896f
LP
180 if (encrypted) {
181 secure = "encrypted";
182 secure_color = ansi_highlight_green();
183 } else if ((st.st_mode & 0377) != 0) {
5945640e
LP
184 secure = "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
185 secure_color = ansi_highlight_red();
186 } else {
187 r = fd_is_fs_type(fd, RAMFS_MAGIC);
188 if (r < 0)
189 return log_error_errno(r, "Failed to determine backing file system of '%s': %m", de->d_name);
190
79de6eb1
MY
191 secure = r > 0 ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
192 secure_color = r > 0 ? ansi_highlight_green() : ansi_highlight_yellow4();
5945640e
LP
193 }
194
05eb896f
LP
195 j = path_join(prefix, de->d_name);
196 if (!j)
197 return log_oom();
198
5945640e
LP
199 r = table_add_many(
200 t,
201 TABLE_STRING, de->d_name,
202 TABLE_STRING, secure,
203 TABLE_SET_COLOR, secure_color,
05eb896f
LP
204 TABLE_SIZE, (uint64_t) st.st_size,
205 TABLE_STRING, j);
5945640e
LP
206 if (r < 0)
207 return table_log_add_error(r);
208 }
209
05eb896f
LP
210 return 1; /* Creds dir set */
211}
212
213static int verb_list(int argc, char **argv, void *userdata) {
214 _cleanup_(table_unrefp) Table *t = NULL;
215 int r, q;
216
217 t = table_new("name", "secure", "size", "path");
218 if (!t)
219 return log_oom();
220
221 (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
222
223 r = add_credentials_to_table(t, /* encrypted= */ true);
224 if (r < 0)
225 return r;
226
227 q = add_credentials_to_table(t, /* encrypted= */ false);
228 if (q < 0)
229 return q;
230
231 if (r == 0 && q == 0) {
232 if (arg_system)
233 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed to system.");
234
235 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
236 }
237
2413a0fa 238 if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_isempty(t)) {
5945640e
LP
239 log_info("No credentials");
240 return 0;
241 }
242
243 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
244}
245
246static int transcode(
247 const void *input,
248 size_t input_size,
249 void **ret_output,
250 size_t *ret_output_size) {
251
252 int r;
253
254 assert(input);
255 assert(input_size);
256 assert(ret_output);
257 assert(ret_output_size);
258
259 switch (arg_transcode) {
260
261 case TRANSCODE_BASE64: {
bc1f27ff 262 char *buf;
5945640e
LP
263 ssize_t l;
264
265 l = base64mem_full(input, input_size, 79, &buf);
266 if (l < 0)
267 return l;
268
269 *ret_output = buf;
270 *ret_output_size = l;
271 return 0;
272 }
273
274 case TRANSCODE_UNBASE64:
275 r = unbase64mem_full(input, input_size, true, ret_output, ret_output_size);
276 if (r == -EPIPE) /* Uneven number of chars */
277 return -EINVAL;
278
279 return r;
280
281 case TRANSCODE_HEX: {
282 char *buf;
283
284 buf = hexmem(input, input_size);
285 if (!buf)
286 return -ENOMEM;
287
288 *ret_output = buf;
289 *ret_output_size = input_size * 2;
290 return 0;
291 }
292
293 case TRANSCODE_UNHEX:
294 r = unhexmem_full(input, input_size, true, ret_output, ret_output_size);
295 if (r == -EPIPE) /* Uneven number of chars */
296 return -EINVAL;
297
298 return r;
299
300 default:
04499a70 301 assert_not_reached();
5945640e
LP
302 }
303}
304
305static int print_newline(FILE *f, const char *data, size_t l) {
306 int fd;
307
308 assert(f);
309 assert(data || l == 0);
310
311 /* If turned off explicitly, don't print newline */
312 if (arg_newline == 0)
313 return 0;
314
315 /* If data already has newline, don't print either */
316 if (l > 0 && data[l-1] == '\n')
317 return 0;
318
319 /* Don't bother unless this is a tty */
320 fd = fileno(f);
dd9c8da8 321 if (fd >= 0 && !isatty_safe(fd))
5945640e
LP
322 return 0;
323
324 if (fputc('\n', f) != '\n')
325 return log_error_errno(errno, "Failed to write trailing newline: %m");
326
327 return 1;
328}
329
330static int write_blob(FILE *f, const void *data, size_t size) {
331 _cleanup_(erase_and_freep) void *transcoded = NULL;
332 int r;
333
334 if (arg_transcode == TRANSCODE_OFF &&
335 arg_json_format_flags != JSON_FORMAT_OFF) {
5945640e
LP
336 _cleanup_(erase_and_freep) char *suffixed = NULL;
337 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
338
7153213e
LP
339 r = make_cstring(data, size, MAKE_CSTRING_REFUSE_TRAILING_NUL, &suffixed);
340 if (r < 0)
341 return log_error_errno(r, "Unable to convert binary string to C string: %m");
5945640e
LP
342
343 r = json_parse(suffixed, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
344 if (r < 0)
345 return log_error_errno(r, "Failed to parse JSON: %m");
346
347 json_variant_dump(v, arg_json_format_flags, f, NULL);
348 return 0;
349 }
350
351 if (arg_transcode != TRANSCODE_OFF) {
352 r = transcode(data, size, &transcoded, &size);
353 if (r < 0)
354 return log_error_errno(r, "Failed to transcode data: %m");
355
356 data = transcoded;
357 }
358
359 if (fwrite(data, 1, size, f) != size)
513412a6 360 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write credential data.");
5945640e
LP
361
362 r = print_newline(f, data, size);
363 if (r < 0)
364 return r;
365
f9568765
ZJS
366 r = fflush_and_check(f);
367 if (r < 0)
368 return log_error_errno(r, "Failed to flush output: %m");
5945640e
LP
369
370 return 0;
371}
372
373static int verb_cat(int argc, char **argv, void *userdata) {
05eb896f 374 usec_t timestamp;
5945640e 375 int r, ret = 0;
5945640e 376
05eb896f 377 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
5945640e
LP
378
379 STRV_FOREACH(cn, strv_skip(argv, 1)) {
380 _cleanup_(erase_and_freep) void *data = NULL;
381 size_t size = 0;
05eb896f 382 int encrypted;
5945640e
LP
383
384 if (!credential_name_valid(*cn)) {
385 log_error("Credential name '%s' is not valid.", *cn);
386 if (ret >= 0)
387 ret = -EINVAL;
388 continue;
389 }
390
05eb896f 391 /* Look both in regular and in encrypted credentials */
07a442ef 392 for (encrypted = 0; encrypted < 2; encrypted++) {
5d2a48da 393 _cleanup_closedir_ DIR *d = NULL;
05eb896f 394
07a442ef 395 r = open_credential_directory(encrypted, &d, NULL);
05eb896f
LP
396 if (r < 0)
397 return log_error_errno(r, "Failed to open credentials directory: %m");
398 if (!d) /* Not set */
399 continue;
400
401 r = read_full_file_full(
402 dirfd(d), *cn,
403 UINT64_MAX, SIZE_MAX,
404 READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
405 NULL,
406 (char**) &data, &size);
407 if (r == -ENOENT) /* Not found */
408 continue;
409 if (r >= 0) /* Found */
410 break;
411
5945640e
LP
412 log_error_errno(r, "Failed to read credential '%s': %m", *cn);
413 if (ret >= 0)
414 ret = r;
05eb896f
LP
415 }
416
417 if (encrypted >= 2) { /* Found nowhere */
418 log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn);
419 if (ret >= 0)
420 ret = -ENOENT;
421
5945640e
LP
422 continue;
423 }
424
05eb896f 425 if (encrypted) {
8d042bc4 426 _cleanup_(iovec_done_erase) struct iovec plaintext = {};
05eb896f 427
19f16c99
LP
428 if (geteuid() != 0)
429 r = ipc_decrypt_credential(
430 *cn,
431 timestamp,
432 uid_is_valid(arg_uid) ? arg_uid : getuid(),
433 &IOVEC_MAKE(data, size),
434 CREDENTIAL_ANY_SCOPE,
435 &plaintext);
436 else
437 r = decrypt_credential_and_warn(
438 *cn,
439 timestamp,
440 arg_tpm2_device,
441 arg_tpm2_signature,
442 uid_is_valid(arg_uid) ? arg_uid : getuid(),
443 &IOVEC_MAKE(data, size),
444 CREDENTIAL_ANY_SCOPE,
445 &plaintext);
05eb896f
LP
446 if (r < 0)
447 return r;
448
449 erase_and_free(data);
8d042bc4
LP
450 data = TAKE_PTR(plaintext.iov_base);
451 size = plaintext.iov_len;
05eb896f
LP
452 }
453
5945640e
LP
454 r = write_blob(stdout, data, size);
455 if (r < 0)
456 return r;
457 }
458
459 return ret;
460}
461
462static int verb_encrypt(int argc, char **argv, void *userdata) {
8d042bc4 463 _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {};
5945640e 464 _cleanup_free_ char *base64_buf = NULL, *fname = NULL;
5945640e 465 const char *input_path, *output_path, *name;
5945640e
LP
466 ssize_t base64_size;
467 usec_t timestamp;
468 int r;
469
470 assert(argc == 3);
471
2f092762 472 input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
5945640e
LP
473
474 if (input_path)
8d042bc4 475 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);
5945640e 476 else
8d042bc4 477 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);
5945640e
LP
478 if (r == -E2BIG)
479 return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
480 if (r < 0)
481 return log_error_errno(r, "Failed to read plaintext: %m");
482
2f092762 483 output_path = empty_or_dash(argv[2]) ? NULL : argv[2];
5945640e
LP
484
485 if (arg_name_any)
486 name = NULL;
487 else if (arg_name)
488 name = arg_name;
489 else if (output_path) {
490 r = path_extract_filename(output_path, &fname);
491 if (r < 0)
492 return log_error_errno(r, "Failed to extract filename from '%s': %m", output_path);
493 if (r == O_DIRECTORY)
494 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", output_path);
495
496 name = fname;
497 } else {
498 log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
499 name = NULL;
500 }
501
502 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
503
504 if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp)
505 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid.");
506
19f16c99
LP
507 if (geteuid() != 0)
508 r = ipc_encrypt_credential(
509 name,
510 timestamp,
511 arg_not_after,
512 arg_uid,
513 &plaintext,
514 /* flags= */ 0,
515 &output);
516 else
517 r = encrypt_credential_and_warn(
518 arg_with_key,
519 name,
520 timestamp,
521 arg_not_after,
522 arg_tpm2_device,
523 arg_tpm2_pcr_mask,
524 arg_tpm2_public_key,
525 arg_tpm2_public_key_pcr_mask,
526 arg_uid,
527 &plaintext,
528 /* flags= */ 0,
529 &output);
5945640e
LP
530 if (r < 0)
531 return r;
532
8d042bc4 533 base64_size = base64mem_full(output.iov_base, output.iov_len, arg_pretty ? 69 : 79, &base64_buf);
5945640e
LP
534 if (base64_size < 0)
535 return base64_size;
536
c74e13a5
FS
537 /* Pretty print makes sense only if we're printing stuff to stdout
538 * and if a cred name is provided via --name= (since we can't use
539 * the output file name as the cred name here) */
540 if (arg_pretty && !output_path && name) {
5945640e
LP
541 _cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
542
c74e13a5
FS
543 escaped = cescape(name);
544 if (!escaped)
545 return log_oom();
5945640e
LP
546
547 indented = strreplace(base64_buf, "\n", " \\\n ");
548 if (!indented)
549 return log_oom();
550
c74e13a5 551 j = strjoin("SetCredentialEncrypted=", escaped, ": \\\n ", indented, "\n");
5945640e
LP
552 if (!j)
553 return log_oom();
554
555 free_and_replace(base64_buf, j);
556 }
557
558 if (output_path)
559 r = write_string_file(output_path, base64_buf, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE);
560 else
561 r = write_string_stream(stdout, base64_buf, 0);
562 if (r < 0)
563 return log_error_errno(r, "Failed to write result: %m");
564
565 return EXIT_SUCCESS;
566}
567
568static int verb_decrypt(int argc, char **argv, void *userdata) {
8d042bc4
LP
569 _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {};
570 _cleanup_free_ char *fname = NULL;
5945640e
LP
571 _cleanup_fclose_ FILE *output_file = NULL;
572 const char *input_path, *output_path, *name;
5945640e
LP
573 usec_t timestamp;
574 FILE *f;
575 int r;
576
577 assert(IN_SET(argc, 2, 3));
578
2f092762 579 input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
5945640e
LP
580
581 if (input_path)
8d042bc4 582 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);
5945640e 583 else
8d042bc4 584 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);
5945640e
LP
585 if (r == -E2BIG)
586 return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
587 if (r < 0)
588 return log_error_errno(r, "Failed to read encrypted credential data: %m");
589
c6661401 590 output_path = (argc < 3 || empty_or_dash(argv[2])) ? NULL : argv[2];
5945640e
LP
591
592 if (arg_name_any)
593 name = NULL;
594 else if (arg_name)
595 name = arg_name;
596 else if (input_path) {
597 r = path_extract_filename(input_path, &fname);
598 if (r < 0)
599 return log_error_errno(r, "Failed to extract filename from '%s': %m", input_path);
600 if (r == O_DIRECTORY)
601 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", input_path);
602
603 name = fname;
604 } else {
605 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
606 name = NULL;
607 }
608
609 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
610
19f16c99
LP
611 if (geteuid() != 0)
612 r = ipc_decrypt_credential(
613 name,
614 timestamp,
615 arg_uid,
616 &input,
617 /* flags= */ 0,
618 &plaintext);
619 else
620 r = decrypt_credential_and_warn(
621 name,
622 timestamp,
623 arg_tpm2_device,
624 arg_tpm2_signature,
625 arg_uid,
626 &input,
aadbe559 627 arg_allow_null ? CREDENTIAL_ALLOW_NULL : 0,
19f16c99 628 &plaintext);
5945640e
LP
629 if (r < 0)
630 return r;
631
632 if (output_path) {
633 output_file = fopen(output_path, "we");
634 if (!output_file)
635 return log_error_errno(errno, "Failed to create output file '%s': %m", output_path);
636
637 f = output_file;
638 } else
639 f = stdout;
640
8d042bc4 641 r = write_blob(f, plaintext.iov_base, plaintext.iov_len);
5945640e
LP
642 if (r < 0)
643 return r;
644
645 return EXIT_SUCCESS;
646}
647
648static int verb_setup(int argc, char **argv, void *userdata) {
8d042bc4 649 _cleanup_(iovec_done_erase) struct iovec host_key = {};
5945640e
LP
650 int r;
651
8d042bc4 652 r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, &host_key);
5945640e
LP
653 if (r < 0)
654 return log_error_errno(r, "Failed to setup credentials host key: %m");
655
8d042bc4 656 log_info("%zu byte credentials host key set up.", host_key.iov_len);
5945640e
LP
657
658 return EXIT_SUCCESS;
659}
660
6e0cb815
LP
661static int verb_has_tpm2(int argc, char **argv, void *userdata) {
662 Tpm2Support s;
663
664 s = tpm2_support();
665
666 if (!arg_quiet) {
667 if (s == TPM2_SUPPORT_FULL)
668 puts("yes");
669 else if (s == TPM2_SUPPORT_NONE)
670 puts("no");
671 else
672 puts("partial");
673
674 printf("%sfirmware\n"
675 "%sdriver\n"
300bba79 676 "%ssystem\n"
33931049
DDM
677 "%ssubsystem\n"
678 "%slibraries\n",
6e0cb815
LP
679 plus_minus(s & TPM2_SUPPORT_FIRMWARE),
680 plus_minus(s & TPM2_SUPPORT_DRIVER),
300bba79 681 plus_minus(s & TPM2_SUPPORT_SYSTEM),
33931049
DDM
682 plus_minus(s & TPM2_SUPPORT_SUBSYSTEM),
683 plus_minus(s & TPM2_SUPPORT_LIBRARIES));
6e0cb815
LP
684 }
685
686 /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values
687 * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than
8ac6b05b 688 * what is there, acknowledging the fact that for process exit statuses it is customary to return
6e0cb815
LP
689 * zero (EXIT_FAILURE) when all is good, instead of all being bad. */
690 return ~s & TPM2_SUPPORT_FULL;
691}
692
5945640e
LP
693static int verb_help(int argc, char **argv, void *userdata) {
694 _cleanup_free_ char *link = NULL;
695 int r;
696
697 r = terminal_urlify_man("systemd-creds", "1", &link);
698 if (r < 0)
699 return log_oom();
700
701 printf("%1$s [OPTIONS...] COMMAND ...\n"
702 "\n%5$sDisplay and Process Credentials.%6$s\n"
703 "\n%3$sCommands:%4$s\n"
704 " list Show installed and available versions\n"
705 " cat CREDENTIAL... Show specified credentials\n"
706 " setup Generate credentials host key, if not existing yet\n"
707 " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
708 " ciphertext credential file\n"
709 " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
710 " plaintext credential file\n"
6e0cb815 711 " has-tpm2 Report whether TPM2 support is available\n"
5945640e
LP
712 " -h --help Show this help\n"
713 " --version Show package version\n"
714 "\n%3$sOptions:%4$s\n"
715 " --no-pager Do not pipe output into a pager\n"
716 " --no-legend Do not show the headers and footers\n"
717 " --json=pretty|short|off\n"
718 " Generate JSON output\n"
719 " --system Show credentials passed to system\n"
720 " --transcode=base64|unbase64|hex|unhex\n"
721 " Transcode credential data\n"
722 " --newline=auto|yes|no\n"
723 " Suffix output with newline\n"
724 " -p --pretty Output as SetCredentialEncrypted= line\n"
725 " --name=NAME Override filename included in encrypted credential\n"
726 " --timestamp=TIME Include specified timestamp in encrypted credential\n"
727 " --not-after=TIME Include specified invalidation time in encrypted\n"
728 " credential\n"
bfbd7ac9 729 " --with-key=host|tpm2|host+tpm2|null|auto|auto-initrd\n"
5945640e
LP
730 " Which keys to encrypt with\n"
731 " -H Shortcut for --with-key=host\n"
732 " -T Shortcut for --with-key=tpm2\n"
733 " --tpm2-device=PATH\n"
734 " Pick TPM2 device\n"
735 " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
75ddec93
LP
736 " Specify TPM2 PCRs to seal against (fixed hash)\n"
737 " --tpm2-public-key=PATH\n"
738 " Specify PEM certificate to seal against\n"
739 " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
740 " Specify TPM2 PCRs to seal against (public key)\n"
741 " --tpm2-signature=PATH\n"
742 " Specify signature for public key PCR policy\n"
c85b68f6
LP
743 " --user Select user-scoped credential encryption\n"
744 " --uid=UID Select user for scoped credentials\n"
aadbe559 745 " --allow-null Allow decrypting credentials with empty key\n"
6e0cb815 746 " -q --quiet Suppress output for 'has-tpm2' verb\n"
65064e2f
AAF
747 "\nSee the %2$s for details.\n",
748 program_invocation_short_name,
749 link,
750 ansi_underline(),
751 ansi_normal(),
752 ansi_highlight(),
753 ansi_normal());
5945640e
LP
754
755 return 0;
756}
757
758static int parse_argv(int argc, char *argv[]) {
759
760 enum {
761 ARG_VERSION = 0x100,
762 ARG_NO_PAGER,
763 ARG_NO_LEGEND,
764 ARG_JSON,
765 ARG_SYSTEM,
766 ARG_TRANSCODE,
767 ARG_NEWLINE,
768 ARG_WITH_KEY,
769 ARG_TPM2_DEVICE,
770 ARG_TPM2_PCRS,
75ddec93
LP
771 ARG_TPM2_PUBLIC_KEY,
772 ARG_TPM2_PUBLIC_KEY_PCRS,
773 ARG_TPM2_SIGNATURE,
5945640e
LP
774 ARG_NAME,
775 ARG_TIMESTAMP,
776 ARG_NOT_AFTER,
c85b68f6
LP
777 ARG_USER,
778 ARG_UID,
aadbe559 779 ARG_ALLOW_NULL,
5945640e
LP
780 };
781
782 static const struct option options[] = {
75ddec93
LP
783 { "help", no_argument, NULL, 'h' },
784 { "version", no_argument, NULL, ARG_VERSION },
785 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
786 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
787 { "json", required_argument, NULL, ARG_JSON },
788 { "system", no_argument, NULL, ARG_SYSTEM },
789 { "transcode", required_argument, NULL, ARG_TRANSCODE },
790 { "newline", required_argument, NULL, ARG_NEWLINE },
791 { "pretty", no_argument, NULL, 'p' },
792 { "with-key", required_argument, NULL, ARG_WITH_KEY },
793 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
794 { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
795 { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
796 { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
797 { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
798 { "name", required_argument, NULL, ARG_NAME },
799 { "timestamp", required_argument, NULL, ARG_TIMESTAMP },
800 { "not-after", required_argument, NULL, ARG_NOT_AFTER },
801 { "quiet", no_argument, NULL, 'q' },
c85b68f6
LP
802 { "user", no_argument, NULL, ARG_USER },
803 { "uid", required_argument, NULL, ARG_UID },
aadbe559 804 { "allow-null", no_argument, NULL, ARG_ALLOW_NULL },
5945640e
LP
805 {}
806 };
807
808 int c, r;
809
810 assert(argc >= 0);
811 assert(argv);
812
6e0cb815 813 while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) {
5945640e
LP
814
815 switch (c) {
816
817 case 'h':
818 return verb_help(0, NULL, NULL);
819
820 case ARG_VERSION:
821 return version();
822
823 case ARG_NO_PAGER:
824 arg_pager_flags |= PAGER_DISABLE;
825 break;
826
827 case ARG_NO_LEGEND:
828 arg_legend = false;
829 break;
830
831 case ARG_JSON:
832 r = parse_json_argument(optarg, &arg_json_format_flags);
833 if (r <= 0)
834 return r;
835
836 break;
837
838 case ARG_SYSTEM:
839 arg_system = true;
840 break;
841
842 case ARG_TRANSCODE:
843 if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */
844 arg_transcode = TRANSCODE_OFF;
845 else {
846 TranscodeMode m;
847
848 m = transcode_mode_from_string(optarg);
849 if (m < 0)
850 return log_error_errno(m, "Failed to parse transcode mode: %m");
851
852 arg_transcode = m;
853 }
854
855 break;
856
857 case ARG_NEWLINE:
858 if (isempty(optarg) || streq(optarg, "auto"))
859 arg_newline = -1;
860 else {
51214cf4 861 r = parse_boolean_argument("--newline=", optarg, NULL);
5945640e
LP
862 if (r < 0)
863 return r;
864
51214cf4 865 arg_newline = r;
5945640e
LP
866 }
867 break;
868
869 case 'p':
870 arg_pretty = true;
871 break;
872
873 case ARG_WITH_KEY:
874 if (isempty(optarg) || streq(optarg, "auto"))
571d829e 875 arg_with_key = _CRED_AUTO;
b6553329
LP
876 else if (streq(optarg, "auto-initrd"))
877 arg_with_key = _CRED_AUTO_INITRD;
5945640e
LP
878 else if (streq(optarg, "host"))
879 arg_with_key = CRED_AES256_GCM_BY_HOST;
880 else if (streq(optarg, "tpm2"))
881 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
75ddec93
LP
882 else if (streq(optarg, "tpm2-with-public-key"))
883 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK;
5945640e
LP
884 else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
885 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
75ddec93
LP
886 else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
887 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
6d78dc28
LP
888 else if (STR_IN_SET(optarg, "null", "tpm2-absent"))
889 arg_with_key = CRED_AES256_GCM_BY_NULL;
5945640e
LP
890 else
891 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
892
893 break;
894
895 case 'H':
896 arg_with_key = CRED_AES256_GCM_BY_HOST;
897 break;
898
899 case 'T':
900 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
901 break;
902
d1829af9 903 case ARG_TPM2_DEVICE:
5945640e
LP
904 if (streq(optarg, "list"))
905 return tpm2_list_devices();
906
d1829af9 907 arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
5945640e 908 break;
5945640e 909
75ddec93 910 case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */
07c04061 911 r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask);
5945640e
LP
912 if (r < 0)
913 return r;
914
5945640e 915 break;
5945640e 916
75ddec93
LP
917 case ARG_TPM2_PUBLIC_KEY:
918 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
919 if (r < 0)
920 return r;
921
922 break;
923
924 case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */
07c04061 925 r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask);
75ddec93
LP
926 if (r < 0)
927 return r;
928
929 break;
930
931 case ARG_TPM2_SIGNATURE:
932 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
933 if (r < 0)
934 return r;
935
936 break;
937
5945640e
LP
938 case ARG_NAME:
939 if (isempty(optarg)) {
940 arg_name = NULL;
941 arg_name_any = true;
942 break;
943 }
944
945 if (!credential_name_valid(optarg))
946 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg);
947
948 arg_name = optarg;
949 arg_name_any = false;
950 break;
951
952 case ARG_TIMESTAMP:
953 r = parse_timestamp(optarg, &arg_timestamp);
954 if (r < 0)
955 return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
956
957 break;
958
959 case ARG_NOT_AFTER:
960 r = parse_timestamp(optarg, &arg_not_after);
961 if (r < 0)
962 return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg);
963
964 break;
965
c85b68f6
LP
966 case ARG_USER:
967 if (!uid_is_valid(arg_uid))
968 arg_uid = getuid();
969
970 break;
971
972 case ARG_UID:
973 if (isempty(optarg))
974 arg_uid = UID_INVALID;
975 else if (streq(optarg, "self"))
976 arg_uid = getuid();
977 else {
978 const char *name = optarg;
979
980 r = get_user_creds(
981 &name,
982 &arg_uid,
983 /* ret_gid= */ NULL,
984 /* ret_home= */ NULL,
985 /* ret_shell= */ NULL,
986 /* flags= */ 0);
987 if (r < 0)
988 return log_error_errno(r, "Failed to resolve user '%s': %m", optarg);
989 }
990 break;
991
aadbe559
LN
992 case ARG_ALLOW_NULL:
993 arg_allow_null = true;
994 break;
995
6e0cb815
LP
996 case 'q':
997 arg_quiet = true;
998 break;
999
5945640e
LP
1000 case '?':
1001 return -EINVAL;
1002
1003 default:
04499a70 1004 assert_not_reached();
5945640e
LP
1005 }
1006 }
1007
c85b68f6
LP
1008 if (uid_is_valid(arg_uid)) {
1009 /* If a UID is specified, then switch to scoped credentials */
1010
1011 if (sd_id128_equal(arg_with_key, _CRED_AUTO))
1012 arg_with_key = _CRED_AUTO_SCOPED;
1013 else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED))
1014 arg_with_key = CRED_AES256_GCM_BY_HOST_SCOPED;
1015 else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED))
1016 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED;
1017 else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED))
1018 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED;
1019 else
1020 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected key not available in --uid= scoped mode, refusing.");
1021 }
1022
5945640e
LP
1023 if (arg_tpm2_pcr_mask == UINT32_MAX)
1024 arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
75ddec93 1025 if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
2099cd62 1026 arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_BOOT;
5945640e 1027
644f19c7
LP
1028 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
1029 if (r < 0)
1030 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
1031 arg_varlink = r;
1032
5945640e
LP
1033 return 1;
1034}
1035
1036static int creds_main(int argc, char *argv[]) {
1037
1038 static const Verb verbs[] = {
6e0cb815
LP
1039 { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list },
1040 { "cat", 2, VERB_ANY, 0, verb_cat },
1041 { "encrypt", 3, 3, 0, verb_encrypt },
1042 { "decrypt", 2, 3, 0, verb_decrypt },
1043 { "setup", VERB_ANY, 1, 0, verb_setup },
1044 { "help", VERB_ANY, 1, 0, verb_help },
1045 { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 },
5945640e
LP
1046 {}
1047 };
1048
1049 return dispatch_verb(argc, argv, verbs, NULL);
1050}
1051
8464f7cb
LP
1052#define TIMESTAMP_FRESH_MAX (30*USEC_PER_SEC)
1053
1054static bool timestamp_is_fresh(usec_t x) {
1055 usec_t n = now(CLOCK_REALTIME);
1056
1057 /* We'll only allow unprivileged encryption/decryption for somehwhat "fresh" timestamps */
1058
1059 if (x > n)
1060 return x - n <= TIMESTAMP_FRESH_MAX;
1061 else
1062 return n - x <= TIMESTAMP_FRESH_MAX;
1063}
1064
1065typedef enum CredentialScope {
1066 CREDENTIAL_SYSTEM,
1067 CREDENTIAL_USER,
1068 /* One day we should add more here, for example, per-app/per-service credentials */
1069 _CREDENTIAL_SCOPE_MAX,
1070 _CREDENTIAL_SCOPE_INVALID = -EINVAL,
1071} CredentialScope;
1072
1073static const char* credential_scope_table[_CREDENTIAL_SCOPE_MAX] = {
1074 [CREDENTIAL_SYSTEM] = "system",
1075 [CREDENTIAL_USER] = "user",
1076};
1077
1078DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(credential_scope, CredentialScope);
1079static JSON_DISPATCH_ENUM_DEFINE(dispatch_credential_scope, CredentialScope, credential_scope_from_string);
1080
644f19c7
LP
1081typedef struct MethodEncryptParameters {
1082 const char *name;
1083 const char *text;
1084 struct iovec data;
1085 uint64_t timestamp;
1086 uint64_t not_after;
8464f7cb
LP
1087 CredentialScope scope;
1088 uid_t uid;
644f19c7
LP
1089} MethodEncryptParameters;
1090
1091static void method_encrypt_parameters_done(MethodEncryptParameters *p) {
1092 assert(p);
1093
1094 iovec_done_erase(&p->data);
1095}
1096
8464f7cb
LP
1097static int settle_scope(
1098 Varlink *link,
1099 CredentialScope *scope,
1100 uid_t *uid,
1101 CredentialFlags *flags,
1102 bool *any_scope_after_polkit) {
1103
1104 uid_t peer_uid;
1105 int r;
1106
1107 assert(link);
1108 assert(scope);
1109 assert(uid);
1110 assert(flags);
1111
1112 r = varlink_get_peer_uid(link, &peer_uid);
1113 if (r < 0)
1114 return r;
1115
1116 if (*scope < 0) {
1117 if (uid_is_valid(*uid))
1118 *scope = CREDENTIAL_USER;
1119 else {
1120 *scope = CREDENTIAL_SYSTEM; /* When encrypting, we spit out a system credential */
1121 *uid = peer_uid; /* When decrypting a user credential, use this UID */
1122 }
1123
1124 if (peer_uid == 0)
1125 *flags |= CREDENTIAL_ANY_SCOPE;
1126
1127 if (any_scope_after_polkit)
1128 *any_scope_after_polkit = true;
1129 } else if (*scope == CREDENTIAL_USER) {
1130 if (!uid_is_valid(*uid))
1131 *uid = peer_uid;
1132 } else {
1133 assert(*scope == CREDENTIAL_SYSTEM);
1134 if (uid_is_valid(*uid))
1135 return varlink_error_invalid_parameter_name(link, "uid");
1136 }
1137
1138 return 0;
1139}
1140
644f19c7
LP
1141static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1142
1143 static const JsonDispatch dispatch_table[] = {
1144 { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, name), 0 },
1145 { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, text), 0 },
1146 { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodEncryptParameters, data), 0 },
1147 { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 },
1148 { "notAfter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 },
8464f7cb
LP
1149 { "scope", JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodEncryptParameters, scope), 0 },
1150 { "uid", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uid_gid, offsetof(MethodEncryptParameters, uid), 0 },
caef0bc3 1151 VARLINK_DISPATCH_POLKIT_FIELD,
644f19c7
LP
1152 {}
1153 };
1154 _cleanup_(method_encrypt_parameters_done) MethodEncryptParameters p = {
1155 .timestamp = UINT64_MAX,
1156 .not_after = UINT64_MAX,
8464f7cb
LP
1157 .scope = _CREDENTIAL_SCOPE_INVALID,
1158 .uid = UID_INVALID,
644f19c7
LP
1159 };
1160 _cleanup_(iovec_done) struct iovec output = {};
caef0bc3 1161 Hashmap **polkit_registry = ASSERT_PTR(userdata);
8464f7cb
LP
1162 CredentialFlags cflags = 0;
1163 bool timestamp_fresh;
1164 uid_t peer_uid;
644f19c7
LP
1165 int r;
1166
1167 assert(link);
1168
644f19c7
LP
1169 r = varlink_dispatch(link, parameters, dispatch_table, &p);
1170 if (r != 0)
1171 return r;
1172
1173 if (p.name && !credential_name_valid(p.name))
1174 return varlink_error_invalid_parameter_name(link, "name");
1175 /* Specifying both or neither the text string and the binary data is not allowed */
1176 if (!!p.text == !!p.data.iov_base)
1177 return varlink_error_invalid_parameter_name(link, "data");
8464f7cb 1178 if (p.timestamp == UINT64_MAX) {
644f19c7 1179 p.timestamp = now(CLOCK_REALTIME);
8464f7cb
LP
1180 timestamp_fresh = true;
1181 } else
1182 timestamp_fresh = timestamp_is_fresh(p.timestamp);
644f19c7
LP
1183 if (p.not_after != UINT64_MAX && p.not_after < p.timestamp)
1184 return varlink_error_invalid_parameter_name(link, "notAfter");
1185
8464f7cb
LP
1186 r = settle_scope(link, &p.scope, &p.uid, &cflags, /* any_scope_after_polkit= */ NULL);
1187 if (r < 0)
1188 return r;
1189
1190 r = varlink_get_peer_uid(link, &peer_uid);
1191 if (r < 0)
caef0bc3
LP
1192 return r;
1193
8464f7cb
LP
1194 /* Relax security requirements if peer wants to encrypt credentials for themselves */
1195 bool own_scope = p.scope == CREDENTIAL_USER && p.uid == peer_uid;
1196
1197 if (!own_scope || !timestamp_fresh) {
9d7f6b3d 1198 /* Insist on PK if client wants to encrypt for another user or the system, or if the timestamp was explicitly overridden. */
8464f7cb
LP
1199 r = varlink_verify_polkit_async(
1200 link,
1201 /* bus= */ NULL,
1202 "io.systemd.credentials.encrypt",
1203 /* details= */ NULL,
8464f7cb
LP
1204 polkit_registry);
1205 if (r <= 0)
1206 return r;
1207 }
1208
644f19c7 1209 r = encrypt_credential_and_warn(
8464f7cb 1210 p.scope == CREDENTIAL_USER ? _CRED_AUTO_SCOPED : _CRED_AUTO,
644f19c7
LP
1211 p.name,
1212 p.timestamp,
1213 p.not_after,
1214 arg_tpm2_device,
1215 arg_tpm2_pcr_mask,
1216 arg_tpm2_public_key,
1217 arg_tpm2_public_key_pcr_mask,
8464f7cb 1218 p.uid,
8d042bc4 1219 p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data,
8464f7cb 1220 cflags,
8d042bc4 1221 &output);
8464f7cb
LP
1222 if (r == -ESRCH)
1223 return varlink_error(link, "io.systemd.Credentials.NoSuchUser", NULL);
644f19c7
LP
1224 if (r < 0)
1225 return r;
1226
1227 _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
1228
1229 r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("blob", &output)));
1230 if (r < 0)
1231 return r;
1232
1233 /* Let's also mark the (theoretically encrypted) reply as sensitive, in case the NULL encryption scheme was used. */
1234 json_variant_sensitive(reply);
1235
1236 return varlink_reply(link, reply);
1237}
1238
1239typedef struct MethodDecryptParameters {
1240 const char *name;
1241 struct iovec blob;
1242 uint64_t timestamp;
8464f7cb
LP
1243 CredentialScope scope;
1244 uid_t uid;
644f19c7
LP
1245} MethodDecryptParameters;
1246
1247static void method_decrypt_parameters_done(MethodDecryptParameters *p) {
1248 assert(p);
1249
1250 iovec_done_erase(&p->blob);
1251}
1252
1253static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1254
1255 static const JsonDispatch dispatch_table[] = {
caef0bc3
LP
1256 { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
1257 { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), JSON_MANDATORY },
1258 { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
8464f7cb
LP
1259 { "scope", JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodDecryptParameters, scope), 0 },
1260 { "uid", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uid_gid, offsetof(MethodDecryptParameters, uid), 0 },
caef0bc3 1261 VARLINK_DISPATCH_POLKIT_FIELD,
644f19c7
LP
1262 {}
1263 };
1264 _cleanup_(method_decrypt_parameters_done) MethodDecryptParameters p = {
1265 .timestamp = UINT64_MAX,
8464f7cb
LP
1266 .scope = _CREDENTIAL_SCOPE_INVALID,
1267 .uid = UID_INVALID,
644f19c7 1268 };
8464f7cb 1269 bool timestamp_fresh, any_scope_after_polkit = false;
644f19c7 1270 _cleanup_(iovec_done_erase) struct iovec output = {};
caef0bc3 1271 Hashmap **polkit_registry = ASSERT_PTR(userdata);
8464f7cb
LP
1272 CredentialFlags cflags = 0;
1273 uid_t peer_uid;
644f19c7
LP
1274 int r;
1275
1276 assert(link);
1277
644f19c7
LP
1278 r = varlink_dispatch(link, parameters, dispatch_table, &p);
1279 if (r != 0)
1280 return r;
1281
1282 if (p.name && !credential_name_valid(p.name))
1283 return varlink_error_invalid_parameter_name(link, "name");
8464f7cb 1284 if (p.timestamp == UINT64_MAX) {
644f19c7 1285 p.timestamp = now(CLOCK_REALTIME);
8464f7cb
LP
1286 timestamp_fresh = true;
1287 } else
1288 timestamp_fresh = timestamp_is_fresh(p.timestamp);
644f19c7 1289
8464f7cb
LP
1290 r = settle_scope(link, &p.scope, &p.uid, &cflags, &any_scope_after_polkit);
1291 if (r < 0)
caef0bc3
LP
1292 return r;
1293
8464f7cb
LP
1294 r = varlink_get_peer_uid(link, &peer_uid);
1295 if (r < 0)
1296 return r;
1297
1298 /* Relax security requirements if peer wants to encrypt credentials for themselves */
1299 bool own_scope = p.scope == CREDENTIAL_USER && p.uid == peer_uid;
1300 bool ask_polkit = !own_scope || !timestamp_fresh;
1301 for (;;) {
1302 if (ask_polkit) {
1303 r = varlink_verify_polkit_async(
1304 link,
1305 /* bus= */ NULL,
1306 "io.systemd.credentials.decrypt",
1307 /* details= */ NULL,
8464f7cb
LP
1308 polkit_registry);
1309 if (r <= 0)
1310 return r;
1311
1312 /* Now that we have authenticated, it's fine to allow unpriv clients access to system secrets */
1313 if (any_scope_after_polkit)
1314 cflags |= CREDENTIAL_ANY_SCOPE;
1315 }
1316
1317 r = decrypt_credential_and_warn(
1318 p.name,
1319 p.timestamp,
1320 arg_tpm2_device,
1321 arg_tpm2_signature,
1322 p.uid,
1323 &p.blob,
1324 cflags,
1325 &output);
1326 if (r != -EMEDIUMTYPE || ask_polkit || !any_scope_after_polkit)
1327 break;
1328
1329 /* So the secret was apparently intended for the system. Let's retry decrypting it after
1330 * acquiring polkit's permission. */
1331 ask_polkit = true;
1332 }
1333
644f19c7
LP
1334 if (r == -EBADMSG)
1335 return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL);
1336 if (r == -EREMOTE)
1337 return varlink_error(link, "io.systemd.Credentials.NameMismatch", NULL);
1338 if (r == -ESTALE)
1339 return varlink_error(link, "io.systemd.Credentials.TimeMismatch", NULL);
8464f7cb
LP
1340 if (r == -ESRCH)
1341 return varlink_error(link, "io.systemd.Credentials.NoSuchUser", NULL);
1342 if (r == -EMEDIUMTYPE)
1343 return varlink_error(link, "io.systemd.Credentials.BadScope", NULL);
644f19c7
LP
1344 if (r < 0)
1345 return r;
1346
1347 _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
1348
1349 r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("data", &output)));
1350 if (r < 0)
1351 return r;
1352
1353 json_variant_sensitive(reply);
1354
1355 return varlink_reply(link, reply);
1356}
1357
5945640e
LP
1358static int run(int argc, char *argv[]) {
1359 int r;
1360
1361 log_setup();
1362
1363 r = parse_argv(argc, argv);
1364 if (r <= 0)
1365 return r;
1366
644f19c7
LP
1367 if (arg_varlink) {
1368 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
caef0bc3 1369 _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL;
644f19c7
LP
1370
1371 /* Invocation as Varlink service */
1372
a570877c 1373 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
644f19c7
LP
1374 if (r < 0)
1375 return log_error_errno(r, "Failed to allocate Varlink server: %m");
1376
1377 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials);
1378 if (r < 0)
1379 return log_error_errno(r, "Failed to add Varlink interface: %m");
1380
1381 r = varlink_server_bind_method_many(
1382 varlink_server,
1383 "io.systemd.Credentials.Encrypt", vl_method_encrypt,
1384 "io.systemd.Credentials.Decrypt", vl_method_decrypt);
1385 if (r < 0)
1386 return log_error_errno(r, "Failed to bind Varlink methods: %m");
1387
caef0bc3
LP
1388 varlink_server_set_userdata(varlink_server, &polkit_registry);
1389
644f19c7
LP
1390 r = varlink_server_loop_auto(varlink_server);
1391 if (r < 0)
1392 return log_error_errno(r, "Failed to run Varlink event loop: %m");
1393
1394 return 0;
1395 }
1396
5945640e
LP
1397 return creds_main(argc, argv);
1398}
1399
1400DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);