Merge pull request #29344 from poettering/tpm2-setup

tpm2-setup: set up TPM2 Storage Root Key (SRK) in a separate service
This commit is contained in:
Lennart Poettering 2023-09-29 21:56:28 +02:00 committed by GitHub
commit 68849251a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 504 additions and 5 deletions

8
TODO
View file

@ -186,6 +186,14 @@ Features:
AllowPeerUser= + AllowPeerGroup= to allow trivially simple access control
when invoked via socket as IPC services
* systemd-tpm2-setup should probably have a factory reset logic, i.e. when some
kernel command line option is set we reset the TPM (equivalent of tpm2_clear
-c owner?).
* systemd-tpm2-setup should support a mode where we refuse booting if the SRK
changed. (Must be opt-in, to not break systems which are supposed to be
migratable between PCs)
* when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/)
then allow them to store the result in a .v/ versioned subdir, for some basic
snapshot logic

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