From ba578556288f5d6f16a94fda702178b349beca85 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 Apr 2022 14:42:27 +0200 Subject: [PATCH 1/5] tpm2-util: add helper that checks for the various facets of TPM2 support So far we were a bit sloppy regarding checks for TPM2 support. Let's make things more precise and introduce a single helper that checks for three axis of TPM2 support: whether we have a loaded kernel driver, whether the firmware used it, and whether we ourselves are compiled for it. This only adds the helper. Follow-up patches will use it at various places. --- src/shared/tpm2-util.c | 23 +++++++++++++++++++++++ src/shared/tpm2-util.h | 10 ++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 3dfc5d8b7d..62ba4b0ba8 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 f9dedd670b..7a0c47e233 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -89,3 +89,13 @@ typedef struct { uint32_t search_pcr_mask; const char *device; } systemd_tpm2_plugin_params; + +typedef enum Tpm2Support { + 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); From e1be2c779c6f9283d2119e3e05fb2ae5fd51d380 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 Apr 2022 14:44:26 +0200 Subject: [PATCH 2/5] condition: rework ConditionSecurity=tpm2 check on top of tpm2_support() No change in behaviour. Let's just use our new helper here. --- src/shared/condition.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/shared/condition.c b/src/shared/condition.c index be09b852db..1d87ed53ce 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) { From 0ea911d14c738447511b5a807750b356e0758895 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 Apr 2022 14:45:20 +0200 Subject: [PATCH 3/5] bootctl: use new tpm2_support() helper to show TPM2 info Let's improve the output regarding TPM2 support in "bootctl": let's show whether we have local driver support and/or firmware support, and colorize it. (For now, don't show if we natively support TPM2, since the tool is mostly bout boot time stuff, where it dosn't really matter much what we do in userspace) --- src/boot/bootctl.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index eac071bcc6..69703a74c1 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) From 6e0cb81505deb8fd785492f90cdac10388d1858e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 Apr 2022 14:47:02 +0200 Subject: [PATCH 4/5] creds-tool: add new "has-tpm2" verb Sometimes it's useful from shell scripts to check if we have a working TPM2 chip around. For example, when putting together encrypted credentials for the initrd (after all: it might be wise to place the root pw in a credential for the initrd to consume, but do so only if we can lock it to the TPM2, and not otherwise, so that we risk nothing). Hence, let's add a new "systemd-creds has-tpm2" verb: it returns zero if we have a working TPM2 (which means: supported by kernel + firmware + us), or non-zero otherwise. Also show which parts are available. Use-case: in future the 'kernel-install' script should use this when deciding whether to augment kernels with security sensitive credentials. --- src/creds/creds.c | 51 ++++++++++++++++++++++++++++++++++++------ src/shared/tpm2-util.h | 2 ++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index 6e3441e5a0..1d69aed4c4 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) { @@ -757,6 +789,10 @@ static int parse_argv(int argc, char *argv[]) { break; + case 'q': + arg_quiet = true; + break; + case '?': return -EINVAL; @@ -774,12 +810,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/tpm2-util.h b/src/shared/tpm2-util.h index 7a0c47e233..ef19bed4f6 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -91,6 +91,8 @@ typedef struct { } 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 */ From eb81249e8a311e78310acbe92245c2ec8d72370c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 Apr 2022 15:08:23 +0200 Subject: [PATCH 5/5] man: document new has-tpm2 verb --- man/systemd-creds.xml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml index d3c0295d64..2d6a4fd577 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. + + @@ -305,6 +319,14 @@ systemd-cryptenroll1. + + + + + When used with has-tpm2 suppresses the output, and only returns an + exit status indicating support for TPM2. + + @@ -315,6 +337,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.