diff --git a/man/rules/meson.build b/man/rules/meson.build
index 9a383b985e..3454668cab 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -1088,6 +1088,10 @@ manpages = [
'systemd-tmpfiles-setup-dev.service',
'systemd-tmpfiles-setup.service'],
''],
+ ['systemd-tpm2-setup.service',
+ '8',
+ ['systemd-tpm2-setup', 'systemd-tpm2-setup-early.service'],
+ 'ENABLE_BOOTLOADER'],
['systemd-tty-ask-password-agent', '1', [], ''],
['systemd-udev-settle.service', '8', [], ''],
['systemd-udevd.service',
diff --git a/man/systemd-tpm2-setup.service.xml b/man/systemd-tpm2-setup.service.xml
new file mode 100644
index 0000000000..77502d5084
--- /dev/null
+++ b/man/systemd-tpm2-setup.service.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+ systemd-tpm2-setup.service
+ systemd
+
+
+
+ systemd-tpm2-setup.service
+ 8
+
+
+
+ systemd-tpm2-setup.service
+ systemd-tpm2-setup-early.service
+ systemd-tpm2-setup
+ Set up the TPM2 Storage Root Key (SRK) at boot
+
+
+
+ systemd-tpm2-setup.service
+ /usr/lib/systemd/systemd-tpm2-setup
+
+
+
+ Description
+
+ systemd-tpm2-setup.service and
+ systemd-tpm2-setup-early.service are services that generate the Storage Root Key
+ (SRK) if it hasn't been generated yet, and stores it in the TPM.
+
+ The services will store the public key of the SRK key pair in a PEM file in
+ /run/systemd/tpm2-srk-public-key.pem and
+ /var/lib/systemd/tpm2-srk-public-key.pem.
+
+ systemd-tpm2-setup-early.service runs very early at boot (possibly in the
+ initrd), and writes the SRK public key to /run/systemd/tpm2-srk-public-key.pem (as
+ /var/ is generally not accessible this early yet), while
+ systemd-tpm2-setup.service runs during a later boot phase and saves the public key
+ to /var/lib/systemd/tpm2-srk-public-key.pem.
+
+
+
+ Files
+
+
+
+ /run/systemd/tpm2-srk-public-key.pem
+
+ The SRK public key in PEM format, written during early boot.
+
+
+
+ /var/lib/systemd/tpm2-srk-public-key.pem
+
+ The SRK public key in PEM format, written during later boot (once
+ /var/ is available).
+
+
+
+
+
+ See Also
+
+ systemd1
+
+
+
diff --git a/meson.build b/meson.build
index 90705b3158..44eac94fdf 100644
--- a/meson.build
+++ b/meson.build
@@ -2186,6 +2186,7 @@ subdir('src/sysusers')
subdir('src/sysv-generator')
subdir('src/timedate')
subdir('src/timesync')
+subdir('src/tpm2-setup')
subdir('src/tmpfiles')
subdir('src/tty-ask-password-agent')
subdir('src/update-done')
diff --git a/src/shared/generator.c b/src/shared/generator.c
index aced4949a9..5626587269 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -741,7 +741,7 @@ int generator_write_cryptsetup_unit_section(
fprintf(f,
"\n"
"DefaultDependencies=no\n"
- "After=cryptsetup-pre.target systemd-udevd-kernel.socket\n"
+ "After=cryptsetup-pre.target systemd-udevd-kernel.socket systemd-tpm2-setup-early.service\n"
"Before=blockdev@dev-mapper-%%i.target\n"
"Wants=blockdev@dev-mapper-%%i.target\n"
"IgnoreOnIsolate=true\n");
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index 64dcc5503b..3374bd4f78 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -148,7 +148,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal));
}
-static void Esys_Freep(void *p) {
+void Esys_Freep(void *p) {
if (*(void**) p)
sym_Esys_Free(*(void**) p);
}
@@ -1175,7 +1175,7 @@ static int tpm2_get_srk(
}
/* Get the SRK, creating one if needed. Returns 0 on success, or < 0 on error. */
-static int tpm2_get_or_create_srk(
+int tpm2_get_or_create_srk(
Tpm2Context *c,
const Tpm2Handle *session,
TPM2B_PUBLIC **ret_public,
@@ -1189,7 +1189,7 @@ static int tpm2_get_or_create_srk(
if (r < 0)
return r;
if (r == 1)
- return 0;
+ return 0; /* 0 → SRK already set up */
/* No SRK, create and persist one */
TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), };
@@ -1223,7 +1223,7 @@ static int tpm2_get_or_create_srk(
/* This should never happen. */
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SRK we just persisted couldn't be found.");
- return 0;
+ return 1; /* > 0 → SRK newly set up */
}
/* Utility functions for TPMS_PCR_SELECTION. */
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 7253372293..8df41f001d 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -67,6 +67,8 @@ typedef struct {
#define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), }
static const Tpm2Handle TPM2_HANDLE_NONE = _tpm2_handle(NULL, ESYS_TR_NONE);
+void Esys_Freep(void *p);
+
int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle);
Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle);
DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free);
@@ -188,6 +190,8 @@ int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_v
int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, void **ret_blob, size_t *ret_blob_size);
int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private);
+int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
+
int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build
new file mode 100644
index 0000000000..c85721c98e
--- /dev/null
+++ b/src/tpm2-setup/meson.build
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executables += [
+ libexec_template + {
+ 'name' : 'systemd-tpm2-setup',
+ 'sources' : files('tpm2-setup.c'),
+ 'conditions' : [
+ 'ENABLE_BOOTLOADER',
+ 'HAVE_OPENSSL',
+ 'HAVE_TPM2',
+ ],
+ 'dependencies' : [
+ libopenssl,
+ ],
+ },
+]
diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c
new file mode 100644
index 0000000000..b88e7ec82f
--- /dev/null
+++ b/src/tpm2-setup/tpm2-setup.c
@@ -0,0 +1,327 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+#include
+
+#include "build.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "main-func.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "pretty-print.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
+#include "tpm2-util.h"
+
+static char *arg_tpm2_device = NULL;
+static bool arg_early = false;
+
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+
+#define TPM2_SRK_PEM_PERSISTENT_PATH "/var/lib/systemd/tpm2-srk-public-key.pem"
+#define TPM2_SRK_PEM_RUNTIME_PATH "/run/systemd/tpm2-srk-public-key.pem"
+
+static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-tpm2-setup", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] COMMAND\n"
+ "\n%5$sSet up the TPM2 Storage Root Key (SRK).%6$s\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --tpm2-device=PATH\n"
+ " Pick TPM2 device\n"
+ " --early=BOOL Store SRK public key in /run/ rather than /var/lib/\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_TPM2_DEVICE,
+ ARG_EARLY,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
+ { "early", required_argument, NULL, ARG_EARLY },
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ return help(0, NULL, NULL);
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_TPM2_DEVICE:
+ if (streq(optarg, "list"))
+ return tpm2_list_devices();
+
+ if (free_and_strdup(&arg_tpm2_device, streq(optarg, "auto") ? NULL : optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_EARLY:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --early= argument: %s", optarg);
+
+ arg_early = r;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (optind != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no argument.");
+
+ return 1;
+}
+
+struct public_key_data {
+ EVP_PKEY *pkey;
+ void *fingerprint;
+ size_t fingerprint_size;
+ char *fingerprint_hex;
+ char *path;
+};
+
+static void public_key_data_done(struct public_key_data *d) {
+ assert(d);
+
+ if (d->pkey) {
+ EVP_PKEY_free(d->pkey);
+ d->pkey = NULL;
+ }
+ d->fingerprint = mfree(d->fingerprint);
+ d->fingerprint_size = 0;
+ d->fingerprint_hex = mfree(d->fingerprint_hex);
+ d->path = mfree(d->path);
+}
+
+static int public_key_make_fingerprint(struct public_key_data *d) {
+ int r;
+
+ assert(d);
+ assert(d->pkey);
+ assert(!d->fingerprint);
+ assert(!d->fingerprint_hex);
+
+ r = pubkey_fingerprint(d->pkey, EVP_sha256(), &d->fingerprint, &d->fingerprint_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to calculate fingerprint of public key: %m");
+
+ d->fingerprint_hex = hexmem(d->fingerprint, d->fingerprint_size);
+ if (!d->fingerprint_hex)
+ return log_oom();
+
+ return 0;
+}
+
+static int load_public_key_disk(const char *path, struct public_key_data *ret) {
+ _cleanup_(public_key_data_done) struct public_key_data data = {};
+ _cleanup_free_ char *blob = NULL;
+ size_t blob_size;
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ r = read_full_file(path, &blob, &blob_size);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return log_error_errno(r, "Failed to read '%s': %m", path);
+
+ log_debug("SRK public key file '%s' does not exist.", path);
+ } else {
+ log_debug("Loaded SRK public key from '%s'.", path);
+
+ r = openssl_pkey_from_pem(blob, blob_size, &data.pkey);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse SRK public key file '%s': %m", path);
+
+ r = public_key_make_fingerprint(&data);
+ if (r < 0)
+ return r;
+
+ log_debug("Loaded SRK public key fingerprint: %s", data.fingerprint_hex);
+ }
+
+ data.path = strdup(path);
+ if (!data.path)
+ return log_oom();
+
+ *ret = data;
+ data = (struct public_key_data) {};
+
+ return 0;
+}
+
+static int load_public_key_tpm2(struct public_key_data *ret) {
+ _cleanup_(public_key_data_done) struct public_key_data data = {};
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
+ _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
+ int r;
+
+ assert(ret);
+
+ r = tpm2_context_new(arg_tpm2_device, &c);
+ if (r < 0)
+ return r;
+
+ r = tpm2_get_or_create_srk(
+ c,
+ /* session= */ NULL,
+ &public,
+ /* ret_name= */ NULL,
+ /* ret_qname= */ NULL,
+ NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ log_info("New SRK generated and stored in the TPM.");
+ else
+ log_info("SRK already stored in the TPM.");
+
+ r = tpm2_tpm2b_public_to_openssl_pkey(public, &data.pkey);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert TPM2 SRK public key to OpenSSL public key: %m");
+
+ r = public_key_make_fingerprint(&data);
+ if (r < 0)
+ return r;
+
+ log_info("SRK fingerprint is %s.", data.fingerprint_hex);
+
+ *ret = data;
+ data = (struct public_key_data) {};
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ umask(0022);
+
+ _cleanup_(public_key_data_done) struct public_key_data runtime_key = {}, persistent_key = {}, tpm2_key = {};
+
+ r = load_public_key_disk(TPM2_SRK_PEM_RUNTIME_PATH, &runtime_key);
+ if (r < 0)
+ return r;
+
+ if (!arg_early) {
+ r = load_public_key_disk(TPM2_SRK_PEM_PERSISTENT_PATH, &persistent_key);
+ if (r < 0)
+ return r;
+
+ if (runtime_key.pkey && persistent_key.pkey &&
+ memcmp_nn(runtime_key.fingerprint, runtime_key.fingerprint_size,
+ persistent_key.fingerprint, persistent_key.fingerprint_size) != 0) {
+
+ /* One of those days we might want to add a stricter policy option here, that refuses
+ * to boot when the SRK changes. For now, let's just warn and proceed, in order not
+ * to break OS images that are moved around PCs. */
+
+ log_notice("Saved persistent SRK (%s) and runtime SRK differ (fingerprint %s vs. %s), updating persistent SRK.",
+ persistent_key.path, persistent_key.fingerprint_hex, runtime_key.fingerprint_hex);
+
+ public_key_data_done(&persistent_key);
+ }
+ }
+
+ r = load_public_key_tpm2(&tpm2_key);
+ if (r < 0)
+ return r;
+
+ assert(tpm2_key.pkey);
+
+ if (runtime_key.pkey) {
+ if (memcmp_nn(tpm2_key.fingerprint, tpm2_key.fingerprint_size,
+ runtime_key.fingerprint, runtime_key.fingerprint_size) != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Saved runtime SRK differs from TPM SRK, refusing.");
+
+ if (arg_early) {
+ log_info("SRK saved in '%s' matches SRK in TPM2.", runtime_key.path);
+ return 0;
+ }
+ }
+
+ if (persistent_key.pkey) {
+ if (memcmp_nn(tpm2_key.fingerprint, tpm2_key.fingerprint_size,
+ persistent_key.fingerprint, persistent_key.fingerprint_size) == 0) {
+ log_info("SRK saved in '%s' matches SRK in TPM2.", persistent_key.path);
+ return 0;
+ }
+
+ /* As above, we probably want a stricter policy option here, one day. */
+
+ log_notice("Saved persistent SRK (%s) and TPM SRK differ (fingerprint %s vs. %s), updating persistent SRK.",
+ persistent_key.path, persistent_key.fingerprint_hex, tpm2_key.fingerprint_hex);
+
+ public_key_data_done(&persistent_key);
+ }
+
+ const char *path = arg_early ? TPM2_SRK_PEM_RUNTIME_PATH : TPM2_SRK_PEM_PERSISTENT_PATH;
+
+ (void) mkdir_parents(path, 0755);
+
+ /* Write out public key (note that we only do that as a help to the user, we don't make use of this ever */
+ _cleanup_(unlink_and_freep) char *t = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ r = fopen_tmpfile_linkable(path, O_WRONLY, &t, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open SRK public key file '%s' for writing: %m", path);
+
+ if (PEM_write_PUBKEY(f, tpm2_key.pkey) <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write SRK public key file '%s'.", path);
+
+ if (fchmod(fileno(f), 0444) < 0)
+ return log_error_errno(errno, "Failed to adjust access mode of SRK public key file '%s' to 0444: %m", path);
+
+ r = flink_tmpfile(f, t, path, LINK_TMPFILE_SYNC|LINK_TMPFILE_REPLACE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to move SRK public key file to '%s': %m", path);
+
+ log_info("SRK public key saved to '%s'.", path);
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh
index 7f7183da08..8358c48434 100755
--- a/test/units/testsuite-70.sh
+++ b/test/units/testsuite-70.sh
@@ -5,6 +5,7 @@ set -o pipefail
SD_MEASURE="/usr/lib/systemd/systemd-measure"
SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend"
+SD_TPM2SETUP="/usr/lib/systemd/systemd-tpm2-setup"
export SYSTEMD_LOG_LEVEL=debug
cryptsetup_has_token_plugin_support() {
@@ -372,4 +373,12 @@ systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$img"
(! systemd-cryptenroll --wipe-slot=10240000 "$img")
(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto "$img")
+# Run this, just to get sanitizer coverage. The tools should be idempotent, hence run the multiple times.
+if [[ -x "$SD_TPM2SETUP" ]]; then
+ "$SD_TPM2SETUP" --early=yes
+ "$SD_TPM2SETUP" --early=yes
+ "$SD_TPM2SETUP" --early=no
+ "$SD_TPM2SETUP" --early=no
+fi
+
touch /testok
diff --git a/units/meson.build b/units/meson.build
index 2fb87934b8..e1de2b4a6d 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -458,6 +458,16 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
+ {
+ 'file' : 'systemd-tpm2-setup.service.in',
+ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
+ 'symlinks' : ['sysinit.target.wants/'],
+ },
+ {
+ 'file' : 'systemd-tpm2-setup-early.service.in',
+ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
+ 'symlinks' : ['sysinit.target.wants/'],
+ },
{
'file' : 'systemd-portabled.service.in',
'conditions' : ['ENABLE_PORTABLED'],
diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in
new file mode 100644
index 0000000000..c1597ea3f9
--- /dev/null
+++ b/units/systemd-tpm2-setup-early.service.in
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=TPM2 SRK Setup (Early)
+Documentation=man:systemd-tpm2-setup.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=sysinit.target shutdown.target
+ConditionSecurity=measured-uki
+ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --early=yes
diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in
new file mode 100644
index 0000000000..6c99f3af0a
--- /dev/null
+++ b/units/systemd-tpm2-setup.service.in
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=TPM2 SRK Setup
+Documentation=man:systemd-tpm2-setup.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-tpm2-setup-early.service systemd-remount-fs.service
+Before=sysinit.target shutdown.target
+RequiresMountsFor=/var/lib/systemd/tpm2-srk-public-key.pem
+ConditionSecurity=measured-uki
+ConditionPathExists=!/etc/initrd-release
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup