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