'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',
--- /dev/null
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+<refentry id="systemd-tpm2-setup.service" conditional='ENABLE_BOOTLOADER'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-tpm2-setup.service</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-tpm2-setup.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-tpm2-setup.service</refname>
+ <refname>systemd-tpm2-setup-early.service</refname>
+ <refname>systemd-tpm2-setup</refname>
+ <refpurpose>Set up the TPM2 Storage Root Key (SRK) at boot</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-tpm2-setup.service</filename></para>
+ <para><filename>/usr/lib/systemd/systemd-tpm2-setup</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><filename>systemd-tpm2-setup.service</filename> and
+ <filename>systemd-tpm2-setup-early.service</filename> are services that generate the Storage Root Key
+ (SRK) if it hasn't been generated yet, and stores it in the TPM.</para>
+
+ <para>The services will store the public key of the SRK key pair in a PEM file in
+ <filename>/run/systemd/tpm2-srk-public-key.pem</filename> and
+ <filename>/var/lib/systemd/tpm2-srk-public-key.pem</filename>.</para>
+
+ <para><filename>systemd-tpm2-setup-early.service</filename> runs very early at boot (possibly in the
+ initrd), and writes the SRK public key to <filename>/run/systemd/tpm2-srk-public-key.pem</filename> (as
+ <filename>/var/</filename> is generally not accessible this early yet), while
+ <filename>systemd-tpm2-setup.service</filename> runs during a later boot phase and saves the public key
+ to <filename>/var/lib/systemd/tpm2-srk-public-key.pem</filename>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Files</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><filename>/run/systemd/tpm2-srk-public-key.pem</filename></term>
+
+ <listitem><para>The SRK public key in PEM format, written during early boot.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><filename>/var/lib/systemd/tpm2-srk-public-key.pem</filename></term>
+
+ <listitem><para>The SRK public key in PEM format, written during later boot (once
+ <filename>/var/</filename> is available).</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>
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')
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");
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);
}
}
/* 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,
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), };
/* 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. */
#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);
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);
--- /dev/null
+# 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,
+ ],
+ },
+]
--- /dev/null
+/* 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"
+
+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);
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() {
(! 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
'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'],
--- /dev/null
+# 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
--- /dev/null
+# 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