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