1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "creds-util.h"
8 #include "dirent-util.h"
11 #include "format-table.h"
12 #include "hexdecoct.h"
15 #include "main-func.h"
16 #include "memory-util.h"
17 #include "missing_magic.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"
26 #include "tpm2-util.h"
27 #include "user-util.h"
29 #include "varlink-io.systemd.Credentials.h"
32 typedef enum TranscodeMode
{
39 _TRANSCODE_INVALID
= -EINVAL
,
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;
48 static sd_id128_t arg_with_key
= _CRED_AUTO
;
49 static const char *arg_tpm2_device
= NULL
;
50 static uint32_t arg_tpm2_pcr_mask
= UINT32_MAX
;
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
;
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;
59 static bool arg_quiet
= false;
60 static bool arg_varlink
= false;
62 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key
, freep
);
63 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature
, freep
);
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",
73 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode
, TranscodeMode
);
75 static int open_credential_directory(
78 const char **ret_prefix
) {
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. */
90 ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY
:
91 SYSTEM_CREDENTIALS_DIRECTORY
;
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? */
98 return log_error_errno(r
, "Failed to get credentials directory: %m");
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
)
109 return log_error_errno(errno
, "Failed to open credentials directory '%s': %m", p
);
128 static int add_credentials_to_table(Table
*t
, bool encrypted
) {
129 _cleanup_closedir_
DIR *d
= NULL
;
135 r
= open_credential_directory(encrypted
, &d
, &prefix
);
139 return 0; /* No creds dir set */
142 _cleanup_free_
char *j
= NULL
;
143 const char *secure
, *secure_color
= NULL
;
144 _cleanup_close_
int fd
= -EBADF
;
149 de
= readdir_no_dot(d
);
154 return log_error_errno(errno
, "Failed to read credentials directory: %m");
157 if (!IN_SET(de
->d_type
, DT_REG
, DT_UNKNOWN
))
160 if (!credential_name_valid(de
->d_name
))
163 fd
= openat(dirfd(d
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
165 if (errno
== ENOENT
) /* Vanished by now? */
168 return log_error_errno(errno
, "Failed to open credential '%s': %m", de
->d_name
);
171 if (fstat(fd
, &st
) < 0)
172 return log_error_errno(errno
, "Failed to stat credential '%s': %m", de
->d_name
);
174 if (!S_ISREG(st
.st_mode
))
178 secure
= "encrypted";
179 secure_color
= ansi_highlight_green();
180 } else if ((st
.st_mode
& 0377) != 0) {
181 secure
= "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
182 secure_color
= ansi_highlight_red();
184 r
= fd_is_fs_type(fd
, RAMFS_MAGIC
);
186 return log_error_errno(r
, "Failed to determine backing file system of '%s': %m", de
->d_name
);
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();
192 j
= path_join(prefix
, de
->d_name
);
198 TABLE_STRING
, de
->d_name
,
199 TABLE_STRING
, secure
,
200 TABLE_SET_COLOR
, secure_color
,
201 TABLE_SIZE
, (uint64_t) st
.st_size
,
204 return table_log_add_error(r
);
207 return 1; /* Creds dir set */
210 static int verb_list(int argc
, char **argv
, void *userdata
) {
211 _cleanup_(table_unrefp
) Table
*t
= NULL
;
214 t
= table_new("name", "secure", "size", "path");
218 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 2), 100);
220 r
= add_credentials_to_table(t
, /* encrypted= */ true);
224 q
= add_credentials_to_table(t
, /* encrypted= */ false);
228 if (r
== 0 && q
== 0) {
230 return log_error_errno(SYNTHETIC_ERRNO(ENXIO
), "No credentials passed to system.");
232 return log_error_errno(SYNTHETIC_ERRNO(ENXIO
), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
235 if ((arg_json_format_flags
& JSON_FORMAT_OFF
) && table_get_rows(t
) <= 1) {
236 log_info("No credentials");
240 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
243 static int transcode(
247 size_t *ret_output_size
) {
254 assert(ret_output_size
);
256 switch (arg_transcode
) {
258 case TRANSCODE_BASE64
: {
262 l
= base64mem_full(input
, input_size
, 79, &buf
);
267 *ret_output_size
= l
;
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 */
278 case TRANSCODE_HEX
: {
281 buf
= hexmem(input
, input_size
);
286 *ret_output_size
= input_size
* 2;
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 */
298 assert_not_reached();
302 static int print_newline(FILE *f
, const char *data
, size_t l
) {
306 assert(data
|| l
== 0);
308 /* If turned off explicitly, don't print newline */
309 if (arg_newline
== 0)
312 /* If data already has newline, don't print either */
313 if (l
> 0 && data
[l
-1] == '\n')
316 /* Don't bother unless this is a tty */
318 if (fd
>= 0 && isatty(fd
) <= 0)
321 if (fputc('\n', f
) != '\n')
322 return log_error_errno(errno
, "Failed to write trailing newline: %m");
327 static int write_blob(FILE *f
, const void *data
, size_t size
) {
328 _cleanup_(erase_and_freep
) void *transcoded
= NULL
;
331 if (arg_transcode
== TRANSCODE_OFF
&&
332 arg_json_format_flags
!= JSON_FORMAT_OFF
) {
333 _cleanup_(erase_and_freep
) char *suffixed
= NULL
;
334 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
336 r
= make_cstring(data
, size
, MAKE_CSTRING_REFUSE_TRAILING_NUL
, &suffixed
);
338 return log_error_errno(r
, "Unable to convert binary string to C string: %m");
340 r
= json_parse(suffixed
, JSON_PARSE_SENSITIVE
, &v
, NULL
, NULL
);
342 return log_error_errno(r
, "Failed to parse JSON: %m");
344 json_variant_dump(v
, arg_json_format_flags
, f
, NULL
);
348 if (arg_transcode
!= TRANSCODE_OFF
) {
349 r
= transcode(data
, size
, &transcoded
, &size
);
351 return log_error_errno(r
, "Failed to transcode data: %m");
356 if (fwrite(data
, 1, size
, f
) != size
)
357 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to write credential data.");
359 r
= print_newline(f
, data
, size
);
363 r
= fflush_and_check(f
);
365 return log_error_errno(r
, "Failed to flush output: %m");
370 static int verb_cat(int argc
, char **argv
, void *userdata
) {
374 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
376 STRV_FOREACH(cn
, strv_skip(argv
, 1)) {
377 _cleanup_(erase_and_freep
) void *data
= NULL
;
381 if (!credential_name_valid(*cn
)) {
382 log_error("Credential name '%s' is not valid.", *cn
);
388 /* Look both in regular and in encrypted credentials */
389 for (encrypted
= 0; encrypted
< 2; encrypted
++) {
390 _cleanup_closedir_
DIR *d
= NULL
;
392 r
= open_credential_directory(encrypted
, &d
, NULL
);
394 return log_error_errno(r
, "Failed to open credentials directory: %m");
395 if (!d
) /* Not set */
398 r
= read_full_file_full(
400 UINT64_MAX
, SIZE_MAX
,
401 READ_FULL_FILE_SECURE
|READ_FULL_FILE_WARN_WORLD_READABLE
,
403 (char**) &data
, &size
);
404 if (r
== -ENOENT
) /* Not found */
406 if (r
>= 0) /* Found */
409 log_error_errno(r
, "Failed to read credential '%s': %m", *cn
);
414 if (encrypted
>= 2) { /* Found nowhere */
415 log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Credential '%s' not set.", *cn
);
423 _cleanup_(erase_and_freep
) void *plaintext
= NULL
;
424 size_t plaintext_size
;
426 r
= decrypt_credential_and_warn(
432 &plaintext
, &plaintext_size
);
436 erase_and_free(data
);
437 data
= TAKE_PTR(plaintext
);
438 size
= plaintext_size
;
441 r
= write_blob(stdout
, data
, size
);
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
;
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
, &plaintext
, &plaintext_size
);
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
);
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 plaintext
, plaintext_size
,
506 &output
, &output_size
);
510 base64_size
= base64mem_full(output
, output_size
, arg_pretty
? 69 : 79, &base64_buf
);
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
) {
518 _cleanup_free_
char *escaped
= NULL
, *indented
= NULL
, *j
= NULL
;
520 escaped
= cescape(name
);
524 indented
= strreplace(base64_buf
, "\n", " \\\n ");
528 j
= strjoin("SetCredentialEncrypted=", escaped
, ": \\\n ", indented
, "\n");
532 free_and_replace(base64_buf
, j
);
536 r
= write_string_file(output_path
, base64_buf
, WRITE_STRING_FILE_ATOMIC
|WRITE_STRING_FILE_CREATE
);
538 r
= write_string_stream(stdout
, base64_buf
, 0);
540 return log_error_errno(r
, "Failed to write result: %m");
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
;
555 assert(IN_SET(argc
, 2, 3));
557 input_path
= empty_or_dash(argv
[1]) ? NULL
: argv
[1];
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
);
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
);
564 return log_error_errno(r
, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX
);
566 return log_error_errno(r
, "Failed to read encrypted credential data: %m");
568 output_path
= (argc
< 3 || empty_or_dash(argv
[2])) ? NULL
: argv
[2];
574 else if (input_path
) {
575 r
= path_extract_filename(input_path
, &fname
);
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
);
583 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
587 timestamp
= arg_timestamp
!= USEC_INFINITY
? arg_timestamp
: now(CLOCK_REALTIME
);
589 r
= decrypt_credential_and_warn(
595 &plaintext
, &plaintext_size
);
600 output_file
= fopen(output_path
, "we");
602 return log_error_errno(errno
, "Failed to create output file '%s': %m", output_path
);
608 r
= write_blob(f
, plaintext
, plaintext_size
);
615 static int verb_setup(int argc
, char **argv
, void *userdata
) {
619 r
= get_credential_host_secret(CREDENTIAL_SECRET_GENERATE
|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED
, NULL
, &size
);
621 return log_error_errno(r
, "Failed to setup credentials host key: %m");
623 log_info("%zu byte credentials host key set up.", size
);
628 static int verb_has_tpm2(int argc
, char **argv
, void *userdata
) {
634 if (s
== TPM2_SUPPORT_FULL
)
636 else if (s
== TPM2_SUPPORT_NONE
)
641 printf("%sfirmware\n"
646 plus_minus(s
& TPM2_SUPPORT_FIRMWARE
),
647 plus_minus(s
& TPM2_SUPPORT_DRIVER
),
648 plus_minus(s
& TPM2_SUPPORT_SYSTEM
),
649 plus_minus(s
& TPM2_SUPPORT_SUBSYSTEM
),
650 plus_minus(s
& TPM2_SUPPORT_LIBRARIES
));
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
655 * what is there, acknowledging the fact that for process exit statuses it is customary to return
656 * zero (EXIT_FAILURE) when all is good, instead of all being bad. */
657 return ~s
& TPM2_SUPPORT_FULL
;
660 static int verb_help(int argc
, char **argv
, void *userdata
) {
661 _cleanup_free_
char *link
= NULL
;
664 r
= terminal_urlify_man("systemd-creds", "1", &link
);
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"
678 " has-tpm2 Report whether TPM2 support is available\n"
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"
696 " --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n"
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"
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"
710 " -q --quiet Suppress output for 'has-tpm2' verb\n"
711 "\nSee the %2$s for details.\n"
712 , program_invocation_short_name
714 , ansi_underline(), ansi_normal()
715 , ansi_highlight(), ansi_normal()
721 static int parse_argv(int argc
, char *argv
[]) {
735 ARG_TPM2_PUBLIC_KEY_PCRS
,
742 static const struct option options
[] = {
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' },
770 while ((c
= getopt_long(argc
, argv
, "hHTpq", options
, NULL
)) >= 0) {
775 return verb_help(0, NULL
, NULL
);
781 arg_pager_flags
|= PAGER_DISABLE
;
789 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
800 if (parse_boolean(optarg
) == 0) /* If specified as "false", turn transcoding off */
801 arg_transcode
= TRANSCODE_OFF
;
805 m
= transcode_mode_from_string(optarg
);
807 return log_error_errno(m
, "Failed to parse transcode mode: %m");
815 if (isempty(optarg
) || streq(optarg
, "auto"))
818 r
= parse_boolean_argument("--newline=", optarg
, NULL
);
831 if (isempty(optarg
) || streq(optarg
, "auto"))
832 arg_with_key
= _CRED_AUTO
;
833 else if (streq(optarg
, "auto-initrd"))
834 arg_with_key
= _CRED_AUTO_INITRD
;
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
;
839 else if (streq(optarg
, "tpm2-with-public-key"))
840 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK
;
841 else if (STR_IN_SET(optarg
, "host+tpm2", "tpm2+host"))
842 arg_with_key
= CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC
;
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
;
845 else if (streq(optarg
, "tpm2-absent"))
846 arg_with_key
= CRED_AES256_GCM_BY_TPM2_ABSENT
;
848 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unknown key type: %s", optarg
);
853 arg_with_key
= CRED_AES256_GCM_BY_HOST
;
857 arg_with_key
= CRED_AES256_GCM_BY_TPM2_HMAC
;
860 case ARG_TPM2_DEVICE
:
861 if (streq(optarg
, "list"))
862 return tpm2_list_devices();
864 arg_tpm2_device
= streq(optarg
, "auto") ? NULL
: optarg
;
867 case ARG_TPM2_PCRS
: /* For fixed hash PCR policies only */
868 r
= tpm2_parse_pcr_argument_to_mask(optarg
, &arg_tpm2_pcr_mask
);
874 case ARG_TPM2_PUBLIC_KEY
:
875 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_tpm2_public_key
);
881 case ARG_TPM2_PUBLIC_KEY_PCRS
: /* For public key PCR policies only */
882 r
= tpm2_parse_pcr_argument_to_mask(optarg
, &arg_tpm2_public_key_pcr_mask
);
888 case ARG_TPM2_SIGNATURE
:
889 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_tpm2_signature
);
896 if (isempty(optarg
)) {
902 if (!credential_name_valid(optarg
))
903 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid credential name: %s", optarg
);
906 arg_name_any
= false;
910 r
= parse_timestamp(optarg
, &arg_timestamp
);
912 return log_error_errno(r
, "Failed to parse timestamp: %s", optarg
);
917 r
= parse_timestamp(optarg
, &arg_not_after
);
919 return log_error_errno(r
, "Failed to parse --not-after= timestamp: %s", optarg
);
931 assert_not_reached();
935 if (arg_tpm2_pcr_mask
== UINT32_MAX
)
936 arg_tpm2_pcr_mask
= TPM2_PCR_MASK_DEFAULT
;
937 if (arg_tpm2_public_key_pcr_mask
== UINT32_MAX
)
938 arg_tpm2_public_key_pcr_mask
= UINT32_C(1) << TPM2_PCR_KERNEL_BOOT
;
940 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
942 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
948 static int creds_main(int argc
, char *argv
[]) {
950 static const Verb verbs
[] = {
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
},
961 return dispatch_verb(argc
, argv
, verbs
, NULL
);
964 typedef struct MethodEncryptParameters
{
970 } MethodEncryptParameters
;
972 static void method_encrypt_parameters_done(MethodEncryptParameters
*p
) {
975 iovec_done_erase(&p
->data
);
978 static int vl_method_encrypt(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
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 },
988 _cleanup_(method_encrypt_parameters_done
) MethodEncryptParameters p
= {
989 .timestamp
= UINT64_MAX
,
990 .not_after
= UINT64_MAX
,
992 _cleanup_(iovec_done
) struct iovec output
= {};
997 json_variant_sensitive(parameters
);
999 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
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");
1013 r
= encrypt_credential_and_warn(
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
);
1027 _cleanup_(json_variant_unrefp
) JsonVariant
*reply
= NULL
;
1029 r
= json_build(&reply
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("blob", &output
)));
1033 /* Let's also mark the (theoretically encrypted) reply as sensitive, in case the NULL encryption scheme was used. */
1034 json_variant_sensitive(reply
);
1036 return varlink_reply(link
, reply
);
1039 typedef struct MethodDecryptParameters
{
1043 } MethodDecryptParameters
;
1045 static void method_decrypt_parameters_done(MethodDecryptParameters
*p
) {
1048 iovec_done_erase(&p
->blob
);
1051 static int vl_method_decrypt(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
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 },
1059 _cleanup_(method_decrypt_parameters_done
) MethodDecryptParameters p
= {
1060 .timestamp
= UINT64_MAX
,
1062 _cleanup_(iovec_done_erase
) struct iovec output
= {};
1067 /* Let's also mark the (theoretically encrypted) input as sensitive, in case the NULL encryption scheme was used. */
1068 json_variant_sensitive(parameters
);
1070 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
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
);
1081 r
= decrypt_credential_and_warn(
1086 p
.blob
.iov_base
, p
.blob
.iov_len
,
1087 &output
.iov_base
, &output
.iov_len
);
1089 return varlink_error(link
, "io.systemd.Credentials.BadFormat", NULL
);
1091 return varlink_error(link
, "io.systemd.Credentials.NameMismatch", NULL
);
1093 return varlink_error(link
, "io.systemd.Credentials.TimeMismatch", NULL
);
1097 _cleanup_(json_variant_unrefp
) JsonVariant
*reply
= NULL
;
1099 r
= json_build(&reply
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("data", &output
)));
1103 json_variant_sensitive(reply
);
1105 return varlink_reply(link
, reply
);
1108 static int run(int argc
, char *argv
[]) {
1113 r
= parse_argv(argc
, argv
);
1118 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
1120 /* Invocation as Varlink service */
1122 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ROOT_ONLY
|VARLINK_SERVER_INHERIT_USERDATA
);
1124 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
1126 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_Credentials
);
1128 return log_error_errno(r
, "Failed to add Varlink interface: %m");
1130 r
= varlink_server_bind_method_many(
1132 "io.systemd.Credentials.Encrypt", vl_method_encrypt
,
1133 "io.systemd.Credentials.Decrypt", vl_method_decrypt
);
1135 return log_error_errno(r
, "Failed to bind Varlink methods: %m");
1137 r
= varlink_server_loop_auto(varlink_server
);
1139 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
1144 return creds_main(argc
, argv
);
1147 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);