1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2024-12-23 17:34:00 +03:00

Merge pull request #24771 from poettering/destroy-pcr11

extend boot phase information into PCR 11 during boot
This commit is contained in:
Luca Boccassi 2022-09-22 20:08:27 +01:00 committed by GitHub
commit c9d65b921b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 881 additions and 86 deletions

36
TODO
View File

@ -119,6 +119,12 @@ Deprecations and removals:
Features:
* systemd-measure: only require private key to be set when signing. iiuc we can
generate the public key from it anyway.
* automatically propagate LUKS password credential into cryptsetup from host,
so that one can unlock LUKS via VM hypervisor supplied password.
* add ability to path_is_valid() to classify paths that refer to a dir from
those which may refer to anything, and use that in various places to filter
early. i.e. stuff ending in "/", "/." and "/.." definitely refers to a
@ -154,11 +160,6 @@ Features:
* tmpfiles: currently if we fail to create an inode, we stat it first, and only
then O_PATH open it. Reverse that.
* during the initrd → host transition measure a fixed value into TPM PCR 11
(where we already measure the UKI into), so that unlock policies for disk
enryption/credential encryption can be put together that only work in the
initrd or only on the host (or both).
* Add support for extra verity configuration options to systemd-repart (FEC,
hash type, etc)
@ -737,8 +738,16 @@ Features:
one.
* we probably should extend the root verity hash of the root fs into some PCR
on boot. (i.e. maybe add a crypttab option tpm2-measure=8 or so to measure it
into PCR 8)
on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
it into PCR 12); Similar: we probably should extend the LUKS volume key of
the root fs into some PCR on boot. (i.e. maybe add a crypttab option
tpm2-measure=15 or so to measure it into PCR 15); once both are in place
update gpt-auto-discovery to generate these by default for the partitions it
discovers. Static vendor stuff should probably end up in PCR 12 (i.e. the
verity hash), with local keys in PCR 15 (i.e. the encryption volume
key). That way, we nicely distinguish resources supplied by the OS vendor
(i.e. sysext, root verity) from those inherently local (i.e. encryption key),
which is useful if they shall be signed separately.
* add a "policy" to the dissection logic. i.e. a bit mask what is OK to mount,
what must be read-only, what requires encryption, and what requires
@ -765,7 +774,6 @@ Features:
* sysupdate:
- add fuzzing to the pattern parser
- support casync as download mechanism
- direct TPM2 PCR change handling, possible renrolling LUKS2 media if needed.
- "systemd-sysupdate update --all" support, that iterates through all components
defined on the host, plus all images installed into /var/lib/machines/,
/var/lib/portable/ and so on.
@ -847,10 +855,6 @@ Features:
* add tpm.target or so which is delayed until TPM2 device showed up in case
firmware indicates there is one.
* Add concept for upgrading TPM2 enrollments, maybe a new switch
--pcrs=4:<hash> or so, i.e. select a PCR to include in the hash, and then
override its hash
* TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades
and such
@ -1606,14 +1610,6 @@ Features:
* firstboot: make it useful to be run immediately after yum --installroot to set up a machine. (most specifically, make --copy-root-password work even if /etc/passwd already exists
* efi stub: optionally, load initrd from disk as a separate file, HMAC check it
with key from TPM, bound to PCR, refusing if failing. This would then allow
traditional distros that generate initrds locally to secure them with TPM:
after generating the initrd, do the HMAC calculation, put result in initrd
filename, done. This would then bind the validity of the initrd to the local
host, and used kernel, and means people cannot change initrd or kernel
without booting the kernel + initrd.
* EFI:
- honor language efi variables for default language selection (if there are any?)
- honor timezone efi variables for default timezone selection (if there are any?)

View File

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

View File

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

View File

@ -143,7 +143,10 @@
unified kernel image, the latter picks the public key of the key pair used to sign the resulting PCR
11 values. The former is the key that the booted system will likely use to lock disk and credential
encryption to, the latter is the key used for unlocking such resources again. Hence, typically the
same PEM key should be supplied in both cases.</para></listitem>
same PEM key should be supplied in both cases.</para>
<para>If the <option>--public-key=</option> is not specified but <option>--private-key=</option> is
specified the public key is automatically derived from the private key.</para></listitem>
</varlistentry>
<varlistentry>
@ -156,6 +159,28 @@
all suitable TPM2 devices currently discovered.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--phase=</option><replaceable>PHASE</replaceable></term>
<listitem><para>Controls which boot phase(s) to calculate expected PCR 11 values for. This takes a
series of colon-separated strings that encode boot "paths" for entering a specific phase of the boot
process. Each of the specified strings is measured by the
<filename>systemd-pcrphase-initrd.service</filename> and
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
into PCR 11 during different milestones of the boot process. This switch may be specified multiple
times to calculate PCR values for multiple boot phases at once. If not used defaults to
<literal>enter-initrd</literal>, <literal>enter-initrd:leave-initrd</literal>,
<literal>enter-initrd:leave-initrd:ready</literal>, i.e. calculates expected PCR values for the boot
phase in the initrd, during early boot, and during system runtime, but excluding the phases before
the initrd or when shutting down. This setting is honoured both by <command>calculate</command> and
<command>sign</command>. When used with the latter it's particularly useful for generating PCR
signatures that can only be used for unlocking resources during specific parts of the boot
process.</para>
<para>For further details about PCR boot phases, see
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />
@ -250,7 +275,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>

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

View File

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

View File

@ -31,11 +31,13 @@ static char *arg_public_key = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_current = false;
static char **arg_phase = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep);
static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
@ -63,7 +65,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --version Print version\n"
" --no-pager Do not pipe output into a pager\n"
" -c --current Use current PCR values\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
" --phase=PHASE Specify a boot phase to sign for\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n"
" --tpm2-device=PATH Use specified TPM2 device\n"
" --private-key=KEY Private key (PEM) to sign with\n"
" --public-key=KEY Public key (PEM) to validate against\n"
@ -89,6 +92,21 @@ static int help(int argc, char *argv[], void *userdata) {
return 0;
}
static char *normalize_phase(const char *s) {
_cleanup_strv_free_ char **l = NULL;
/* Let's normalize phase expressions. We split the series of colon-separated words up, then remove
* all empty ones, and glue them back together again. In other words we remove duplicate ":", as well
* as leading and trailing ones. */
l = strv_split(s, ":"); /* Split series of words */
if (!l)
return NULL;
/* Remove all empty words and glue things back together */
return strv_join(strv_remove(l, ""), ":");
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
@ -108,6 +126,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PUBLIC_KEY,
ARG_TPM2_DEVICE,
ARG_JSON,
ARG_PHASE,
};
static const struct option options[] = {
@ -127,6 +146,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
{ "public-key", required_argument, NULL, ARG_PUBLIC_KEY },
{ "json", required_argument, NULL, ARG_JSON },
{ "phase", required_argument, NULL, ARG_PHASE },
{}
};
@ -219,6 +239,20 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_PHASE: {
char *n;
n = normalize_phase(optarg);
if (!n)
return log_oom();
r = strv_consume(&arg_phase, TAKE_PTR(n));
if (r < 0)
return r;
break;
}
case '?':
return -EINVAL;
@ -241,14 +275,36 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_sections[us])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --current switch cannot be used in combination with --linux= and related switches.");
if (strv_isempty(arg_phase)) {
/* If no phases are specifically selected, pick everything from the beginning of the initrd
* to the beginning of shutdown. */
if (strv_extend_strv(&arg_phase,
STRV_MAKE("enter-initrd",
"enter-initrd:leave-initrd",
"enter-initrd:leave-initrd:ready"),
/* filter_duplicates= */ false) < 0)
return log_oom();
} else {
strv_sort(arg_phase);
strv_uniq(arg_phase);
}
_cleanup_free_ char *j = NULL;
j = strv_join(arg_phase, ", ");
if (!j)
return log_oom();
log_debug("Measuring boot phases: %s", j);
return 1;
}
/* The PCR 11 state for one specific bank */
typedef struct PcrState {
char *bank;
const EVP_MD *md;
void *value;
size_t value_size;
void *saved_value; /* A copy of the original value we calculated, used by pcr_states_save()/pcr_states_restore() to come later back to */
} PcrState;
static void pcr_state_free_all(PcrState **pcr_state) {
@ -260,6 +316,7 @@ static void pcr_state_free_all(PcrState **pcr_state) {
for (size_t i = 0; (*pcr_state)[i].value; i++) {
free((*pcr_state)[i].bank);
free((*pcr_state)[i].value);
free((*pcr_state)[i].saved_value);
}
*pcr_state = mfree(*pcr_state);
@ -320,6 +377,8 @@ static int measure_kernel(PcrState *pcr_states, size_t n) {
assert(n > 0);
assert(pcr_states);
/* Virtually measures the components of a unified kernel image into PCR 11 */
if (arg_current) {
/* Shortcut things, if we should just use the current PCR value */
@ -432,6 +491,54 @@ static int measure_kernel(PcrState *pcr_states, size_t n) {
return 0;
}
static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(pcr_states);
assert(n > 0);
/* Measure a phase string into PCR 11. This splits up the "phase" expression at colons, and then
* virtually extends each specified word into PCR 11, to model how during boot we measure a series of
* words into PCR 11, one for each phase. */
l = strv_split(phase, ":");
if (!l)
return log_oom();
STRV_FOREACH(word, l) {
size_t wl;
if (isempty(*word))
continue;
wl = strlen(*word);
for (size_t i = 0; i < n; i++) { /* For each bank */
_cleanup_free_ void *b = NULL;
int bsz;
bsz = EVP_MD_size(pcr_states[i].md);
assert(bsz > 0);
b = malloc(bsz);
if (!b)
return log_oom();
/* First hash the word itself */
if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word);
/* And then extend the PCR with the resulting hash */
r = pcr_state_extend(pcr_states + i, b, bsz);
if (r < 0)
return r;
}
}
return 0;
}
static int pcr_states_allocate(PcrState **ret) {
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
size_t n = 0;
@ -473,6 +580,39 @@ static int pcr_states_allocate(PcrState **ret) {
return (int) n;
}
static int pcr_states_save(PcrState *pcr_states, size_t n) {
assert(pcr_states);
assert(n > 0);
for (size_t i = 0; i < n; i++) {
_cleanup_free_ void *saved = NULL;
if (!pcr_states[i].value)
continue;
saved = memdup(pcr_states[i].value, pcr_states[i].value_size);
if (!saved)
return log_oom();
free_and_replace(pcr_states[i].saved_value, saved);
}
return 0;
}
static void pcr_states_restore(PcrState *pcr_states, size_t n) {
assert(pcr_states);
assert(n > 0);
for (size_t i = 0; i < n; i++) {
assert(pcr_states[i].value);
assert(pcr_states[i].saved_value);
memcpy(pcr_states[i].value, pcr_states[i].saved_value, pcr_states[i].value_size);
}
}
static int verb_calculate(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
@ -492,34 +632,64 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
for (size_t i = 0; i < n; i++) {
if (arg_json_format_flags & JSON_FORMAT_OFF) {
_cleanup_free_ char *hd = NULL;
/* Save the current state, so that we later can restore to it. This way we can measure the PCR values
* for multiple different boot phases without heaving to start from zero each time */
r = pcr_states_save(pcr_states, n);
if (r < 0)
return r;
hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
if (!hd)
return log_oom();
STRV_FOREACH(phase, arg_phase) {
printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, pcr_states[i].bank, hd);
} else {
_cleanup_(json_variant_unrefp) JsonVariant *bv = NULL;
r = measure_phase(pcr_states, n, *phase);
if (r < 0)
return r;
r = json_build(&bv,
JSON_BUILD_ARRAY(
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("pcr", JSON_BUILD_INTEGER(TPM_PCR_INDEX_KERNEL_IMAGE)),
JSON_BUILD_PAIR("hash", JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))
)
)
);
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
for (size_t i = 0; i < n; i++) {
if (arg_json_format_flags & JSON_FORMAT_OFF) {
_cleanup_free_ char *hd = NULL;
r = json_variant_set_field(&w, pcr_states[i].bank, bv);
if (r < 0)
return log_error_errno(r, "Failed to add bank info to object: %m");
if (i == 0) {
fflush(stdout);
fprintf(stderr, "%s# PCR[%" PRIu32 "] Phase <%s>%s\n",
ansi_grey(),
TPM_PCR_INDEX_KERNEL_IMAGE,
isempty(*phase) ? ":" : *phase,
ansi_normal());
fflush(stderr);
}
hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
if (!hd)
return log_oom();
printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, pcr_states[i].bank, hd);
} else {
_cleanup_(json_variant_unrefp) JsonVariant *bv = NULL, *array = NULL;
array = json_variant_ref(json_variant_by_key(w, pcr_states[i].bank));
r = json_build(&bv,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_CONDITION(!isempty(*phase), "phase", JSON_BUILD_STRING(*phase)),
JSON_BUILD_PAIR("pcr", JSON_BUILD_INTEGER(TPM_PCR_INDEX_KERNEL_IMAGE)),
JSON_BUILD_PAIR("hash", JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))
)
);
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
r = json_variant_append_array(&array, bv);
if (r < 0)
return log_error_errno(r, "Failed to append JSON object to array: %m");
r = json_variant_set_field(&w, pcr_states[i].bank, array);
if (r < 0)
return log_error_errno(r, "Failed to add bank info to object: %m");
}
}
/* Return to the original kernel measurement for the next phase calculation */
pcr_states_restore(pcr_states, n);
}
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
@ -538,7 +708,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
_cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL;
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
_cleanup_fclose_ FILE *privkeyf = NULL , *pubkeyf = NULL;
_cleanup_fclose_ FILE *privkeyf = NULL;
ESYS_TR session_handle = ESYS_TR_NONE;
TSS2_RC rc;
size_t n;
@ -549,8 +719,6 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
if (!arg_private_key)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No private key specified, use --private-key=.");
if (!arg_public_key)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No public key specified, use --public-key=.");
/* When signing we only support JSON output */
arg_json_format_flags &= ~JSON_FORMAT_OFF;
@ -559,17 +727,40 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
if (!privkeyf)
return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key);
pubkeyf = fopen(arg_public_key, "re");
if (!pubkeyf)
return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key);
privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL);
if (!privkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key);
pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL);
if (!pubkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key);
if (arg_public_key) {
_cleanup_fclose_ FILE *pubkeyf = NULL;
pubkeyf = fopen(arg_public_key, "re");
if (!pubkeyf)
return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key);
pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL);
if (!pubkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key);
} else {
_cleanup_free_ char *data = NULL;
_cleanup_fclose_ FILE *tf = NULL;
size_t sz;
/* No public key was specified, let's derive it automatically, if we can */
tf = open_memstream_unlocked(&data, &sz);
if (!tf)
return log_oom();
if (i2d_PUBKEY_fp(tf, privkey) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from private key file '%s'.", arg_private_key);
fflush(tf);
rewind(tf);
if (!d2i_PUBKEY_fp(tf, &pubkey))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse extracted public key of private key file '%s'.", arg_private_key);
}
r = pcr_states_allocate(&pcr_states);
if (r < 0)

262
src/boot/pcrphase.c Normal file
View 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);

View File

@ -37,6 +37,7 @@ TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_
TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle);
TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests);
TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket);
TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
@ -72,6 +73,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
DLSYM_ARG(Esys_LoadExternal),
DLSYM_ARG(Esys_PCR_Extend),
DLSYM_ARG(Esys_PCR_Read),
DLSYM_ARG(Esys_PolicyAuthorize),
DLSYM_ARG(Esys_PolicyAuthValue),
@ -499,6 +501,36 @@ static int tpm2_pcr_mask_good(
return good;
}
static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) {
assert(selection);
/* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
* TPM2 on a Client PC must have at least 24 PCRs. If this TPM has less, just skip over it. */
if (selection->sizeofSelect < TPM2_PCRS_MAX/8) {
log_debug("Skipping TPM2 PCR bank %s with fewer than 24 PCRs.",
strna(tpm2_pcr_bank_to_string(selection->hash)));
return false;
}
assert_cc(TPM2_PCRS_MAX % 8 == 0);
/* It's not enough to check how many PCRs there are, we also need to check that the 24 are
* enabled for this bank. Otherwise this TPM doesn't qualify. */
bool valid = true;
for (size_t j = 0; j < TPM2_PCRS_MAX/8; j++)
if (selection->pcrSelect[j] != 0xFF) {
valid = false;
break;
}
if (!valid)
log_debug("TPM2 PCR bank %s has fewer than 24 PCR bits enabled, ignoring.",
strna(tpm2_pcr_bank_to_string(selection->hash)));
return valid;
}
static int tpm2_get_best_pcr_bank(
ESYS_CONTEXT *c,
uint32_t pcr_mask,
@ -508,6 +540,7 @@ static int tpm2_get_best_pcr_bank(
TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0;
TPMI_YES_NO more;
TSS2_RC rc;
int r;
assert(c);
@ -528,38 +561,17 @@ static int tpm2_get_best_pcr_bank(
assert(pcap->capability == TPM2_CAP_PCRS);
for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) {
bool valid = true;
int good;
/* For now we are only interested in the SHA1 and SHA256 banks */
if (!IN_SET(pcap->data.assignedPCR.pcrSelections[i].hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1))
continue;
/* As per
* https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
* TPM2 on a Client PC must have at least 24 PCRs. If this TPM has less, just skip over
* it. */
if (pcap->data.assignedPCR.pcrSelections[i].sizeofSelect < TPM2_PCRS_MAX/8) {
log_debug("Skipping TPM2 PCR bank %s with fewer than 24 PCRs.",
strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash)));
r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i);
if (r < 0)
return r;
if (!r)
continue;
}
assert_cc(TPM2_PCRS_MAX % 8 == 0);
/* It's not enough to check how many PCRs there are, we also need to check that the 24 are
* enabled for this bank. Otherwise this TPM doesn't qualify. */
for (size_t j = 0; j < TPM2_PCRS_MAX/8; j++)
if (pcap->data.assignedPCR.pcrSelections[i].pcrSelect[j] != 0xFF) {
valid = false;
break;
}
if (!valid) {
log_debug("TPM2 PCR bank %s has fewer than 24 PCR bits enabled, ignoring.",
strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash)));
continue;
}
good = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask);
if (good < 0)
@ -616,6 +628,85 @@ static int tpm2_get_best_pcr_bank(
return 0;
}
int tpm2_get_good_pcr_banks(
ESYS_CONTEXT *c,
uint32_t pcr_mask,
TPMI_ALG_HASH **ret) {
_cleanup_free_ TPMI_ALG_HASH *good_banks = NULL, *fallback_banks = NULL;
_cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL;
size_t n_good_banks = 0, n_fallback_banks = 0;
TPMI_YES_NO more;
TSS2_RC rc;
int r;
assert(c);
assert(ret);
rc = sym_Esys_GetCapability(
c,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
TPM2_CAP_PCRS,
0,
1,
&more,
&pcap);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc));
assert(pcap->capability == TPM2_CAP_PCRS);
for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) {
/* Let's see if this bank is superficially OK, i.e. has at least 24 enabled registers */
r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i);
if (r < 0)
return r;
if (!r)
continue;
/* Let's now see if this bank has any of the selected PCRs actually initialized */
r = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask);
if (r < 0)
return r;
if (n_good_banks + n_fallback_banks >= INT_MAX)
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many good TPM2 banks?");
if (r) {
if (!GREEDY_REALLOC(good_banks, n_good_banks+1))
return log_oom();
good_banks[n_good_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash;
} else {
if (!GREEDY_REALLOC(fallback_banks, n_fallback_banks+1))
return log_oom();
fallback_banks[n_fallback_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash;
}
}
/* Preferably, use the good banks (i.e. the ones the PCR values are actually initialized so
* far). Otherwise use the fallback banks (i.e. which exist and are enabled, but so far not used. */
if (n_good_banks > 0) {
log_debug("Found %zu fully initialized TPM2 banks.", n_good_banks);
*ret = TAKE_PTR(good_banks);
return (int) n_good_banks;
}
if (n_fallback_banks > 0) {
log_debug("Found %zu enabled but un-initialized TPM2 banks.", n_fallback_banks);
*ret = TAKE_PTR(fallback_banks);
return (int) n_fallback_banks;
}
/* No suitable banks found. */
*ret = NULL;
return 0;
}
static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) {
struct sha256_ctx hash;

View File

@ -26,6 +26,7 @@ extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1
extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
extern TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle);
extern TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests);
extern TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
extern TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket);
extern TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3);
@ -66,6 +67,8 @@ static inline void Esys_Freep(void *p) {
sym_Esys_Free(*(void**) p);
}
int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks);
#else
struct tpm2_context;
#endif

View File

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

View File

@ -69,14 +69,27 @@ if [[ -e /usr/lib/systemd/systemd-measure ]]; then
11:sha384=5573f9b2caf55b1d0a6a701f890662d682af961899f0419cf1e2d5ea4a6a68c1f25bd4f5b8a0865eeee82af90f5cb087
11:sha512=961305d7e9981d6606d1ce97b3a9a1f92610cac033e9c39064895f0e306abc1680463d55767bd98e751eae115bdef3675a9ee1d29ed37da7885b1db45bb2555b
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 | cmp - /tmp/result
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=: | cmp - /tmp/result
cat >/tmp/result.json <<EOF
{"sha1":[{"pcr":11,"hash":"5177e4ad69db92192c10e5f80402bf81bfec8a81"}],"sha256":[{"pcr":11,"hash":"37b48bd0b222394dbe3cceff2fca4660c4b0a90ae9369ec90b42f14489989c13"}],"sha384":[{"pcr":11,"hash":"5573f9b2caf55b1d0a6a701f890662d682af961899f0419cf1e2d5ea4a6a68c1f25bd4f5b8a0865eeee82af90f5cb087"}],"sha512":[{"pcr":11,"hash":"961305d7e9981d6606d1ce97b3a9a1f92610cac033e9c39064895f0e306abc1680463d55767bd98e751eae115bdef3675a9ee1d29ed37da7885b1db45bb2555b"}]}
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=: -j | diff -u - /tmp/result.json
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 -j | diff -u - /tmp/result.json
cat >/tmp/result <<EOF
11:sha1=6765ee305db063040c454d32697d922b3d4f232b
11:sha256=21c49c1242042649e09c156546fd7d425ccc3c67359f840507b30be4e0f6f699
11:sha384=08d0b003a134878eee552070d51d58abe942f457ca85704131dd36f73728e7327ca837594bc9d5ac7de818d02a3d5dd2
11:sha512=65120f6ebc04b156421c6f3d543b2fad545363d9ca61c514205459e9c0e0b22e09c23605eae5853e38458ef3ca54e087168af8d8a882a98d220d9391e48be6d0
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=foo | cmp - /tmp/result
cat >/tmp/result.json <<EOF
{"sha1":[{"phase":"foo","pcr":11,"hash":"6765ee305db063040c454d32697d922b3d4f232b"}],"sha256":[{"phase":"foo","pcr":11,"hash":"21c49c1242042649e09c156546fd7d425ccc3c67359f840507b30be4e0f6f699"}],"sha384":[{"phase":"foo","pcr":11,"hash":"08d0b003a134878eee552070d51d58abe942f457ca85704131dd36f73728e7327ca837594bc9d5ac7de818d02a3d5dd2"}],"sha512":[{"phase":"foo","pcr":11,"hash":"65120f6ebc04b156421c6f3d543b2fad545363d9ca61c514205459e9c0e0b22e09c23605eae5853e38458ef3ca54e087168af8d8a882a98d220d9391e48be6d0"}]}
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=foo -j | diff -u - /tmp/result.json
rm /tmp/result /tmp/result.json
else
echo "/usr/lib/systemd/systemd-measure not found, skipping PCR policy test case"
fi
@ -89,7 +102,7 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
openssl rsa -pubout -in "/tmp/pcrsign-private.pem" -out "/tmp/pcrsign-public.pem"
# Sign current PCR state with it
/usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" | tee "/tmp/pcrsign.sig"
/usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: | tee "/tmp/pcrsign.sig"
dd if=/dev/urandom of=/tmp/pcrtestdata bs=1024 count=64
systemd-creds encrypt /tmp/pcrtestdata /tmp/pcrtestdata.encrypted --with-key=host+tpm2-with-public-key --tpm2-public-key="/tmp/pcrsign-public.pem"
systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" | cmp - /tmp/pcrtestdata
@ -99,7 +112,7 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" > /dev/null && { echo 'unexpected success'; exit 1; }
# Sign new PCR state, decrypting should work now.
/usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" > "/tmp/pcrsign.sig2"
/usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: > "/tmp/pcrsign.sig2"
systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig2" | cmp - /tmp/pcrtestdata
# Now, do the same, but with a cryptsetup binding
@ -121,7 +134,7 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
# But once we sign the current PCRs, we should be able to unlock again
/usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" > "/tmp/pcrsign.sig3"
/usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: > "/tmp/pcrsign.sig3"
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume2
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1

View File

@ -260,6 +260,10 @@ in_units = [
'sysinit.target.wants/ initrd-root-fs.target.wants/'],
['user-runtime-dir@.service', ''],
['user@.service', ''],
['systemd-pcrphase-initrd.service', 'HAVE_GNU_EFI ENABLE_INITRD',
'initrd.target.wants/'],
['systemd-pcrphase.service', 'HAVE_GNU_EFI',
'sysinit.target.wants/'],
]
add_wants = []

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 PCR Barrier (initrd)
Documentation=man:systemd-pcrphase-initrd.service(8)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target initrd-switch-root.target systemd-sysext.service
AssertPathExists=/etc/initrd-release
ConditionSecurity=tpm2
ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase enter-initrd
ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase leave-initrd

View File

@ -0,0 +1,23 @@
# 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 PCR Barrier (Host)
Documentation=man:systemd-pcrphase.service(8)
After=remote-fs.target remote-cryptsetup.target
Before=systemd-user-sessions.service
AssertPathExists=!/etc/initrd-release
ConditionSecurity=tpm2
ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase ready
ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase shutdown