tpm2-setup: add new early boot tool for initializing the SRK

This adds an explicit service for initializing the TPM2 SRK. This is
implicitly also done by systemd-cryptsetup, hence strictly speaking
redundant, but doing this early has the benefit that we can parallelize
this in a nicer way. This also write a copy of the SRK public key in PEM
format to /run/ + /var/lib/, thus pinning the disk image to the TPM.
Making the SRK public key is also useful for allowing easy offline
encryption for a specific TPM.

Sooner or later we should probably grow what this service does, the
above is just the first step. For example, the service should probably
offer the ability to reset the TPM (clear the owner hierarchy?) on a
factory reset, if such a policy is needed. And we might want to install
some default AK (?).

Fixes: #27986
Also see: #22637
This commit is contained in:
Lennart Poettering 2023-09-26 21:25:53 +02:00
parent baab1b3faa
commit 2e64cb71b9
12 changed files with 496 additions and 5 deletions

View file

@ -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',

View file

@ -0,0 +1,74 @@
<?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>

View file

@ -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')

View file

@ -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");

View file

@ -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. */

View file

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

View file

@ -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,
],
},
]

327
src/tpm2-setup/tpm2-setup.c Normal file
View file

@ -0,0 +1,327 @@
/* 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);

View file

@ -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

View file

@ -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'],

View file

@ -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

View file

@ -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