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 statusOn 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);