1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <getopt.h>
4 #include <unistd.h>
5 
6 #include "creds-util.h"
7 #include "dirent-util.h"
8 #include "escape.h"
9 #include "fileio.h"
10 #include "format-table.h"
11 #include "hexdecoct.h"
12 #include "io-util.h"
13 #include "json.h"
14 #include "main-func.h"
15 #include "memory-util.h"
16 #include "missing_magic.h"
17 #include "pager.h"
18 #include "parse-argument.h"
19 #include "pretty-print.h"
20 #include "process-util.h"
21 #include "stat-util.h"
22 #include "string-table.h"
23 #include "terminal-util.h"
24 #include "tpm2-util.h"
25 #include "verbs.h"
26 
27 typedef enum TranscodeMode {
28         TRANSCODE_OFF,
29         TRANSCODE_BASE64,
30         TRANSCODE_UNBASE64,
31         TRANSCODE_HEX,
32         TRANSCODE_UNHEX,
33         _TRANSCODE_MAX,
34         _TRANSCODE_INVALID = -EINVAL,
35 } TranscodeMode;
36 
37 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
38 static PagerFlags arg_pager_flags = 0;
39 static bool arg_legend = true;
40 static bool arg_system = false;
41 static TranscodeMode arg_transcode = TRANSCODE_OFF;
42 static int arg_newline = -1;
43 static sd_id128_t arg_with_key = _CRED_AUTO;
44 static const char *arg_tpm2_device = NULL;
45 static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
46 static const char *arg_name = NULL;
47 static bool arg_name_any = false;
48 static usec_t arg_timestamp = USEC_INFINITY;
49 static usec_t arg_not_after = USEC_INFINITY;
50 static bool arg_pretty = false;
51 static bool arg_quiet = false;
52 
53 static const char* transcode_mode_table[_TRANSCODE_MAX] = {
54         [TRANSCODE_OFF] = "off",
55         [TRANSCODE_BASE64] = "base64",
56         [TRANSCODE_UNBASE64] = "unbase64",
57         [TRANSCODE_HEX] = "hex",
58         [TRANSCODE_UNHEX] = "unhex",
59 };
60 
61 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode, TranscodeMode);
62 
open_credential_directory(bool encrypted,DIR ** ret_dir,const char ** ret_prefix)63 static int open_credential_directory(
64                 bool encrypted,
65                 DIR **ret_dir,
66                 const char **ret_prefix) {
67 
68         const char *p;
69         DIR *d;
70         int r;
71 
72         assert(ret_dir);
73 
74         if (arg_system)
75                 /* PID 1 ensures that system credentials are always accessible under the same fixed path. It
76                  * will create symlinks if necessary to guarantee that. */
77                 p = encrypted ?
78                         ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY :
79                         SYSTEM_CREDENTIALS_DIRECTORY;
80         else {
81                 /* Otherwise take the dirs from the env vars we got passed */
82                 r = (encrypted ? get_encrypted_credentials_dir : get_credentials_dir)(&p);
83                 if (r == -ENXIO) /* No environment variable? */
84                         goto not_found;
85                 if (r < 0)
86                         return log_error_errno(r, "Failed to get credentials directory: %m");
87         }
88 
89         d = opendir(p);
90         if (!d) {
91                 /* No such dir? Then no creds where passed. (We conditionalize this on arg_system, since for
92                  * the per-service case a non-existing path would indicate an issue since the env var would
93                  * be set incorrectly in that case.) */
94                 if (arg_system && errno == ENOENT)
95                         goto not_found;
96 
97                 return log_error_errno(errno, "Failed to open credentials directory '%s': %m", p);
98         }
99 
100         *ret_dir = d;
101 
102         if (ret_prefix)
103                 *ret_prefix = p;
104 
105         return 1;
106 
107 not_found:
108         *ret_dir = NULL;
109 
110         if (ret_prefix)
111                 *ret_prefix = NULL;
112 
113         return 0;
114 }
115 
add_credentials_to_table(Table * t,bool encrypted)116 static int add_credentials_to_table(Table *t, bool encrypted) {
117         _cleanup_(closedirp) DIR *d = NULL;
118         const char *prefix;
119         int r;
120 
121         assert(t);
122 
123         r = open_credential_directory(encrypted, &d, &prefix);
124         if (r < 0)
125                 return r;
126         if (!d)
127                 return 0; /* No creds dir set */
128 
129         for (;;) {
130                 _cleanup_free_ char *j = NULL;
131                 const char *secure, *secure_color = NULL;
132                 _cleanup_close_ int fd = -1;
133                 struct dirent *de;
134                 struct stat st;
135 
136                 errno = 0;
137                 de = readdir_no_dot(d);
138                 if (!de) {
139                         if (errno == 0)
140                                 break;
141 
142                         return log_error_errno(errno, "Failed to read credentials directory: %m");
143                 }
144 
145                 if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
146                         continue;
147 
148                 if (!credential_name_valid(de->d_name))
149                         continue;
150 
151                 fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
152                 if (fd < 0) {
153                         if (errno == ENOENT) /* Vanished by now? */
154                                 continue;
155 
156                         return log_error_errno(errno, "Failed to open credential '%s': %m", de->d_name);
157                 }
158 
159                 if (fstat(fd, &st) < 0)
160                         return log_error_errno(errno, "Failed to stat credential '%s': %m", de->d_name);
161 
162                 if (!S_ISREG(st.st_mode))
163                         continue;
164 
165                 if (encrypted) {
166                         secure = "encrypted";
167                         secure_color = ansi_highlight_green();
168                 } else if ((st.st_mode & 0377) != 0) {
169                         secure = "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
170                         secure_color = ansi_highlight_red();
171                 } else {
172                         r = fd_is_fs_type(fd, RAMFS_MAGIC);
173                         if (r < 0)
174                                 return log_error_errno(r, "Failed to determine backing file system of '%s': %m", de->d_name);
175 
176                         secure = r ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
177                         secure_color = r ? ansi_highlight_green() : ansi_highlight_yellow4();
178                 }
179 
180                 j = path_join(prefix, de->d_name);
181                 if (!j)
182                         return log_oom();
183 
184                 r = table_add_many(
185                                 t,
186                                 TABLE_STRING, de->d_name,
187                                 TABLE_STRING, secure,
188                                 TABLE_SET_COLOR, secure_color,
189                                 TABLE_SIZE, (uint64_t) st.st_size,
190                                 TABLE_STRING, j);
191                 if (r < 0)
192                         return table_log_add_error(r);
193         }
194 
195         return 1; /* Creds dir set */
196 }
197 
verb_list(int argc,char ** argv,void * userdata)198 static int verb_list(int argc, char **argv, void *userdata) {
199         _cleanup_(table_unrefp) Table *t = NULL;
200         int r, q;
201 
202         t = table_new("name", "secure", "size", "path");
203         if (!t)
204                 return log_oom();
205 
206         (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
207 
208         r = add_credentials_to_table(t, /* encrypted= */ true);
209         if (r < 0)
210                 return r;
211 
212         q = add_credentials_to_table(t, /* encrypted= */ false);
213         if (q < 0)
214                 return q;
215 
216         if (r == 0 && q == 0) {
217                 if (arg_system)
218                         return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed to system.");
219 
220                 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
221         }
222 
223         if ((arg_json_format_flags & JSON_FORMAT_OFF) && table_get_rows(t) <= 1) {
224                 log_info("No credentials");
225                 return 0;
226         }
227 
228         return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
229 }
230 
transcode(const void * input,size_t input_size,void ** ret_output,size_t * ret_output_size)231 static int transcode(
232                 const void *input,
233                 size_t input_size,
234                 void **ret_output,
235                 size_t *ret_output_size) {
236 
237         int r;
238 
239         assert(input);
240         assert(input_size);
241         assert(ret_output);
242         assert(ret_output_size);
243 
244         switch (arg_transcode) {
245 
246         case TRANSCODE_BASE64: {
247                 char *buf;
248                 ssize_t l;
249 
250                 l = base64mem_full(input, input_size, 79, &buf);
251                 if (l < 0)
252                         return l;
253 
254                 *ret_output = buf;
255                 *ret_output_size = l;
256                 return 0;
257         }
258 
259         case TRANSCODE_UNBASE64:
260                 r = unbase64mem_full(input, input_size, true, ret_output, ret_output_size);
261                 if (r == -EPIPE) /* Uneven number of chars */
262                         return -EINVAL;
263 
264                 return r;
265 
266         case TRANSCODE_HEX: {
267                 char *buf;
268 
269                 buf = hexmem(input, input_size);
270                 if (!buf)
271                         return -ENOMEM;
272 
273                 *ret_output = buf;
274                 *ret_output_size = input_size * 2;
275                 return 0;
276         }
277 
278         case TRANSCODE_UNHEX:
279                 r = unhexmem_full(input, input_size, true, ret_output, ret_output_size);
280                 if (r == -EPIPE) /* Uneven number of chars */
281                         return -EINVAL;
282 
283                 return r;
284 
285         default:
286                 assert_not_reached();
287         }
288 }
289 
print_newline(FILE * f,const char * data,size_t l)290 static int print_newline(FILE *f, const char *data, size_t l) {
291         int fd;
292 
293         assert(f);
294         assert(data || l == 0);
295 
296         /* If turned off explicitly, don't print newline */
297         if (arg_newline == 0)
298                 return 0;
299 
300         /* If data already has newline, don't print either */
301         if (l > 0 && data[l-1] == '\n')
302                 return 0;
303 
304         /* Don't bother unless this is a tty */
305         fd = fileno(f);
306         if (fd >= 0 && isatty(fd) <= 0)
307                 return 0;
308 
309         if (fputc('\n', f) != '\n')
310                 return log_error_errno(errno, "Failed to write trailing newline: %m");
311 
312         return 1;
313 }
314 
write_blob(FILE * f,const void * data,size_t size)315 static int write_blob(FILE *f, const void *data, size_t size) {
316         _cleanup_(erase_and_freep) void *transcoded = NULL;
317         int r;
318 
319         if (arg_transcode == TRANSCODE_OFF &&
320             arg_json_format_flags != JSON_FORMAT_OFF) {
321 
322                 _cleanup_(erase_and_freep) char *suffixed = NULL;
323                 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
324 
325                 if (memchr(data, 0, size))
326                         return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential data contains embedded NUL, can't parse as JSON.");
327 
328                 suffixed = memdup_suffix0(data, size);
329                 if (!suffixed)
330                         return log_oom();
331 
332                 r = json_parse(suffixed, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
333                 if (r < 0)
334                         return log_error_errno(r, "Failed to parse JSON: %m");
335 
336                 json_variant_dump(v, arg_json_format_flags, f, NULL);
337                 return 0;
338         }
339 
340         if (arg_transcode != TRANSCODE_OFF) {
341                 r = transcode(data, size, &transcoded, &size);
342                 if (r < 0)
343                         return log_error_errno(r, "Failed to transcode data: %m");
344 
345                 data = transcoded;
346         }
347 
348         if (fwrite(data, 1, size, f) != size)
349                 return log_error_errno(errno, "Failed to write credential data: %m");
350 
351         r = print_newline(f, data, size);
352         if (r < 0)
353                 return r;
354 
355         if (fflush(f) != 0)
356                 return log_error_errno(errno, "Failed to flush output: %m");
357 
358         return 0;
359 }
360 
verb_cat(int argc,char ** argv,void * userdata)361 static int verb_cat(int argc, char **argv, void *userdata) {
362         usec_t timestamp;
363         int r, ret = 0;
364 
365         timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
366 
367         STRV_FOREACH(cn, strv_skip(argv, 1)) {
368                 _cleanup_(erase_and_freep) void *data = NULL;
369                 size_t size = 0;
370                 int encrypted;
371 
372                 if (!credential_name_valid(*cn)) {
373                         log_error("Credential name '%s' is not valid.", *cn);
374                         if (ret >= 0)
375                                 ret = -EINVAL;
376                         continue;
377                 }
378 
379                 /* Look both in regular and in encrypted credentials */
380                 for (encrypted = 0; encrypted < 2; encrypted++) {
381                         _cleanup_(closedirp) DIR *d = NULL;
382 
383                         r = open_credential_directory(encrypted, &d, NULL);
384                         if (r < 0)
385                                 return log_error_errno(r, "Failed to open credentials directory: %m");
386                         if (!d) /* Not set */
387                                 continue;
388 
389                         r = read_full_file_full(
390                                         dirfd(d), *cn,
391                                         UINT64_MAX, SIZE_MAX,
392                                         READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
393                                         NULL,
394                                         (char**) &data, &size);
395                         if (r == -ENOENT) /* Not found */
396                                 continue;
397                         if (r >= 0) /* Found */
398                                 break;
399 
400                         log_error_errno(r, "Failed to read credential '%s': %m", *cn);
401                         if (ret >= 0)
402                                 ret = r;
403                 }
404 
405                 if (encrypted >= 2) { /* Found nowhere */
406                         log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn);
407                         if (ret >= 0)
408                                 ret = -ENOENT;
409 
410                         continue;
411                 }
412 
413                 if (encrypted) {
414                         _cleanup_(erase_and_freep) void *plaintext = NULL;
415                         size_t plaintext_size;
416 
417                         r = decrypt_credential_and_warn(
418                                         *cn,
419                                         timestamp,
420                                         arg_tpm2_device,
421                                         data, size,
422                                         &plaintext, &plaintext_size);
423                         if (r < 0)
424                                 return r;
425 
426                         erase_and_free(data);
427                         data = TAKE_PTR(plaintext);
428                         size = plaintext_size;
429                 }
430 
431                 r = write_blob(stdout, data, size);
432                 if (r < 0)
433                         return r;
434         }
435 
436         return ret;
437 }
438 
verb_encrypt(int argc,char ** argv,void * userdata)439 static int verb_encrypt(int argc, char **argv, void *userdata) {
440         _cleanup_free_ char *base64_buf = NULL, *fname = NULL;
441         _cleanup_(erase_and_freep) char *plaintext = NULL;
442         const char *input_path, *output_path, *name;
443         _cleanup_free_ void *output = NULL;
444         size_t plaintext_size, output_size;
445         ssize_t base64_size;
446         usec_t timestamp;
447         int r;
448 
449         assert(argc == 3);
450 
451         input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
452 
453         if (input_path)
454                 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);
455         else
456                 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);
457         if (r == -E2BIG)
458                 return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
459         if (r < 0)
460                 return log_error_errno(r, "Failed to read plaintext: %m");
461 
462         output_path = empty_or_dash(argv[2]) ? NULL : argv[2];
463 
464         if (arg_name_any)
465                 name = NULL;
466         else if (arg_name)
467                 name = arg_name;
468         else if (output_path) {
469                 r = path_extract_filename(output_path, &fname);
470                 if (r < 0)
471                         return log_error_errno(r, "Failed to extract filename from '%s': %m", output_path);
472                 if (r == O_DIRECTORY)
473                         return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", output_path);
474 
475                 name = fname;
476         } else {
477                 log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
478                 name = NULL;
479         }
480 
481         timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
482 
483         if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp)
484                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid.");
485 
486         r = encrypt_credential_and_warn(
487                         arg_with_key,
488                         name,
489                         timestamp,
490                         arg_not_after,
491                         arg_tpm2_device,
492                         arg_tpm2_pcr_mask,
493                         plaintext, plaintext_size,
494                         &output, &output_size);
495         if (r < 0)
496                 return r;
497 
498         base64_size = base64mem_full(output, output_size, arg_pretty ? 69 : 79, &base64_buf);
499         if (base64_size < 0)
500                 return base64_size;
501 
502         if (arg_pretty) {
503                 _cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
504 
505                 if (name) {
506                         escaped = cescape(name);
507                         if (!escaped)
508                                 return log_oom();
509                 }
510 
511                 indented = strreplace(base64_buf, "\n", " \\\n        ");
512                 if (!indented)
513                         return log_oom();
514 
515                 j = strjoin("SetCredentialEncrypted=", name, ": \\\n        ", indented, "\n");
516                 if (!j)
517                         return log_oom();
518 
519                 free_and_replace(base64_buf, j);
520         }
521 
522         if (output_path)
523                 r = write_string_file(output_path, base64_buf, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE);
524         else
525                 r = write_string_stream(stdout, base64_buf, 0);
526         if (r < 0)
527                 return log_error_errno(r, "Failed to write result: %m");
528 
529         return EXIT_SUCCESS;
530 }
531 
verb_decrypt(int argc,char ** argv,void * userdata)532 static int verb_decrypt(int argc, char **argv, void *userdata) {
533         _cleanup_(erase_and_freep) void *plaintext = NULL;
534         _cleanup_free_ char *input = NULL, *fname = NULL;
535         _cleanup_fclose_ FILE *output_file = NULL;
536         const char *input_path, *output_path, *name;
537         size_t input_size, plaintext_size;
538         usec_t timestamp;
539         FILE *f;
540         int r;
541 
542         assert(IN_SET(argc, 2, 3));
543 
544         input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
545 
546         if (input_path)
547                 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);
548         else
549                 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);
550         if (r == -E2BIG)
551                 return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
552         if (r < 0)
553                 return log_error_errno(r, "Failed to read encrypted credential data: %m");
554 
555         output_path = (argc < 3 || isempty(argv[2]) || streq(argv[2], "-")) ? NULL : argv[2];
556 
557         if (arg_name_any)
558                 name = NULL;
559         else if (arg_name)
560                 name = arg_name;
561         else if (input_path) {
562                 r = path_extract_filename(input_path, &fname);
563                 if (r < 0)
564                         return log_error_errno(r, "Failed to extract filename from '%s': %m", input_path);
565                 if (r == O_DIRECTORY)
566                         return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", input_path);
567 
568                 name = fname;
569         } else {
570                 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
571                 name = NULL;
572         }
573 
574         timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
575 
576         r = decrypt_credential_and_warn(
577                         name,
578                         timestamp,
579                         arg_tpm2_device,
580                         input, input_size,
581                         &plaintext, &plaintext_size);
582         if (r < 0)
583                 return r;
584 
585         if (output_path) {
586                 output_file = fopen(output_path, "we");
587                 if (!output_file)
588                         return log_error_errno(errno, "Failed to create output file '%s': %m", output_path);
589 
590                 f = output_file;
591         } else
592                 f = stdout;
593 
594         r = write_blob(f, plaintext, plaintext_size);
595         if (r < 0)
596                 return r;
597 
598         return EXIT_SUCCESS;
599 }
600 
verb_setup(int argc,char ** argv,void * userdata)601 static int verb_setup(int argc, char **argv, void *userdata) {
602         size_t size;
603         int r;
604 
605         r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, NULL, &size);
606         if (r < 0)
607                 return log_error_errno(r, "Failed to setup credentials host key: %m");
608 
609         log_info("%zu byte credentials host key set up.", size);
610 
611         return EXIT_SUCCESS;
612 }
613 
verb_has_tpm2(int argc,char ** argv,void * userdata)614 static int verb_has_tpm2(int argc, char **argv, void *userdata) {
615         Tpm2Support s;
616 
617         s = tpm2_support();
618 
619         if (!arg_quiet) {
620                 if (s == TPM2_SUPPORT_FULL)
621                         puts("yes");
622                 else if (s == TPM2_SUPPORT_NONE)
623                         puts("no");
624                 else
625                         puts("partial");
626 
627                 printf("%sfirmware\n"
628                        "%sdriver\n"
629                        "%ssystem\n",
630                        plus_minus(s & TPM2_SUPPORT_FIRMWARE),
631                        plus_minus(s & TPM2_SUPPORT_DRIVER),
632                        plus_minus(s & TPM2_SUPPORT_SYSTEM));
633         }
634 
635         /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values
636          * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than
637          * what is there, acknowledging the fact that for process exit statuses it is customary to return
638          * zero (EXIT_FAILURE) when all is good, instead of all being bad. */
639         return ~s & TPM2_SUPPORT_FULL;
640 }
641 
verb_help(int argc,char ** argv,void * userdata)642 static int verb_help(int argc, char **argv, void *userdata) {
643         _cleanup_free_ char *link = NULL;
644         int r;
645 
646         r = terminal_urlify_man("systemd-creds", "1", &link);
647         if (r < 0)
648                 return log_oom();
649 
650         printf("%1$s [OPTIONS...] COMMAND ...\n"
651                "\n%5$sDisplay and Process Credentials.%6$s\n"
652                "\n%3$sCommands:%4$s\n"
653                "  list                    Show installed and available versions\n"
654                "  cat CREDENTIAL...       Show specified credentials\n"
655                "  setup                   Generate credentials host key, if not existing yet\n"
656                "  encrypt INPUT OUTPUT    Encrypt plaintext credential file and write to\n"
657                "                          ciphertext credential file\n"
658                "  decrypt INPUT [OUTPUT]  Decrypt ciphertext credential file and write to\n"
659                "                          plaintext credential file\n"
660                "  has-tpm2                Report whether TPM2 support is available\n"
661                "  -h --help               Show this help\n"
662                "     --version            Show package version\n"
663                "\n%3$sOptions:%4$s\n"
664                "     --no-pager           Do not pipe output into a pager\n"
665                "     --no-legend          Do not show the headers and footers\n"
666                "     --json=pretty|short|off\n"
667                "                          Generate JSON output\n"
668                "     --system             Show credentials passed to system\n"
669                "     --transcode=base64|unbase64|hex|unhex\n"
670                "                          Transcode credential data\n"
671                "     --newline=auto|yes|no\n"
672                "                          Suffix output with newline\n"
673                "  -p --pretty             Output as SetCredentialEncrypted= line\n"
674                "     --name=NAME          Override filename included in encrypted credential\n"
675                "     --timestamp=TIME     Include specified timestamp in encrypted credential\n"
676                "     --not-after=TIME     Include specified invalidation time in encrypted\n"
677                "                          credential\n"
678                "     --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n"
679                "                          Which keys to encrypt with\n"
680                "  -H                      Shortcut for --with-key=host\n"
681                "  -T                      Shortcut for --with-key=tpm2\n"
682                "     --tpm2-device=PATH\n"
683                "                          Pick TPM2 device\n"
684                "     --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
685                "                          Specify TPM2 PCRs to seal against\n"
686                "  -q --quiet              Suppress output for 'has-tpm2' verb\n"
687                "\nSee the %2$s for details.\n"
688                , program_invocation_short_name
689                , link
690                , ansi_underline(), ansi_normal()
691                , ansi_highlight(), ansi_normal()
692         );
693 
694         return 0;
695 }
696 
parse_argv(int argc,char * argv[])697 static int parse_argv(int argc, char *argv[]) {
698 
699         enum {
700                 ARG_VERSION = 0x100,
701                 ARG_NO_PAGER,
702                 ARG_NO_LEGEND,
703                 ARG_JSON,
704                 ARG_SYSTEM,
705                 ARG_TRANSCODE,
706                 ARG_NEWLINE,
707                 ARG_WITH_KEY,
708                 ARG_TPM2_DEVICE,
709                 ARG_TPM2_PCRS,
710                 ARG_NAME,
711                 ARG_TIMESTAMP,
712                 ARG_NOT_AFTER,
713         };
714 
715         static const struct option options[] = {
716                 { "help",        no_argument,       NULL, 'h'             },
717                 { "version",     no_argument,       NULL, ARG_VERSION     },
718                 { "no-pager",    no_argument,       NULL, ARG_NO_PAGER    },
719                 { "no-legend",   no_argument,       NULL, ARG_NO_LEGEND   },
720                 { "json",        required_argument, NULL, ARG_JSON        },
721                 { "system",      no_argument,       NULL, ARG_SYSTEM      },
722                 { "transcode",   required_argument, NULL, ARG_TRANSCODE   },
723                 { "newline",     required_argument, NULL, ARG_NEWLINE     },
724                 { "pretty",      no_argument,       NULL, 'p'             },
725                 { "with-key",    required_argument, NULL, ARG_WITH_KEY    },
726                 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
727                 { "tpm2-pcrs",   required_argument, NULL, ARG_TPM2_PCRS   },
728                 { "name",        required_argument, NULL, ARG_NAME        },
729                 { "timestamp",   required_argument, NULL, ARG_TIMESTAMP   },
730                 { "not-after",   required_argument, NULL, ARG_NOT_AFTER   },
731                 { "quiet",       no_argument,       NULL, 'q'             },
732                 {}
733         };
734 
735         int c, r;
736 
737         assert(argc >= 0);
738         assert(argv);
739 
740         while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) {
741 
742                 switch (c) {
743 
744                 case 'h':
745                         return verb_help(0, NULL, NULL);
746 
747                 case ARG_VERSION:
748                         return version();
749 
750                 case ARG_NO_PAGER:
751                         arg_pager_flags |= PAGER_DISABLE;
752                         break;
753 
754                 case ARG_NO_LEGEND:
755                         arg_legend = false;
756                         break;
757 
758                 case ARG_JSON:
759                         r = parse_json_argument(optarg, &arg_json_format_flags);
760                         if (r <= 0)
761                                 return r;
762 
763                         break;
764 
765                 case ARG_SYSTEM:
766                         arg_system = true;
767                         break;
768 
769                 case ARG_TRANSCODE:
770                         if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */
771                                 arg_transcode = TRANSCODE_OFF;
772                         else {
773                                 TranscodeMode m;
774 
775                                 m = transcode_mode_from_string(optarg);
776                                 if (m < 0)
777                                         return log_error_errno(m, "Failed to parse transcode mode: %m");
778 
779                                 arg_transcode = m;
780                         }
781 
782                         break;
783 
784                 case ARG_NEWLINE:
785                         if (isempty(optarg) || streq(optarg, "auto"))
786                                 arg_newline = -1;
787                         else {
788                                 bool b;
789 
790                                 r = parse_boolean_argument("--newline=", optarg, &b);
791                                 if (r < 0)
792                                         return r;
793 
794                                 arg_newline = b;
795                         }
796                         break;
797 
798                 case 'p':
799                         arg_pretty = true;
800                         break;
801 
802                 case ARG_WITH_KEY:
803                         if (isempty(optarg) || streq(optarg, "auto"))
804                                 arg_with_key = _CRED_AUTO;
805                         else if (streq(optarg, "auto-initrd"))
806                                 arg_with_key = _CRED_AUTO_INITRD;
807                         else if (streq(optarg, "host"))
808                                 arg_with_key = CRED_AES256_GCM_BY_HOST;
809                         else if (streq(optarg, "tpm2"))
810                                 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
811                         else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
812                                 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
813                         else if (streq(optarg, "tpm2-absent"))
814                                 arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
815                         else
816                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
817 
818                         break;
819 
820                 case 'H':
821                         arg_with_key = CRED_AES256_GCM_BY_HOST;
822                         break;
823 
824                 case 'T':
825                         arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
826                         break;
827 
828                 case ARG_TPM2_DEVICE:
829                         if (streq(optarg, "list"))
830                                 return tpm2_list_devices();
831 
832                         arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
833                         break;
834 
835                 case ARG_TPM2_PCRS:
836                         if (isempty(optarg)) {
837                                 arg_tpm2_pcr_mask = 0;
838                                 break;
839                         }
840 
841                         uint32_t mask;
842                         r = tpm2_parse_pcrs(optarg, &mask);
843                         if (r < 0)
844                                 return r;
845 
846                         if (arg_tpm2_pcr_mask == UINT32_MAX)
847                                 arg_tpm2_pcr_mask = mask;
848                         else
849                                 arg_tpm2_pcr_mask |= mask;
850 
851                         break;
852 
853                 case ARG_NAME:
854                         if (isempty(optarg)) {
855                                 arg_name = NULL;
856                                 arg_name_any = true;
857                                 break;
858                         }
859 
860                         if (!credential_name_valid(optarg))
861                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg);
862 
863                         arg_name = optarg;
864                         arg_name_any = false;
865                         break;
866 
867                 case ARG_TIMESTAMP:
868                         r = parse_timestamp(optarg, &arg_timestamp);
869                         if (r < 0)
870                                 return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
871 
872                         break;
873 
874                 case ARG_NOT_AFTER:
875                         r = parse_timestamp(optarg, &arg_not_after);
876                         if (r < 0)
877                                 return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg);
878 
879                         break;
880 
881                 case 'q':
882                         arg_quiet = true;
883                         break;
884 
885                 case '?':
886                         return -EINVAL;
887 
888                 default:
889                         assert_not_reached();
890                 }
891         }
892 
893         if (arg_tpm2_pcr_mask == UINT32_MAX)
894                 arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
895 
896         return 1;
897 }
898 
creds_main(int argc,char * argv[])899 static int creds_main(int argc, char *argv[]) {
900 
901         static const Verb verbs[] = {
902                 { "list",     VERB_ANY, 1,        VERB_DEFAULT, verb_list     },
903                 { "cat",      2,        VERB_ANY, 0,            verb_cat      },
904                 { "encrypt",  3,        3,        0,            verb_encrypt  },
905                 { "decrypt",  2,        3,        0,            verb_decrypt  },
906                 { "setup",    VERB_ANY, 1,        0,            verb_setup    },
907                 { "help",     VERB_ANY, 1,        0,            verb_help     },
908                 { "has-tpm2", VERB_ANY, 1,        0,            verb_has_tpm2 },
909                 {}
910         };
911 
912         return dispatch_verb(argc, argv, verbs, NULL);
913 }
914 
run(int argc,char * argv[])915 static int run(int argc, char *argv[]) {
916         int r;
917 
918         log_setup();
919 
920         r = parse_argv(argc, argv);
921         if (r <= 0)
922                 return r;
923 
924         return creds_main(argc, argv);
925 }
926 
927 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
928