1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "bus-polkit.h"
8 #include "creds-util.h"
9 #include "dirent-util.h"
12 #include "format-table.h"
13 #include "hexdecoct.h"
16 #include "main-func.h"
17 #include "memory-util.h"
18 #include "missing_magic.h"
20 #include "parse-argument.h"
21 #include "pretty-print.h"
22 #include "process-util.h"
23 #include "stat-util.h"
24 #include "string-table.h"
25 #include "terminal-util.h"
27 #include "tpm2-util.h"
28 #include "user-util.h"
30 #include "varlink-io.systemd.Credentials.h"
33 typedef enum TranscodeMode
{
40 _TRANSCODE_INVALID
= -EINVAL
,
43 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
44 static PagerFlags arg_pager_flags
= 0;
45 static bool arg_legend
= true;
46 static bool arg_system
= false;
47 static TranscodeMode arg_transcode
= TRANSCODE_OFF
;
48 static int arg_newline
= -1;
49 static sd_id128_t arg_with_key
= _CRED_AUTO
;
50 static const char *arg_tpm2_device
= NULL
;
51 static uint32_t arg_tpm2_pcr_mask
= UINT32_MAX
;
52 static char *arg_tpm2_public_key
= NULL
;
53 static uint32_t arg_tpm2_public_key_pcr_mask
= UINT32_MAX
;
54 static char *arg_tpm2_signature
= NULL
;
55 static const char *arg_name
= NULL
;
56 static bool arg_name_any
= false;
57 static usec_t arg_timestamp
= USEC_INFINITY
;
58 static usec_t arg_not_after
= USEC_INFINITY
;
59 static bool arg_pretty
= false;
60 static bool arg_quiet
= false;
61 static bool arg_varlink
= false;
63 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key
, freep
);
64 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature
, freep
);
66 static const char* transcode_mode_table
[_TRANSCODE_MAX
] = {
67 [TRANSCODE_OFF
] = "off",
68 [TRANSCODE_BASE64
] = "base64",
69 [TRANSCODE_UNBASE64
] = "unbase64",
70 [TRANSCODE_HEX
] = "hex",
71 [TRANSCODE_UNHEX
] = "unhex",
74 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode
, TranscodeMode
);
76 static int open_credential_directory(
79 const char **ret_prefix
) {
88 /* PID 1 ensures that system credentials are always accessible under the same fixed path. It
89 * will create symlinks if necessary to guarantee that. */
91 ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY
:
92 SYSTEM_CREDENTIALS_DIRECTORY
;
94 /* Otherwise take the dirs from the env vars we got passed */
95 r
= (encrypted
? get_encrypted_credentials_dir
: get_credentials_dir
)(&p
);
96 if (r
== -ENXIO
) /* No environment variable? */
99 return log_error_errno(r
, "Failed to get credentials directory: %m");
104 /* No such dir? Then no creds where passed. (We conditionalize this on arg_system, since for
105 * the per-service case a non-existing path would indicate an issue since the env var would
106 * be set incorrectly in that case.) */
107 if (arg_system
&& errno
== ENOENT
)
110 return log_error_errno(errno
, "Failed to open credentials directory '%s': %m", p
);
129 static int add_credentials_to_table(Table
*t
, bool encrypted
) {
130 _cleanup_closedir_
DIR *d
= NULL
;
136 r
= open_credential_directory(encrypted
, &d
, &prefix
);
140 return 0; /* No creds dir set */
143 _cleanup_free_
char *j
= NULL
;
144 const char *secure
, *secure_color
= NULL
;
145 _cleanup_close_
int fd
= -EBADF
;
150 de
= readdir_no_dot(d
);
155 return log_error_errno(errno
, "Failed to read credentials directory: %m");
158 if (!IN_SET(de
->d_type
, DT_REG
, DT_UNKNOWN
))
161 if (!credential_name_valid(de
->d_name
))
164 fd
= openat(dirfd(d
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
166 if (errno
== ENOENT
) /* Vanished by now? */
169 return log_error_errno(errno
, "Failed to open credential '%s': %m", de
->d_name
);
172 if (fstat(fd
, &st
) < 0)
173 return log_error_errno(errno
, "Failed to stat credential '%s': %m", de
->d_name
);
175 if (!S_ISREG(st
.st_mode
))
179 secure
= "encrypted";
180 secure_color
= ansi_highlight_green();
181 } else if ((st
.st_mode
& 0377) != 0) {
182 secure
= "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
183 secure_color
= ansi_highlight_red();
185 r
= fd_is_fs_type(fd
, RAMFS_MAGIC
);
187 return log_error_errno(r
, "Failed to determine backing file system of '%s': %m", de
->d_name
);
189 secure
= r
> 0 ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
190 secure_color
= r
> 0 ? ansi_highlight_green() : ansi_highlight_yellow4();
193 j
= path_join(prefix
, de
->d_name
);
199 TABLE_STRING
, de
->d_name
,
200 TABLE_STRING
, secure
,
201 TABLE_SET_COLOR
, secure_color
,
202 TABLE_SIZE
, (uint64_t) st
.st_size
,
205 return table_log_add_error(r
);
208 return 1; /* Creds dir set */
211 static int verb_list(int argc
, char **argv
, void *userdata
) {
212 _cleanup_(table_unrefp
) Table
*t
= NULL
;
215 t
= table_new("name", "secure", "size", "path");
219 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 2), 100);
221 r
= add_credentials_to_table(t
, /* encrypted= */ true);
225 q
= add_credentials_to_table(t
, /* encrypted= */ false);
229 if (r
== 0 && q
== 0) {
231 return log_error_errno(SYNTHETIC_ERRNO(ENXIO
), "No credentials passed to system.");
233 return log_error_errno(SYNTHETIC_ERRNO(ENXIO
), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
236 if (FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
) && table_isempty(t
)) {
237 log_info("No credentials");
241 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
244 static int transcode(
248 size_t *ret_output_size
) {
255 assert(ret_output_size
);
257 switch (arg_transcode
) {
259 case TRANSCODE_BASE64
: {
263 l
= base64mem_full(input
, input_size
, 79, &buf
);
268 *ret_output_size
= l
;
272 case TRANSCODE_UNBASE64
:
273 r
= unbase64mem_full(input
, input_size
, true, ret_output
, ret_output_size
);
274 if (r
== -EPIPE
) /* Uneven number of chars */
279 case TRANSCODE_HEX
: {
282 buf
= hexmem(input
, input_size
);
287 *ret_output_size
= input_size
* 2;
291 case TRANSCODE_UNHEX
:
292 r
= unhexmem_full(input
, input_size
, true, ret_output
, ret_output_size
);
293 if (r
== -EPIPE
) /* Uneven number of chars */
299 assert_not_reached();
303 static int print_newline(FILE *f
, const char *data
, size_t l
) {
307 assert(data
|| l
== 0);
309 /* If turned off explicitly, don't print newline */
310 if (arg_newline
== 0)
313 /* If data already has newline, don't print either */
314 if (l
> 0 && data
[l
-1] == '\n')
317 /* Don't bother unless this is a tty */
319 if (fd
>= 0 && !isatty_safe(fd
))
322 if (fputc('\n', f
) != '\n')
323 return log_error_errno(errno
, "Failed to write trailing newline: %m");
328 static int write_blob(FILE *f
, const void *data
, size_t size
) {
329 _cleanup_(erase_and_freep
) void *transcoded
= NULL
;
332 if (arg_transcode
== TRANSCODE_OFF
&&
333 arg_json_format_flags
!= JSON_FORMAT_OFF
) {
334 _cleanup_(erase_and_freep
) char *suffixed
= NULL
;
335 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
337 r
= make_cstring(data
, size
, MAKE_CSTRING_REFUSE_TRAILING_NUL
, &suffixed
);
339 return log_error_errno(r
, "Unable to convert binary string to C string: %m");
341 r
= json_parse(suffixed
, JSON_PARSE_SENSITIVE
, &v
, NULL
, NULL
);
343 return log_error_errno(r
, "Failed to parse JSON: %m");
345 json_variant_dump(v
, arg_json_format_flags
, f
, NULL
);
349 if (arg_transcode
!= TRANSCODE_OFF
) {
350 r
= transcode(data
, size
, &transcoded
, &size
);
352 return log_error_errno(r
, "Failed to transcode data: %m");
357 if (fwrite(data
, 1, size
, f
) != size
)
358 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to write credential data.");
360 r
= print_newline(f
, data
, size
);
364 r
= fflush_and_check(f
);
366 return log_error_errno(r
, "Failed to flush output: %m");
371 static int verb_cat(int argc
, char **argv
, void *userdata
) {
375 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
377 STRV_FOREACH(cn
, strv_skip(argv
, 1)) {
378 _cleanup_(erase_and_freep
) void *data
= NULL
;
382 if (!credential_name_valid(*cn
)) {
383 log_error("Credential name '%s' is not valid.", *cn
);
389 /* Look both in regular and in encrypted credentials */
390 for (encrypted
= 0; encrypted
< 2; encrypted
++) {
391 _cleanup_closedir_
DIR *d
= NULL
;
393 r
= open_credential_directory(encrypted
, &d
, NULL
);
395 return log_error_errno(r
, "Failed to open credentials directory: %m");
396 if (!d
) /* Not set */
399 r
= read_full_file_full(
401 UINT64_MAX
, SIZE_MAX
,
402 READ_FULL_FILE_SECURE
|READ_FULL_FILE_WARN_WORLD_READABLE
,
404 (char**) &data
, &size
);
405 if (r
== -ENOENT
) /* Not found */
407 if (r
>= 0) /* Found */
410 log_error_errno(r
, "Failed to read credential '%s': %m", *cn
);
415 if (encrypted
>= 2) { /* Found nowhere */
416 log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Credential '%s' not set.", *cn
);
424 _cleanup_(iovec_done_erase
) struct iovec plaintext
= {};
426 r
= decrypt_credential_and_warn(
432 &IOVEC_MAKE(data
, size
),
433 CREDENTIAL_ANY_SCOPE
,
438 erase_and_free(data
);
439 data
= TAKE_PTR(plaintext
.iov_base
);
440 size
= plaintext
.iov_len
;
443 r
= write_blob(stdout
, data
, size
);
451 static int verb_encrypt(int argc
, char **argv
, void *userdata
) {
452 _cleanup_(iovec_done_erase
) struct iovec plaintext
= {}, output
= {};
453 _cleanup_free_
char *base64_buf
= NULL
, *fname
= NULL
;
454 const char *input_path
, *output_path
, *name
;
461 input_path
= empty_or_dash(argv
[1]) ? NULL
: argv
[1];
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
, (char**) &plaintext
.iov_base
, &plaintext
.iov_len
);
466 r
= read_full_stream_full(stdin
, NULL
, UINT64_MAX
, CREDENTIAL_SIZE_MAX
, READ_FULL_FILE_SECURE
|READ_FULL_FILE_FAIL_WHEN_LARGER
, (char**) &plaintext
.iov_base
, &plaintext
.iov_len
);
468 return log_error_errno(r
, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX
);
470 return log_error_errno(r
, "Failed to read plaintext: %m");
472 output_path
= empty_or_dash(argv
[2]) ? NULL
: argv
[2];
478 else if (output_path
) {
479 r
= path_extract_filename(output_path
, &fname
);
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
);
487 log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
491 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
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.");
496 r
= encrypt_credential_and_warn(
504 arg_tpm2_public_key_pcr_mask
,
505 /* uid= */ UID_INVALID
,
512 base64_size
= base64mem_full(output
.iov_base
, output
.iov_len
, arg_pretty
? 69 : 79, &base64_buf
);
516 /* Pretty print makes sense only if we're printing stuff to stdout
517 * and if a cred name is provided via --name= (since we can't use
518 * the output file name as the cred name here) */
519 if (arg_pretty
&& !output_path
&& name
) {
520 _cleanup_free_
char *escaped
= NULL
, *indented
= NULL
, *j
= NULL
;
522 escaped
= cescape(name
);
526 indented
= strreplace(base64_buf
, "\n", " \\\n ");
530 j
= strjoin("SetCredentialEncrypted=", escaped
, ": \\\n ", indented
, "\n");
534 free_and_replace(base64_buf
, j
);
538 r
= write_string_file(output_path
, base64_buf
, WRITE_STRING_FILE_ATOMIC
|WRITE_STRING_FILE_CREATE
);
540 r
= write_string_stream(stdout
, base64_buf
, 0);
542 return log_error_errno(r
, "Failed to write result: %m");
547 static int verb_decrypt(int argc
, char **argv
, void *userdata
) {
548 _cleanup_(iovec_done_erase
) struct iovec input
= {}, plaintext
= {};
549 _cleanup_free_
char *fname
= NULL
;
550 _cleanup_fclose_
FILE *output_file
= NULL
;
551 const char *input_path
, *output_path
, *name
;
556 assert(IN_SET(argc
, 2, 3));
558 input_path
= empty_or_dash(argv
[1]) ? NULL
: argv
[1];
561 r
= read_full_file_full(AT_FDCWD
, argv
[1], UINT64_MAX
, CREDENTIAL_ENCRYPTED_SIZE_MAX
, READ_FULL_FILE_UNBASE64
|READ_FULL_FILE_FAIL_WHEN_LARGER
, NULL
, (char**) &input
, &input
.iov_len
);
563 r
= read_full_stream_full(stdin
, NULL
, UINT64_MAX
, CREDENTIAL_ENCRYPTED_SIZE_MAX
, READ_FULL_FILE_UNBASE64
|READ_FULL_FILE_FAIL_WHEN_LARGER
, (char**) &input
, &input
.iov_len
);
565 return log_error_errno(r
, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX
);
567 return log_error_errno(r
, "Failed to read encrypted credential data: %m");
569 output_path
= (argc
< 3 || empty_or_dash(argv
[2])) ? NULL
: argv
[2];
575 else if (input_path
) {
576 r
= path_extract_filename(input_path
, &fname
);
578 return log_error_errno(r
, "Failed to extract filename from '%s': %m", input_path
);
579 if (r
== O_DIRECTORY
)
580 return log_error_errno(SYNTHETIC_ERRNO(EISDIR
), "Path '%s' refers to directory, refusing.", input_path
);
584 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
588 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
590 r
= decrypt_credential_and_warn(
595 /* uid= */ UID_INVALID
,
603 output_file
= fopen(output_path
, "we");
605 return log_error_errno(errno
, "Failed to create output file '%s': %m", output_path
);
611 r
= write_blob(f
, plaintext
.iov_base
, plaintext
.iov_len
);
618 static int verb_setup(int argc
, char **argv
, void *userdata
) {
619 _cleanup_(iovec_done_erase
) struct iovec host_key
= {};
622 r
= get_credential_host_secret(CREDENTIAL_SECRET_GENERATE
|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED
, &host_key
);
624 return log_error_errno(r
, "Failed to setup credentials host key: %m");
626 log_info("%zu byte credentials host key set up.", host_key
.iov_len
);
631 static int verb_has_tpm2(int argc
, char **argv
, void *userdata
) {
637 if (s
== TPM2_SUPPORT_FULL
)
639 else if (s
== TPM2_SUPPORT_NONE
)
644 printf("%sfirmware\n"
649 plus_minus(s
& TPM2_SUPPORT_FIRMWARE
),
650 plus_minus(s
& TPM2_SUPPORT_DRIVER
),
651 plus_minus(s
& TPM2_SUPPORT_SYSTEM
),
652 plus_minus(s
& TPM2_SUPPORT_SUBSYSTEM
),
653 plus_minus(s
& TPM2_SUPPORT_LIBRARIES
));
656 /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values
657 * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than
658 * what is there, acknowledging the fact that for process exit statuses it is customary to return
659 * zero (EXIT_FAILURE) when all is good, instead of all being bad. */
660 return ~s
& TPM2_SUPPORT_FULL
;
663 static int verb_help(int argc
, char **argv
, void *userdata
) {
664 _cleanup_free_
char *link
= NULL
;
667 r
= terminal_urlify_man("systemd-creds", "1", &link
);
671 printf("%1$s [OPTIONS...] COMMAND ...\n"
672 "\n%5$sDisplay and Process Credentials.%6$s\n"
673 "\n%3$sCommands:%4$s\n"
674 " list Show installed and available versions\n"
675 " cat CREDENTIAL... Show specified credentials\n"
676 " setup Generate credentials host key, if not existing yet\n"
677 " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
678 " ciphertext credential file\n"
679 " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
680 " plaintext credential file\n"
681 " has-tpm2 Report whether TPM2 support is available\n"
682 " -h --help Show this help\n"
683 " --version Show package version\n"
684 "\n%3$sOptions:%4$s\n"
685 " --no-pager Do not pipe output into a pager\n"
686 " --no-legend Do not show the headers and footers\n"
687 " --json=pretty|short|off\n"
688 " Generate JSON output\n"
689 " --system Show credentials passed to system\n"
690 " --transcode=base64|unbase64|hex|unhex\n"
691 " Transcode credential data\n"
692 " --newline=auto|yes|no\n"
693 " Suffix output with newline\n"
694 " -p --pretty Output as SetCredentialEncrypted= line\n"
695 " --name=NAME Override filename included in encrypted credential\n"
696 " --timestamp=TIME Include specified timestamp in encrypted credential\n"
697 " --not-after=TIME Include specified invalidation time in encrypted\n"
699 " --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n"
700 " Which keys to encrypt with\n"
701 " -H Shortcut for --with-key=host\n"
702 " -T Shortcut for --with-key=tpm2\n"
703 " --tpm2-device=PATH\n"
704 " Pick TPM2 device\n"
705 " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
706 " Specify TPM2 PCRs to seal against (fixed hash)\n"
707 " --tpm2-public-key=PATH\n"
708 " Specify PEM certificate to seal against\n"
709 " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
710 " Specify TPM2 PCRs to seal against (public key)\n"
711 " --tpm2-signature=PATH\n"
712 " Specify signature for public key PCR policy\n"
713 " -q --quiet Suppress output for 'has-tpm2' verb\n"
714 "\nSee the %2$s for details.\n"
715 , program_invocation_short_name
717 , ansi_underline(), ansi_normal()
718 , ansi_highlight(), ansi_normal()
724 static int parse_argv(int argc
, char *argv
[]) {
738 ARG_TPM2_PUBLIC_KEY_PCRS
,
745 static const struct option options
[] = {
746 { "help", no_argument
, NULL
, 'h' },
747 { "version", no_argument
, NULL
, ARG_VERSION
},
748 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
749 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
750 { "json", required_argument
, NULL
, ARG_JSON
},
751 { "system", no_argument
, NULL
, ARG_SYSTEM
},
752 { "transcode", required_argument
, NULL
, ARG_TRANSCODE
},
753 { "newline", required_argument
, NULL
, ARG_NEWLINE
},
754 { "pretty", no_argument
, NULL
, 'p' },
755 { "with-key", required_argument
, NULL
, ARG_WITH_KEY
},
756 { "tpm2-device", required_argument
, NULL
, ARG_TPM2_DEVICE
},
757 { "tpm2-pcrs", required_argument
, NULL
, ARG_TPM2_PCRS
},
758 { "tpm2-public-key", required_argument
, NULL
, ARG_TPM2_PUBLIC_KEY
},
759 { "tpm2-public-key-pcrs", required_argument
, NULL
, ARG_TPM2_PUBLIC_KEY_PCRS
},
760 { "tpm2-signature", required_argument
, NULL
, ARG_TPM2_SIGNATURE
},
761 { "name", required_argument
, NULL
, ARG_NAME
},
762 { "timestamp", required_argument
, NULL
, ARG_TIMESTAMP
},
763 { "not-after", required_argument
, NULL
, ARG_NOT_AFTER
},
764 { "quiet", no_argument
, NULL
, 'q' },
773 while ((c
= getopt_long(argc
, argv
, "hHTpq", options
, NULL
)) >= 0) {
778 return verb_help(0, NULL
, NULL
);
784 arg_pager_flags
|= PAGER_DISABLE
;
792 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
803 if (parse_boolean(optarg
) == 0) /* If specified as "false", turn transcoding off */
804 arg_transcode
= TRANSCODE_OFF
;
808 m
= transcode_mode_from_string(optarg
);
810 return log_error_errno(m
, "Failed to parse transcode mode: %m");
818 if (isempty(optarg
) || streq(optarg
, "auto"))
821 r
= parse_boolean_argument("--newline=", optarg
, NULL
);
834 if (isempty(optarg
) || streq(optarg
, "auto"))
835 arg_with_key
= _CRED_AUTO
;
836 else if (streq(optarg
, "auto-initrd"))
837 arg_with_key
= _CRED_AUTO_INITRD
;
838 else if (streq(optarg
, "host"))
839 arg_with_key
= CRED_AES256_GCM_BY_HOST
;
840 else if (streq(optarg
, "tpm2"))
841 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC
;
842 else if (streq(optarg
, "tpm2-with-public-key"))
843 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK
;
844 else if (STR_IN_SET(optarg
, "host+tpm2", "tpm2+host"))
845 arg_with_key
= CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC
;
846 else if (STR_IN_SET(optarg
, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
847 arg_with_key
= CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK
;
848 else if (STR_IN_SET(optarg
, "null", "tpm2-absent"))
849 arg_with_key
= CRED_AES256_GCM_BY_NULL
;
851 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unknown key type: %s", optarg
);
856 arg_with_key
= CRED_AES256_GCM_BY_HOST
;
860 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC
;
863 case ARG_TPM2_DEVICE
:
864 if (streq(optarg
, "list"))
865 return tpm2_list_devices();
867 arg_tpm2_device
= streq(optarg
, "auto") ? NULL
: optarg
;
870 case ARG_TPM2_PCRS
: /* For fixed hash PCR policies only */
871 r
= tpm2_parse_pcr_argument_to_mask(optarg
, &arg_tpm2_pcr_mask
);
877 case ARG_TPM2_PUBLIC_KEY
:
878 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_tpm2_public_key
);
884 case ARG_TPM2_PUBLIC_KEY_PCRS
: /* For public key PCR policies only */
885 r
= tpm2_parse_pcr_argument_to_mask(optarg
, &arg_tpm2_public_key_pcr_mask
);
891 case ARG_TPM2_SIGNATURE
:
892 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_tpm2_signature
);
899 if (isempty(optarg
)) {
905 if (!credential_name_valid(optarg
))
906 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid credential name: %s", optarg
);
909 arg_name_any
= false;
913 r
= parse_timestamp(optarg
, &arg_timestamp
);
915 return log_error_errno(r
, "Failed to parse timestamp: %s", optarg
);
920 r
= parse_timestamp(optarg
, &arg_not_after
);
922 return log_error_errno(r
, "Failed to parse --not-after= timestamp: %s", optarg
);
934 assert_not_reached();
938 if (arg_tpm2_pcr_mask
== UINT32_MAX
)
939 arg_tpm2_pcr_mask
= TPM2_PCR_MASK_DEFAULT
;
940 if (arg_tpm2_public_key_pcr_mask
== UINT32_MAX
)
941 arg_tpm2_public_key_pcr_mask
= UINT32_C(1) << TPM2_PCR_KERNEL_BOOT
;
943 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
945 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
951 static int creds_main(int argc
, char *argv
[]) {
953 static const Verb verbs
[] = {
954 { "list", VERB_ANY
, 1, VERB_DEFAULT
, verb_list
},
955 { "cat", 2, VERB_ANY
, 0, verb_cat
},
956 { "encrypt", 3, 3, 0, verb_encrypt
},
957 { "decrypt", 2, 3, 0, verb_decrypt
},
958 { "setup", VERB_ANY
, 1, 0, verb_setup
},
959 { "help", VERB_ANY
, 1, 0, verb_help
},
960 { "has-tpm2", VERB_ANY
, 1, 0, verb_has_tpm2
},
964 return dispatch_verb(argc
, argv
, verbs
, NULL
);
967 typedef struct MethodEncryptParameters
{
973 } MethodEncryptParameters
;
975 static void method_encrypt_parameters_done(MethodEncryptParameters
*p
) {
978 iovec_done_erase(&p
->data
);
981 static int vl_method_encrypt(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
983 static const JsonDispatch dispatch_table
[] = {
984 { "name", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodEncryptParameters
, name
), 0 },
985 { "text", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodEncryptParameters
, text
), 0 },
986 { "data", JSON_VARIANT_STRING
, json_dispatch_unbase64_iovec
, offsetof(MethodEncryptParameters
, data
), 0 },
987 { "timestamp", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_uint64
, offsetof(MethodEncryptParameters
, timestamp
), 0 },
988 { "notAfter", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_uint64
, offsetof(MethodEncryptParameters
, not_after
), 0 },
989 VARLINK_DISPATCH_POLKIT_FIELD
,
992 _cleanup_(method_encrypt_parameters_done
) MethodEncryptParameters p
= {
993 .timestamp
= UINT64_MAX
,
994 .not_after
= UINT64_MAX
,
996 _cleanup_(iovec_done
) struct iovec output
= {};
997 Hashmap
**polkit_registry
= ASSERT_PTR(userdata
);
1002 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
1006 if (p
.name
&& !credential_name_valid(p
.name
))
1007 return varlink_error_invalid_parameter_name(link
, "name");
1008 /* Specifying both or neither the text string and the binary data is not allowed */
1009 if (!!p
.text
== !!p
.data
.iov_base
)
1010 return varlink_error_invalid_parameter_name(link
, "data");
1011 if (p
.timestamp
== UINT64_MAX
)
1012 p
.timestamp
= now(CLOCK_REALTIME
);
1013 if (p
.not_after
!= UINT64_MAX
&& p
.not_after
< p
.timestamp
)
1014 return varlink_error_invalid_parameter_name(link
, "notAfter");
1016 r
= varlink_verify_polkit_async(
1019 "io.systemd.credentials.encrypt",
1020 /* details= */ NULL
,
1021 /* good_user= */ UID_INVALID
,
1026 r
= encrypt_credential_and_warn(
1033 arg_tpm2_public_key
,
1034 arg_tpm2_public_key_pcr_mask
,
1035 /* uid= */ UID_INVALID
,
1036 p
.text
? &IOVEC_MAKE_STRING(p
.text
) : &p
.data
,
1042 _cleanup_(json_variant_unrefp
) JsonVariant
*reply
= NULL
;
1044 r
= json_build(&reply
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("blob", &output
)));
1048 /* Let's also mark the (theoretically encrypted) reply as sensitive, in case the NULL encryption scheme was used. */
1049 json_variant_sensitive(reply
);
1051 return varlink_reply(link
, reply
);
1054 typedef struct MethodDecryptParameters
{
1058 } MethodDecryptParameters
;
1060 static void method_decrypt_parameters_done(MethodDecryptParameters
*p
) {
1063 iovec_done_erase(&p
->blob
);
1066 static int vl_method_decrypt(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
1068 static const JsonDispatch dispatch_table
[] = {
1069 { "name", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodDecryptParameters
, name
), 0 },
1070 { "blob", JSON_VARIANT_STRING
, json_dispatch_unbase64_iovec
, offsetof(MethodDecryptParameters
, blob
), JSON_MANDATORY
},
1071 { "timestamp", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_uint64
, offsetof(MethodDecryptParameters
, timestamp
), 0 },
1072 VARLINK_DISPATCH_POLKIT_FIELD
,
1075 _cleanup_(method_decrypt_parameters_done
) MethodDecryptParameters p
= {
1076 .timestamp
= UINT64_MAX
,
1078 _cleanup_(iovec_done_erase
) struct iovec output
= {};
1079 Hashmap
**polkit_registry
= ASSERT_PTR(userdata
);
1084 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
1088 if (p
.name
&& !credential_name_valid(p
.name
))
1089 return varlink_error_invalid_parameter_name(link
, "name");
1090 if (p
.timestamp
== UINT64_MAX
)
1091 p
.timestamp
= now(CLOCK_REALTIME
);
1093 r
= varlink_verify_polkit_async(
1096 "io.systemd.credentials.decrypt",
1097 /* details= */ NULL
,
1098 /* good_user= */ UID_INVALID
,
1103 r
= decrypt_credential_and_warn(
1108 /* uid= */ UID_INVALID
,
1113 return varlink_error(link
, "io.systemd.Credentials.BadFormat", NULL
);
1115 return varlink_error(link
, "io.systemd.Credentials.NameMismatch", NULL
);
1117 return varlink_error(link
, "io.systemd.Credentials.TimeMismatch", NULL
);
1121 _cleanup_(json_variant_unrefp
) JsonVariant
*reply
= NULL
;
1123 r
= json_build(&reply
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("data", &output
)));
1127 json_variant_sensitive(reply
);
1129 return varlink_reply(link
, reply
);
1132 static int run(int argc
, char *argv
[]) {
1137 r
= parse_argv(argc
, argv
);
1142 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
1143 _cleanup_(hashmap_freep
) Hashmap
*polkit_registry
= NULL
;
1145 /* Invocation as Varlink service */
1147 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ACCOUNT_UID
|VARLINK_SERVER_INHERIT_USERDATA
|VARLINK_SERVER_INPUT_SENSITIVE
);
1149 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
1151 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_Credentials
);
1153 return log_error_errno(r
, "Failed to add Varlink interface: %m");
1155 r
= varlink_server_bind_method_many(
1157 "io.systemd.Credentials.Encrypt", vl_method_encrypt
,
1158 "io.systemd.Credentials.Decrypt", vl_method_decrypt
);
1160 return log_error_errno(r
, "Failed to bind Varlink methods: %m");
1162 varlink_server_set_userdata(varlink_server
, &polkit_registry
);
1164 r
= varlink_server_loop_auto(varlink_server
);
1166 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
1171 return creds_main(argc
, argv
);
1174 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);