1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "creds-util.h"
7 #include "dirent-util.h"
10 #include "format-table.h"
11 #include "hexdecoct.h"
14 #include "main-func.h"
15 #include "memory-util.h"
16 #include "missing_magic.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"
27 typedef enum TranscodeMode
{
34 _TRANSCODE_INVALID
= -EINVAL
,
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
= SD_ID128_NULL
;
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;
52 static const char* transcode_mode_table
[_TRANSCODE_MAX
] = {
53 [TRANSCODE_OFF
] = "off",
54 [TRANSCODE_BASE64
] = "base64",
55 [TRANSCODE_UNBASE64
] = "unbase64",
56 [TRANSCODE_HEX
] = "hex",
57 [TRANSCODE_UNHEX
] = "unhex",
60 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode
, TranscodeMode
);
62 static int open_credential_directory(DIR **ret
) {
63 _cleanup_free_
char *j
= NULL
;
69 _cleanup_free_
char *cd
= NULL
;
71 r
= getenv_for_pid(1, "CREDENTIALS_DIRECTORY", &cd
);
77 if (!path_is_absolute(cd
) || !path_is_normalized(cd
))
80 j
= path_join("/proc/1/root", cd
);
86 r
= get_credentials_dir(&p
);
99 static int verb_list(int argc
, char **argv
, void *userdata
) {
100 _cleanup_(table_unrefp
) Table
*t
= NULL
;
101 _cleanup_(closedirp
) DIR *d
= NULL
;
104 r
= open_credential_directory(&d
);
106 return log_error_errno(r
, "No credentials received. (i.e. $CREDENTIALS_PATH not set or pointing to empty directory.)");
108 return log_error_errno(r
, "Failed to open credentials directory: %m");
110 t
= table_new("name", "secure", "size");
114 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 2), 100);
117 _cleanup_close_
int fd
= -1;
118 const char *secure
, *secure_color
= NULL
;
123 de
= readdir_no_dot(d
);
128 return log_error_errno(errno
, "Failed to read credentials directory: %m");
131 if (!IN_SET(de
->d_type
, DT_REG
, DT_UNKNOWN
))
134 if (!credential_name_valid(de
->d_name
))
137 fd
= openat(dirfd(d
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
139 if (errno
== ENOENT
) /* Vanished by now? */
142 return log_error_errno(errno
, "Failed to open credential '%s': %m", de
->d_name
);
145 if (fstat(fd
, &st
) < 0)
146 return log_error_errno(errno
, "Failed to stat credential '%s': %m", de
->d_name
);
148 if (!S_ISREG(st
.st_mode
))
151 if ((st
.st_mode
& 0377) != 0) {
152 secure
= "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
153 secure_color
= ansi_highlight_red();
155 r
= fd_is_fs_type(fd
, RAMFS_MAGIC
);
157 return log_error_errno(r
, "Failed to determine backing file system of '%s': %m", de
->d_name
);
159 secure
= r
? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
160 secure_color
= r
? ansi_highlight_green() : ansi_highlight_yellow4();
165 TABLE_STRING
, de
->d_name
,
166 TABLE_STRING
, secure
,
167 TABLE_SET_COLOR
, secure_color
,
168 TABLE_SIZE
, (uint64_t) st
.st_size
);
170 return table_log_add_error(r
);
173 if ((arg_json_format_flags
& JSON_FORMAT_OFF
) && table_get_rows(t
) <= 1) {
174 log_info("No credentials");
178 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
181 static int transcode(
185 size_t *ret_output_size
) {
192 assert(ret_output_size
);
194 switch (arg_transcode
) {
196 case TRANSCODE_BASE64
: {
200 l
= base64mem_full(input
, input_size
, 79, &buf
);
205 *ret_output_size
= l
;
209 case TRANSCODE_UNBASE64
:
210 r
= unbase64mem_full(input
, input_size
, true, ret_output
, ret_output_size
);
211 if (r
== -EPIPE
) /* Uneven number of chars */
216 case TRANSCODE_HEX
: {
219 buf
= hexmem(input
, input_size
);
224 *ret_output_size
= input_size
* 2;
228 case TRANSCODE_UNHEX
:
229 r
= unhexmem_full(input
, input_size
, true, ret_output
, ret_output_size
);
230 if (r
== -EPIPE
) /* Uneven number of chars */
236 assert_not_reached();
240 static int print_newline(FILE *f
, const char *data
, size_t l
) {
244 assert(data
|| l
== 0);
246 /* If turned off explicitly, don't print newline */
247 if (arg_newline
== 0)
250 /* If data already has newline, don't print either */
251 if (l
> 0 && data
[l
-1] == '\n')
254 /* Don't bother unless this is a tty */
256 if (fd
>= 0 && isatty(fd
) <= 0)
259 if (fputc('\n', f
) != '\n')
260 return log_error_errno(errno
, "Failed to write trailing newline: %m");
265 static int write_blob(FILE *f
, const void *data
, size_t size
) {
266 _cleanup_(erase_and_freep
) void *transcoded
= NULL
;
269 if (arg_transcode
== TRANSCODE_OFF
&&
270 arg_json_format_flags
!= JSON_FORMAT_OFF
) {
272 _cleanup_(erase_and_freep
) char *suffixed
= NULL
;
273 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
275 if (memchr(data
, 0, size
))
276 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Credential data contains embedded NUL, can't parse as JSON.");
278 suffixed
= memdup_suffix0(data
, size
);
282 r
= json_parse(suffixed
, JSON_PARSE_SENSITIVE
, &v
, NULL
, NULL
);
284 return log_error_errno(r
, "Failed to parse JSON: %m");
286 json_variant_dump(v
, arg_json_format_flags
, f
, NULL
);
290 if (arg_transcode
!= TRANSCODE_OFF
) {
291 r
= transcode(data
, size
, &transcoded
, &size
);
293 return log_error_errno(r
, "Failed to transcode data: %m");
298 if (fwrite(data
, 1, size
, f
) != size
)
299 return log_error_errno(errno
, "Failed to write credential data: %m");
301 r
= print_newline(f
, data
, size
);
306 return log_error_errno(errno
, "Failed to flush output: %m");
311 static int verb_cat(int argc
, char **argv
, void *userdata
) {
312 _cleanup_(closedirp
) DIR *d
= NULL
;
316 r
= open_credential_directory(&d
);
318 return log_error_errno(r
, "No credentials passed.");
320 return log_error_errno(r
, "Failed to open credentials directory: %m");
322 STRV_FOREACH(cn
, strv_skip(argv
, 1)) {
323 _cleanup_(erase_and_freep
) void *data
= NULL
;
326 if (!credential_name_valid(*cn
)) {
327 log_error("Credential name '%s' is not valid.", *cn
);
333 r
= read_full_file_full(
335 UINT64_MAX
, SIZE_MAX
,
336 READ_FULL_FILE_SECURE
|READ_FULL_FILE_WARN_WORLD_READABLE
,
338 (char**) &data
, &size
);
340 log_error_errno(r
, "Failed to read credential '%s': %m", *cn
);
346 r
= write_blob(stdout
, data
, size
);
354 static int verb_encrypt(int argc
, char **argv
, void *userdata
) {
355 _cleanup_free_
char *base64_buf
= NULL
, *fname
= NULL
;
356 _cleanup_(erase_and_freep
) char *plaintext
= NULL
;
357 const char *input_path
, *output_path
, *name
;
358 _cleanup_free_
void *output
= NULL
;
359 size_t plaintext_size
, output_size
;
366 input_path
= empty_or_dash(argv
[1]) ? NULL
: argv
[1];
369 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
);
371 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
);
373 return log_error_errno(r
, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX
);
375 return log_error_errno(r
, "Failed to read plaintext: %m");
377 output_path
= empty_or_dash(argv
[2]) ? NULL
: argv
[2];
383 else if (output_path
) {
384 r
= path_extract_filename(output_path
, &fname
);
386 return log_error_errno(r
, "Failed to extract filename from '%s': %m", output_path
);
387 if (r
== O_DIRECTORY
)
388 return log_error_errno(SYNTHETIC_ERRNO(EISDIR
), "Path '%s' refers to directory, refusing.", output_path
);
392 log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
396 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
398 if (arg_not_after
!= USEC_INFINITY
&& arg_not_after
< timestamp
)
399 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Credential is invalidated before it is valid.");
401 r
= encrypt_credential_and_warn(
408 plaintext
, plaintext_size
,
409 &output
, &output_size
);
413 base64_size
= base64mem_full(output
, output_size
, arg_pretty
? 69 : 79, &base64_buf
);
418 _cleanup_free_
char *escaped
= NULL
, *indented
= NULL
, *j
= NULL
;
421 escaped
= cescape(name
);
426 indented
= strreplace(base64_buf
, "\n", " \\\n ");
430 j
= strjoin("SetCredentialEncrypted=", name
, ": \\\n ", indented
, "\n");
434 free_and_replace(base64_buf
, j
);
438 r
= write_string_file(output_path
, base64_buf
, WRITE_STRING_FILE_ATOMIC
|WRITE_STRING_FILE_CREATE
);
440 r
= write_string_stream(stdout
, base64_buf
, 0);
442 return log_error_errno(r
, "Failed to write result: %m");
447 static int verb_decrypt(int argc
, char **argv
, void *userdata
) {
448 _cleanup_(erase_and_freep
) void *plaintext
= NULL
;
449 _cleanup_free_
char *input
= NULL
, *fname
= NULL
;
450 _cleanup_fclose_
FILE *output_file
= NULL
;
451 const char *input_path
, *output_path
, *name
;
452 size_t input_size
, plaintext_size
;
457 assert(IN_SET(argc
, 2, 3));
459 input_path
= empty_or_dash(argv
[1]) ? NULL
: argv
[1];
462 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
);
464 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
);
466 return log_error_errno(r
, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX
);
468 return log_error_errno(r
, "Failed to read encrypted credential data: %m");
470 output_path
= (argc
< 3 || isempty(argv
[2]) || streq(argv
[2], "-")) ? NULL
: argv
[2];
476 else if (input_path
) {
477 r
= path_extract_filename(input_path
, &fname
);
479 return log_error_errno(r
, "Failed to extract filename from '%s': %m", input_path
);
480 if (r
== O_DIRECTORY
)
481 return log_error_errno(SYNTHETIC_ERRNO(EISDIR
), "Path '%s' refers to directory, refusing.", input_path
);
485 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
489 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
491 r
= decrypt_credential_and_warn(
496 &plaintext
, &plaintext_size
);
501 output_file
= fopen(output_path
, "we");
503 return log_error_errno(errno
, "Failed to create output file '%s': %m", output_path
);
509 r
= write_blob(f
, plaintext
, plaintext_size
);
516 static int verb_setup(int argc
, char **argv
, void *userdata
) {
520 r
= get_credential_host_secret(CREDENTIAL_SECRET_GENERATE
|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED
, NULL
, &size
);
522 return log_error_errno(r
, "Failed to setup credentials host key: %m");
524 log_info("%zu byte credentials host key set up.", size
);
529 static int verb_help(int argc
, char **argv
, void *userdata
) {
530 _cleanup_free_
char *link
= NULL
;
533 r
= terminal_urlify_man("systemd-creds", "1", &link
);
537 printf("%1$s [OPTIONS...] COMMAND ...\n"
538 "\n%5$sDisplay and Process Credentials.%6$s\n"
539 "\n%3$sCommands:%4$s\n"
540 " list Show installed and available versions\n"
541 " cat CREDENTIAL... Show specified credentials\n"
542 " setup Generate credentials host key, if not existing yet\n"
543 " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
544 " ciphertext credential file\n"
545 " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
546 " plaintext credential file\n"
547 " -h --help Show this help\n"
548 " --version Show package version\n"
549 "\n%3$sOptions:%4$s\n"
550 " --no-pager Do not pipe output into a pager\n"
551 " --no-legend Do not show the headers and footers\n"
552 " --json=pretty|short|off\n"
553 " Generate JSON output\n"
554 " --system Show credentials passed to system\n"
555 " --transcode=base64|unbase64|hex|unhex\n"
556 " Transcode credential data\n"
557 " --newline=auto|yes|no\n"
558 " Suffix output with newline\n"
559 " -p --pretty Output as SetCredentialEncrypted= line\n"
560 " --name=NAME Override filename included in encrypted credential\n"
561 " --timestamp=TIME Include specified timestamp in encrypted credential\n"
562 " --not-after=TIME Include specified invalidation time in encrypted\n"
564 " --with-key=host|tpm2|host+tpm2|auto\n"
565 " Which keys to encrypt with\n"
566 " -H Shortcut for --with-key=host\n"
567 " -T Shortcut for --with-key=tpm2\n"
568 " --tpm2-device=PATH\n"
569 " Pick TPM2 device\n"
570 " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
571 " Specify TPM2 PCRs to seal against\n"
572 "\nSee the %2$s for details.\n"
573 , program_invocation_short_name
575 , ansi_underline(), ansi_normal()
576 , ansi_highlight(), ansi_normal()
582 static int parse_argv(int argc
, char *argv
[]) {
600 static const struct option options
[] = {
601 { "help", no_argument
, NULL
, 'h' },
602 { "version", no_argument
, NULL
, ARG_VERSION
},
603 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
604 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
605 { "json", required_argument
, NULL
, ARG_JSON
},
606 { "system", no_argument
, NULL
, ARG_SYSTEM
},
607 { "transcode", required_argument
, NULL
, ARG_TRANSCODE
},
608 { "newline", required_argument
, NULL
, ARG_NEWLINE
},
609 { "pretty", no_argument
, NULL
, 'p' },
610 { "with-key", required_argument
, NULL
, ARG_WITH_KEY
},
611 { "tpm2-device", required_argument
, NULL
, ARG_TPM2_DEVICE
},
612 { "tpm2-pcrs", required_argument
, NULL
, ARG_TPM2_PCRS
},
613 { "name", required_argument
, NULL
, ARG_NAME
},
614 { "timestamp", required_argument
, NULL
, ARG_TIMESTAMP
},
615 { "not-after", required_argument
, NULL
, ARG_NOT_AFTER
},
624 while ((c
= getopt_long(argc
, argv
, "hHTp", options
, NULL
)) >= 0) {
629 return verb_help(0, NULL
, NULL
);
635 arg_pager_flags
|= PAGER_DISABLE
;
643 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
654 if (parse_boolean(optarg
) == 0) /* If specified as "false", turn transcoding off */
655 arg_transcode
= TRANSCODE_OFF
;
659 m
= transcode_mode_from_string(optarg
);
661 return log_error_errno(m
, "Failed to parse transcode mode: %m");
669 if (isempty(optarg
) || streq(optarg
, "auto"))
674 r
= parse_boolean_argument("--newline=", optarg
, &b
);
687 if (isempty(optarg
) || streq(optarg
, "auto"))
688 arg_with_key
= SD_ID128_NULL
;
689 else if (streq(optarg
, "host"))
690 arg_with_key
= CRED_AES256_GCM_BY_HOST
;
691 else if (streq(optarg
, "tpm2"))
692 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC
;
693 else if (STR_IN_SET(optarg
, "host+tpm2", "tpm2+host"))
694 arg_with_key
= CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC
;
696 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unknown key type: %s", optarg
);
701 arg_with_key
= CRED_AES256_GCM_BY_HOST
;
705 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC
;
708 case ARG_TPM2_DEVICE
:
709 if (streq(optarg
, "list"))
710 return tpm2_list_devices();
712 arg_tpm2_device
= streq(optarg
, "auto") ? NULL
: optarg
;
716 if (isempty(optarg
)) {
717 arg_tpm2_pcr_mask
= 0;
722 r
= tpm2_parse_pcrs(optarg
, &mask
);
726 if (arg_tpm2_pcr_mask
== UINT32_MAX
)
727 arg_tpm2_pcr_mask
= mask
;
729 arg_tpm2_pcr_mask
|= mask
;
734 if (isempty(optarg
)) {
740 if (!credential_name_valid(optarg
))
741 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid credential name: %s", optarg
);
744 arg_name_any
= false;
748 r
= parse_timestamp(optarg
, &arg_timestamp
);
750 return log_error_errno(r
, "Failed to parse timestamp: %s", optarg
);
755 r
= parse_timestamp(optarg
, &arg_not_after
);
757 return log_error_errno(r
, "Failed to parse --not-after= timestamp: %s", optarg
);
765 assert_not_reached();
769 if (arg_tpm2_pcr_mask
== UINT32_MAX
)
770 arg_tpm2_pcr_mask
= TPM2_PCR_MASK_DEFAULT
;
775 static int creds_main(int argc
, char *argv
[]) {
777 static const Verb verbs
[] = {
778 { "list", VERB_ANY
, 1, VERB_DEFAULT
, verb_list
},
779 { "cat", 2, VERB_ANY
, 0, verb_cat
},
780 { "encrypt", 3, 3, 0, verb_encrypt
},
781 { "decrypt", 2, 3, 0, verb_decrypt
},
782 { "setup", VERB_ANY
, 1, 0, verb_setup
},
783 { "help", VERB_ANY
, 1, 0, verb_help
},
787 return dispatch_verb(argc
, argv
, verbs
, NULL
);
790 static int run(int argc
, char *argv
[]) {
795 r
= parse_argv(argc
, argv
);
799 return creds_main(argc
, argv
);
802 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);