summaryrefslogtreecommitdiffstats
path: root/src/tpm2-setup
diff options
context:
space:
mode:
Diffstat (limited to 'src/tpm2-setup')
-rw-r--r--src/tpm2-setup/meson.build16
-rw-r--r--src/tpm2-setup/tpm2-setup.c373
2 files changed, 389 insertions, 0 deletions
diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build
new file mode 100644
index 0000000..c85721c
--- /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 0000000..0be7ffc
--- /dev/null
+++ b/src/tpm2-setup/tpm2-setup.c
@@ -0,0 +1,373 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+#include <unistd.h>
+
+#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"
+
+#define TPM2_SRK_TPM2B_PUBLIC_PERSISTENT_PATH "/var/lib/systemd/tpm2-srk-public-key.tpm2b_public"
+#define TPM2_SRK_TPM2B_PUBLIC_RUNTIME_PATH "/run/systemd/tpm2-srk-public-key.tpm2b_public"
+
+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...]\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; /* as OpenSSL object */
+ TPM2B_PUBLIC *public; /* in TPM2 format */
+ 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;
+ }
+ if (d->public) {
+ Esys_Freep(&d->public);
+ d->public = 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;
+ int r;
+
+ assert(ret);
+
+ r = tpm2_context_new(arg_tpm2_device, &c);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create TPM2 context: %m");
+
+ r = tpm2_get_or_create_srk(
+ c,
+ /* session= */ NULL,
+ &data.public,
+ /* ret_name= */ NULL,
+ /* ret_qname= */ NULL,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get or create SRK: %m");
+ 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(data.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 *pem_path = arg_early ? TPM2_SRK_PEM_RUNTIME_PATH : TPM2_SRK_PEM_PERSISTENT_PATH;
+ (void) mkdir_parents(pem_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(pem_path, O_WRONLY, &t, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open SRK public key file '%s' for writing: %m", pem_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'.", pem_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", pem_path);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to sync SRK key to disk: %m");
+
+ r = flink_tmpfile(f, t, pem_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", pem_path);
+
+ f = safe_fclose(f);
+ t = mfree(t);
+
+ log_info("SRK public key saved to '%s' in PEM format.", pem_path);
+
+ const char *tpm2b_public_path = arg_early ? TPM2_SRK_TPM2B_PUBLIC_RUNTIME_PATH : TPM2_SRK_TPM2B_PUBLIC_PERSISTENT_PATH;
+ (void) mkdir_parents(tpm2b_public_path, 0755);
+
+ /* Now also write this out in TPM2B_PUBLIC format */
+ r = fopen_tmpfile_linkable(tpm2b_public_path, O_WRONLY, &t, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open SRK public key file '%s' for writing: %m", tpm2b_public_path);
+
+ _cleanup_free_ void *marshalled = NULL;
+ size_t marshalled_size = 0;
+ r = tpm2_marshal_public(tpm2_key.public, &marshalled, &marshalled_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to marshal TPM2_PUBLIC key.");
+
+ if (fwrite(marshalled, 1, marshalled_size, f) != marshalled_size)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to write SRK public key file '%s'.", tpm2b_public_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", tpm2b_public_path);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to sync SRK key to disk: %m");
+
+ r = flink_tmpfile(f, t, tpm2b_public_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", tpm2b_public_path);
+
+ log_info("SRK public key saved to '%s' in TPM2B_PUBLIC format.", tpm2b_public_path);
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);