mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-10 01:17:44 +03:00
boot: add new pcrphase tool to measure barrier strings into PCR 11
This commit is contained in:
parent
c5bf1f85cb
commit
708d752479
@ -527,3 +527,15 @@ Support: %SUPPORT_URL%
|
||||
|
||||
For the first time during the current boot an NTP synchronization has been
|
||||
acquired and the local system clock adjustment has been initiated.
|
||||
|
||||
-- 3f7d5ef3e54f4302b4f0b143bb270cab
|
||||
Subject: TPM PCR Extended
|
||||
Defined-By: systemd
|
||||
Support: %SUPPORT_URL%
|
||||
|
||||
The string '@MEASURING@' has been extended into Trusted Platform Module's (TPM)
|
||||
Platform Configuration Register (PCR) @PCR@, on banks @BANKS@.
|
||||
|
||||
Whenever the system transitions to a new runtime phase, a different string is
|
||||
extended into the specified PCR, to ensure that security policies for TPM-bound
|
||||
secrets and other resources are limited to specific phases of the runtime.
|
||||
|
@ -966,6 +966,10 @@ manpages = [
|
||||
['systemd-nspawn', '1', [], ''],
|
||||
['systemd-oomd.service', '8', ['systemd-oomd'], 'ENABLE_OOMD'],
|
||||
['systemd-path', '1', [], ''],
|
||||
['systemd-pcrphase.service',
|
||||
'8',
|
||||
['systemd-pcrphase', 'systemd-pcrphase-initrd.service'],
|
||||
'HAVE_GNU_EFI'],
|
||||
['systemd-portabled.service', '8', ['systemd-portabled'], 'ENABLE_PORTABLED'],
|
||||
['systemd-pstore.service', '8', ['systemd-pstore'], 'ENABLE_PSTORE'],
|
||||
['systemd-quotacheck.service',
|
||||
|
@ -250,7 +250,8 @@
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
|
134
man/systemd-pcrphase.service.xml
Normal file
134
man/systemd-pcrphase.service.xml
Normal file
@ -0,0 +1,134 @@
|
||||
<?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-pcrphase.service" conditional='HAVE_GNU_EFI'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-pcrphase.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-pcrphase.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-pcrphase.service</refname>
|
||||
<refname>systemd-pcrphase-initrd.service</refname>
|
||||
<refname>systemd-pcrphase</refname>
|
||||
<refpurpose>Mark current boot process as successful</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-pcrphase.service</filename></para>
|
||||
<para><filename>systemd-pcrphase-initrd.service</filename></para>
|
||||
<para><filename>/usr/lib/systemd/system-pcrphase</filename> <replaceable>STRING</replaceable></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><filename>systemd-pcrphase.service</filename> and
|
||||
<filename>systemd-pcrphase-initrd.service</filename> are system services that measure specific strings
|
||||
into TPM2 PCR 11 during boot.</para>
|
||||
|
||||
<para>These services require
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> to be
|
||||
used in a unified kernel image (UKI) setup. They execute no operation when invoked when the stub has not
|
||||
been used to invoke the kernel. The stub will measure the invoked kernel and associated vendor resources
|
||||
into PCR 11 before handing control to it; once userspace is invoked these services then will extend
|
||||
certain literal strings indicating various phases of the boot process into TPM2 PCR 11. During a regular
|
||||
boot process the following strings are extended into PCR 11.</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem><para><literal>enter-initrd</literal> is extended into PCR 11 early when the initrd
|
||||
initializes, before activating system extension images for the initrd. It is supposed to act as barrier
|
||||
between the time where the kernel initializes, and where the initrd starts operating and enables
|
||||
system extension images, i.e. code shipped outside of the UKI. (This string is extended at start of
|
||||
<filename>systemd-pcrphase-initrd.service</filename>.)</para></listitem>
|
||||
|
||||
<listitem><para><literal>leave-initrd</literal> is extended into PCR 11 when the initrd is about to
|
||||
transition into the host file system, i.e. when it achieved its purpose. It is supposed to act as
|
||||
barrier between kernel/initrd code and host OS code. (This string is extended at stop of
|
||||
<filename>systemd-pcrphase-initrd.service</filename>.)</para></listitem>
|
||||
|
||||
<listitem><para><literal>ready</literal> is extended into PCR 11 during later boot-up, after remote
|
||||
file systems have been activated (i.e. after <filename>remote-fs.target</filename>), but before users
|
||||
are permitted to log in (i.e. before <filename>systemd-user-sessions.service</filename>). It is
|
||||
supposed to act as barrier between the time where unprivileged regular users are still prohibited to
|
||||
log in and where they are allowed to log in. (This string is extended at start of
|
||||
<filename>systemd-pcrphase.service</filename>.)</para></listitem>
|
||||
|
||||
<listitem><para><literal>shutdown</literal> is extended into PCR 11 during system shutdown. It is
|
||||
supposed to act as barrier between the time the system is fully up and running and where it is about to
|
||||
shut down. (This string is extended at stop of
|
||||
<filename>systemd-pcrphase.service</filename>.)</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>During a regular system lifecycle, the strings <literal>enter-initrd</literal> →
|
||||
<literal>leave-initrd</literal> → <literal>ready</literal> → <literal>shutdown</literal> are extended into
|
||||
PCR 11, one after the other.</para>
|
||||
|
||||
<para>Specific phases of the boot process may be referenced via the series of strings measured, separated
|
||||
by colons (the "boot path"). For example, the boot path for the regular system runtime is
|
||||
<literal>enter-initrd:leave-initrd:ready</literal>, while the one for the initrd is just
|
||||
<literal>enter-initrd</literal>. The boot path for the the boot phase before the initrd, is an empty
|
||||
string; because that's hard to pass around a single colon (<literal>:</literal>) may be used
|
||||
instead. Note that the aforementioned four strings are just the default strings and individual systems
|
||||
might measure other strings at other times, and thus implement different and more fine-grained boot
|
||||
phases to bind policy to.</para>
|
||||
|
||||
<para>By binding policy of TPM2 objects to a specific boot path it is possible to restrict access to them
|
||||
to specific phases of the boot process, for example making it impossible to access the root file system's
|
||||
encryption key after the system transitioned from the initrd into the host root file system.</para>
|
||||
|
||||
<para>Use
|
||||
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> to
|
||||
pre-calculate expected PCR 11 values for specific boot phases (via the <option>--phase=</option> switch).</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The <filename>/usr/lib/systemd/system-pcrphase</filename> executable may also be invoked from the
|
||||
command line, where it expects the word to extend into PCR 11, as well as the following switches:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--bank=</option></term>
|
||||
|
||||
<listitem><para>Takes the PCR banks to extend the specified word into. If not specified the tool
|
||||
automatically determines all enabled PCR banks and measures the word into all of
|
||||
them.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--tpm2-device=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Controls which TPM2 device to use. Expects a device node path referring to the TPM2
|
||||
chip (e.g. <filename>/dev/tpmrm0</filename>). Alternatively the special value <literal>auto</literal>
|
||||
may be specified, in order to automatically determine the device node of a suitable TPM2 device (of
|
||||
which there must be exactly one). The special value <literal>list</literal> may be used to enumerate
|
||||
all suitable TPM2 devices currently discovered.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
@ -2566,6 +2566,15 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
executable(
|
||||
'systemd-pcrphase',
|
||||
'src/boot/pcrphase.c',
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [libopenssl, tpm2],
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
endif
|
||||
endif
|
||||
|
||||
|
262
src/boot/pcrphase.c
Normal file
262
src/boot/pcrphase.c
Normal file
@ -0,0 +1,262 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include <sd-messages.h>
|
||||
|
||||
#include "efivars.h"
|
||||
#include "main-func.h"
|
||||
#include "openssl-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "tpm-pcr.h"
|
||||
#include "tpm2-util.h"
|
||||
|
||||
static char *arg_tpm2_device = NULL;
|
||||
static char **arg_banks = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-pcrphase", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Print version\n"
|
||||
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
|
||||
" --tpm2-device=PATH Use specified TPM2 device\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_BANK,
|
||||
ARG_TPM2_DEVICE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "bank", required_argument, NULL, ARG_BANK },
|
||||
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||
{}
|
||||
};
|
||||
|
||||
int c;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
help(0, NULL, NULL);
|
||||
return 0;
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_BANK: {
|
||||
const EVP_MD *implementation;
|
||||
|
||||
implementation = EVP_get_digestbyname(optarg);
|
||||
if (!implementation)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
|
||||
|
||||
if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
|
||||
return log_oom();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_TPM2_DEVICE: {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
return tpm2_list_devices();
|
||||
|
||||
if (!streq(optarg, "auto")) {
|
||||
device = strdup(optarg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
free_and_replace(arg_tpm2_device, device);
|
||||
break;
|
||||
}
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int determine_banks(struct tpm2_context *c) {
|
||||
_cleanup_free_ TPMI_ALG_HASH *algs = NULL;
|
||||
int n_algs, r;
|
||||
|
||||
assert(c);
|
||||
|
||||
if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
|
||||
return 0;
|
||||
|
||||
n_algs = tpm2_get_good_pcr_banks(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &algs);
|
||||
if (n_algs <= 0)
|
||||
return n_algs;
|
||||
|
||||
for (int i = 0; i < n_algs; i++) {
|
||||
const EVP_MD *implementation;
|
||||
const char *salg;
|
||||
|
||||
salg = tpm2_pcr_bank_to_string(algs[i]);
|
||||
if (!salg)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure.");
|
||||
|
||||
implementation = EVP_get_digestbyname(salg);
|
||||
if (!implementation)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure.");
|
||||
|
||||
r = strv_extend(&arg_banks, EVP_MD_name(implementation));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
|
||||
_cleanup_free_ char *joined = NULL, *pcr_string = NULL;
|
||||
const char *word;
|
||||
unsigned pcr_nr;
|
||||
size_t length;
|
||||
TSS2_RC rc;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (optind+1 != argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
|
||||
|
||||
word = argv[optind];
|
||||
|
||||
/* Refuse to measure an empty word. We want to be able to write the series of measured words
|
||||
* separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
|
||||
* disallow an empty word to avoid ambiguities. */
|
||||
if (isempty(word))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
|
||||
|
||||
length = strlen(word);
|
||||
|
||||
/* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
|
||||
r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string);
|
||||
if (r == -ENOENT) {
|
||||
log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m");
|
||||
|
||||
/* Let's validate that the stub announced PCR 11 as we expected. */
|
||||
r = safe_atou(pcr_string, &pcr_nr);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
|
||||
if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
|
||||
r = dlopen_tpm2();
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load TPM2 libraries: %m");
|
||||
|
||||
r = tpm2_context_init(arg_tpm2_device, &c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_banks(&c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (strv_isempty(arg_banks)) /* Still none? */
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
|
||||
|
||||
TPML_DIGEST_VALUES values = {};
|
||||
STRV_FOREACH(bank, arg_banks) {
|
||||
const EVP_MD *implementation;
|
||||
int id;
|
||||
|
||||
assert_se(implementation = EVP_get_digestbyname(*bank));
|
||||
|
||||
if (values.count >= ELEMENTSOF(values.digests))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected.");
|
||||
|
||||
if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2.");
|
||||
|
||||
id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation));
|
||||
if (id < 0)
|
||||
return log_error_errno(id, "Can't map hash name to TPM2.");
|
||||
|
||||
values.digests[values.count].hashAlg = id;
|
||||
|
||||
if (EVP_Digest(word, length, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word.");
|
||||
|
||||
values.count++;
|
||||
}
|
||||
|
||||
joined = strv_join(arg_banks, ", ");
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined);
|
||||
|
||||
rc = sym_Esys_PCR_Extend(
|
||||
c.esys_context,
|
||||
ESYS_TR_PCR0 + TPM_PCR_INDEX_KERNEL_IMAGE, /* → PCR 11 */
|
||||
ESYS_TR_PASSWORD,
|
||||
ESYS_TR_NONE,
|
||||
ESYS_TR_NONE,
|
||||
&values);
|
||||
if (rc != TSS2_RC_SUCCESS)
|
||||
return log_error_errno(
|
||||
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to measure '%s': %s",
|
||||
word,
|
||||
sym_Tss2_RC_Decode(rc));
|
||||
|
||||
log_struct(LOG_INFO,
|
||||
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
|
||||
LOG_MESSAGE("Successfully extended PCR index %u with '%s' (banks %s).", TPM_PCR_INDEX_KERNEL_IMAGE, word, joined),
|
||||
"MEASURING=%s", word,
|
||||
"PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE,
|
||||
"BANKS=%s", joined);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
@ -189,6 +189,9 @@ _SD_BEGIN_DECLARATIONS;
|
||||
#define SD_MESSAGE_SHUTDOWN_CANCELED SD_ID128_MAKE(24,9f,6f,b9,e6,e2,42,8c,96,f3,f0,87,56,81,ff,a3)
|
||||
#define SD_MESSAGE_SHUTDOWN_CANCELED_STR SD_ID128_MAKE_STR(24,9f,6f,b9,e6,e2,42,8c,96,f3,f0,87,56,81,ff,a3)
|
||||
|
||||
#define SD_MESSAGE_TPM_PCR_EXTEND SD_ID128_MAKE(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab)
|
||||
#define SD_MESSAGE_TPM_PCR_EXTEND_STR SD_ID128_MAKE_STR(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab)
|
||||
|
||||
_SD_END_DECLARATIONS;
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user