diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml index 5fa6067ed15..342c39a4e25 100644 --- a/man/systemd-creds.xml +++ b/man/systemd-creds.xml @@ -163,6 +163,20 @@ and thus decryption is entirely automatic. + + has-tpm2 + + Reports whether the system is equipped with a TPM2 device usable for protecting + credentials. If the a TPM2 device has been discovered, is supported, and is being used by firmware, + by the OS kernel drivers and by userspace (i.e. systemd) this prints yes and exits + with exit status zero. If no such device is discovered/supported/used, prints + no. Otherwise prints partial. In either of these two cases + exits with non-zero exit status. It also shows three lines indicating separately whether drivers, + firmware and the system discovered/support/use TPM2. + + Combine with to suppress the output. + + @@ -314,6 +328,14 @@ systemd-cryptenroll1. + + + + + When used with has-tpm2 suppresses the output, and only returns an + exit status indicating support for TPM2. + + @@ -324,6 +346,12 @@ Exit status On success, 0 is returned. + + In case of the has-tpm2 command returns 0 if a TPM2 device is discovered, + supported and used by firmware, driver, and userspace (i.e. systemd). Otherwise returns the OR + combination of the value 1 (in case firmware support is missing), 2 (in case driver support is missing) + and 4 (in case userspace support is missing). If no TPM2 support is available at all, value 7 is hence + returned. diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index eac071bcc63..69703a74c1a 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -47,6 +47,7 @@ #include "terminal-util.h" #include "tmpfile-util.h" #include "tmpfile-util-label.h" +#include "tpm2-util.h" #include "umask-util.h" #include "utf8.h" #include "util.h" @@ -1697,10 +1698,10 @@ static int verb_status(int argc, char *argv[], void *userdata) { { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, }; - _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL; sd_id128_t loader_part_uuid = SD_ID128_NULL; uint64_t loader_features = 0; + Tpm2Support s; int have; read_efi_var(EFI_LOADER_VARIABLE(LoaderFirmwareType), &fw_type); @@ -1723,7 +1724,15 @@ static int verb_status(int argc, char *argv[], void *userdata) { printf(" Secure Boot: %sd (%s)\n", enable_disable(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)), secure_boot_mode_to_string(secure)); - printf(" TPM2 Support: %s\n", yes_no(efi_has_tpm2())); + + s = tpm2_support(); + printf(" TPM2 Support: %s%s%s\n", + FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? ansi_highlight_green() : + (s & (TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER)) != 0 ? ansi_highlight_red() : ansi_highlight_yellow(), + FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? "yes" : + (s & TPM2_SUPPORT_FIRMWARE) ? "firmware only, driver unavailable" : + (s & TPM2_SUPPORT_DRIVER) ? "driver only, firmware unavailable" : "no", + ansi_normal()); k = efi_get_reboot_to_firmware(); if (k > 0) diff --git a/src/creds/creds.c b/src/creds/creds.c index c5a1dc506ce..92aedc903f1 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -48,6 +48,7 @@ static bool arg_name_any = false; static usec_t arg_timestamp = USEC_INFINITY; static usec_t arg_not_after = USEC_INFINITY; static bool arg_pretty = false; +static bool arg_quiet = false; static const char* transcode_mode_table[_TRANSCODE_MAX] = { [TRANSCODE_OFF] = "off", @@ -525,6 +526,34 @@ static int verb_setup(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } +static int verb_has_tpm2(int argc, char **argv, void *userdata) { + Tpm2Support s; + + s = tpm2_support(); + + if (!arg_quiet) { + if (s == TPM2_SUPPORT_FULL) + puts("yes"); + else if (s == TPM2_SUPPORT_NONE) + puts("no"); + else + puts("partial"); + + printf("%sfirmware\n" + "%sdriver\n" + "%ssystem\n", + plus_minus(s & TPM2_SUPPORT_FIRMWARE), + plus_minus(s & TPM2_SUPPORT_DRIVER), + plus_minus(s & TPM2_SUPPORT_SYSTEM)); + } + + /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values + * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than + * what is there, acknowledging the fact that for process exit statusses it is customary to return + * zero (EXIT_FAILURE) when all is good, instead of all being bad. */ + return ~s & TPM2_SUPPORT_FULL; +} + static int verb_help(int argc, char **argv, void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -543,6 +572,7 @@ static int verb_help(int argc, char **argv, void *userdata) { " ciphertext credential file\n" " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n" " plaintext credential file\n" + " has-tpm2 Report whether TPM2 support is available\n" " -h --help Show this help\n" " --version Show package version\n" "\n%3$sOptions:%4$s\n" @@ -568,6 +598,7 @@ static int verb_help(int argc, char **argv, void *userdata) { " Pick TPM2 device\n" " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" " Specify TPM2 PCRs to seal against\n" + " -q --quiet Suppress output for 'has-tpm2' verb\n" "\nSee the %2$s for details.\n" , program_invocation_short_name , link @@ -612,6 +643,7 @@ static int parse_argv(int argc, char *argv[]) { { "name", required_argument, NULL, ARG_NAME }, { "timestamp", required_argument, NULL, ARG_TIMESTAMP }, { "not-after", required_argument, NULL, ARG_NOT_AFTER }, + { "quiet", no_argument, NULL, 'q' }, {} }; @@ -620,7 +652,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hHTp", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) { switch (c) { @@ -761,6 +793,10 @@ static int parse_argv(int argc, char *argv[]) { break; + case 'q': + arg_quiet = true; + break; + case '?': return -EINVAL; @@ -778,12 +814,13 @@ static int parse_argv(int argc, char *argv[]) { static int creds_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list }, - { "cat", 2, VERB_ANY, 0, verb_cat }, - { "encrypt", 3, 3, 0, verb_encrypt }, - { "decrypt", 2, 3, 0, verb_decrypt }, - { "setup", VERB_ANY, 1, 0, verb_setup }, - { "help", VERB_ANY, 1, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list }, + { "cat", 2, VERB_ANY, 0, verb_cat }, + { "encrypt", 3, 3, 0, verb_encrypt }, + { "decrypt", 2, 3, 0, verb_decrypt }, + { "setup", VERB_ANY, 1, 0, verb_setup }, + { "help", VERB_ANY, 1, 0, verb_help }, + { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, {} }; diff --git a/src/shared/condition.c b/src/shared/condition.c index be09b852db2..1d87ed53ce7 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -50,6 +50,7 @@ #include "string-table.h" #include "string-util.h" #include "tomoyo-util.h" +#include "tpm2-util.h" #include "udev-util.h" #include "uid-alloc-range.h" #include "user-util.h" @@ -623,29 +624,14 @@ static int condition_test_ac_power(Condition *c, char **env) { } static int has_tpm2(void) { - int r; - /* Checks whether the system has at least one TPM2 resource manager device, i.e. at least one "tpmrm" - * class device */ + * class device. Alternatively, we are also happy if the firmware reports support (this is to cover + * for cases where we simply haven't loaded the driver for it yet, i.e. during early boot where we + * very likely want to use this condition check). + * + * Note that we don't check if we ourselves are built with TPM2 support here! */ - r = dir_is_empty("/sys/class/tpmrm"); - if (r == 0) - return true; /* nice! we have a device */ - - /* Hmm, so Linux doesn't know of the TPM2 device (or we couldn't check for it), most likely because - * the driver wasn't loaded yet. Let's see if the firmware knows about a TPM2 device, in this - * case. This way we can answer the TPM2 question already during early boot (where we most likely - * need it) */ - if (efi_has_tpm2()) - return true; - - /* OK, this didn't work either, in this case propagate the original errors */ - if (r == -ENOENT) - return false; - if (r < 0) - return log_debug_errno(r, "Failed to determine whether system has TPM2 support: %m"); - - return !r; + return (tpm2_support() & (TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_FIRMWARE)) != 0; } static int condition_test_security(Condition *c, char **env) { diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 3dfc5d8b7dd..62ba4b0ba8f 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "efi-api.h" #include "extract-word.h" #include "parse-util.h" +#include "stat-util.h" #include "tpm2-util.h" #if HAVE_TPM2 @@ -1453,3 +1455,24 @@ int tpm2_primary_alg_from_string(const char *alg) { return TPM2_ALG_RSA; return -EINVAL; } + +Tpm2Support tpm2_support(void) { + Tpm2Support support = TPM2_SUPPORT_NONE; + int r; + + r = dir_is_empty("/sys/class/tpmrm"); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Unable to test whether /sys/class/tpmrm/ exists and is populated, assuming it is not: %m"); + } else if (r == 0) /* populated! */ + support |= TPM2_SUPPORT_DRIVER; + + if (efi_has_tpm2()) + support |= TPM2_SUPPORT_FIRMWARE; + +#if HAVE_TPM2 + support |= TPM2_SUPPORT_SYSTEM; +#endif + + return support; +} diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index f9dedd670b0..ef19bed4f6c 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -89,3 +89,15 @@ typedef struct { uint32_t search_pcr_mask; const char *device; } systemd_tpm2_plugin_params; + +typedef enum Tpm2Support { + /* NOTE! The systemd-creds tool returns these flags 1:1 as exit status. Hence these flags are pretty + * much ABI! Hence, be extra careful when changing/extending these definitions. */ + TPM2_SUPPORT_NONE = 0, /* no support */ + TPM2_SUPPORT_FIRMWARE = 1 << 0, /* firmware reports TPM2 was used */ + TPM2_SUPPORT_DRIVER = 1 << 1, /* the kernel has a driver loaded for it */ + TPM2_SUPPORT_SYSTEM = 1 << 2, /* we support it ourselves */ + TPM2_SUPPORT_FULL = TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_SYSTEM, +} Tpm2Support; + +Tpm2Support tpm2_support(void);