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

Merge pull request #24351 from poettering/pcr-sign

support for signed TPM2 PCR policies in cryptsetup/cryptenrolls/credentials
This commit is contained in:
Frantisek Sumsal 2022-09-08 19:07:04 +00:00 committed by GitHub
commit 8432b0cd20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2019 additions and 584 deletions

View File

@ -685,6 +685,21 @@
when TPM2 enrollment metadata is not available.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-signature=</option></term>
<listitem><para>Takes an absolute path to a TPM2 PCR JSON signature file, as produced by the
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool. This permits locking LUKS2 volumes to any PCR values for which a valid signature matching a
public key specified at key enrollment time can be provided. See
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for details on enrolling TPM2 PCR public keys. If this option is not specified but it is attempted to
unlock a LUKS2 volume with a signed TPM2 PCR enrollment a suitable signature file
<filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
<filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this
order).</para></listitem>
</varlistentry>
<varlistentry>
<term><option>token-timeout=</option></term>

View File

@ -333,6 +333,40 @@
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-public-key=</option><arg>PATH</arg></term>
<term><option>--tpm2-public-key-pcrs=</option><arg rep="repeat">PCR</arg></term>
<listitem><para>Configures a TPM2 signed PCR policy to bind encryption to, for use with the
<command>encrypt</command> command. The <option>--tpm2-public-key=</option> option accepts a path to
a PEM encoded RSA public key, to bind the encryption to. If this is not specified explicitly, but a
file <filename>tpm2-pcr-public-key.pem</filename> exists in one of the directories
<filename>/etc/systemd/</filename>, <filename>/run/systemd/</filename>,
<filename>/usr/lib/systemd/</filename> (searched in this order), it is automatically used. The
<option>--tpm2-public-key-pcrs=</option> option takes a list of TPM2 PCR indexes to bind to (same
syntax as <option>--tpm2-pcrs=</option> described above). If not specified defaults to 11 (i.e. this
binds the policy to any unified kernel image for which a PCR signature can be provided).</para>
<para>Note the difference between <option>--tpm2-pcrs=</option> and
<option>--tpm2-public-key-pcrs=</option>: the former binds decryption to the current, specific PCR
values; the latter binds decryption to any set of PCR values for which a signature by the specified
public key can be provided. The latter is hence more useful in scenarios where software updates shall
be possible without losing access to all previously encrypted secrets.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-signature=</option><arg>PATH</arg></term>
<listitem><para>Takes a path to a TPM2 PCR signature file as generated by the
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool and that may be used to allow the <command>decrypt</command> command to decrypt credentials that
are bound to specific signed PCR values. If this this is not specified explicitly, and a credential
with a signed PCR policy is attempted to be decrypted, a suitable signature file
<filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
<filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and
used.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--quiet</option></term>
<term><option>-q</option></term>
@ -413,7 +447,8 @@ SetCredentialEncrypted=mysql-password: \
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -347,16 +347,52 @@
to <literal>no</literal>. Despite being called PIN, any character can be used, not just numbers.
</para>
<para>Note that incorrect PIN entry when unlocking increments the
TPM dictionary attack lockout mechanism, and may lock out users for a prolonged time, depending on
its configuration. The lockout mechanism is a global property of the TPM,
<command>systemd-cryptenroll</command> does not control or configure the lockout mechanism. You may
use tpm2-tss tools to inspect or configure the dictionary attack lockout, with
<citerefentry project='mankier'><refentrytitle>tpm2_getcap</refentrytitle><manvolnum>1</manvolnum></citerefentry> and
<citerefentry project='mankier'><refentrytitle>tpm2_dictionarylockout</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<para>Note that incorrect PIN entry when unlocking increments the TPM dictionary attack lockout
mechanism, and may lock out users for a prolonged time, depending on its configuration. The lockout
mechanism is a global property of the TPM, <command>systemd-cryptenroll</command> does not control or
configure the lockout mechanism. You may use tpm2-tss tools to inspect or configure the dictionary
attack lockout, with <citerefentry
project='mankier'><refentrytitle>tpm2_getcap</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and <citerefentry
project='mankier'><refentrytitle>tpm2_dictionarylockout</refentrytitle><manvolnum>1</manvolnum></citerefentry>
commands, respectively.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-public-key=</option><arg>PATH</arg></term>
<term><option>--tpm2-public-key-pcrs=</option><arg rep="repeat">PCR</arg></term>
<term><option>--tpm2-signature=</option><arg>PATH</arg></term>
<listitem><para>Configures a TPM2 signed PCR policy to bind encryption to. The
<option>--tpm2-public-key=</option> option accepts a path to a PEM encoded RSA public key, to bind
the encryption to. If this is not specified explicitly, but a file
<filename>tpm2-pcr-public-key.pem</filename> exists in one of the directories
<filename>/etc/systemd/</filename>, <filename>/run/systemd/</filename>,
<filename>/usr/lib/systemd/</filename> (searched in this order), it is automatically used. The
<option>--tpm2-public-key-pcrs=</option> option takes a list of TPM2 PCR indexes to bind to (same
syntax as <option>--tpm2-pcrs=</option> described above). If not specified defaults to 11 (i.e. this
binds the policy to any unified kernel image for which a PCR signature can be provided).</para>
<para>Note the difference between <option>--tpm2-pcrs=</option> and
<option>--tpm2-public-key-pcrs=</option>: the former binds decryption to the current, specific PCR
values; the latter binds decryption to any set of PCR values for which a signature by the specified
public key can be provided. The latter is hence more useful in scenarios where software updates shell
be possible without losing access to all previously encrypted LUKS2 volumes.</para>
<para>The <option>--tpm2-signature=</option> option takes a path to a TPM2 PCR signature file
as generated by the
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool. If this this is not specified explicitly a suitable signature file
<filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
<filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and
used. If a signature file is specified or found it is used to verify if the volume can be unlocked
with it given the current PCR state, before the new slot is written to disk. This is intended as
safety net to ensure that access to a volume is not lost if a public key is enrolled for which no
valid signature for the current PCR state is available. If the supplied signature does not unlock the
current PCR state and public key combination, no slot is enrolled and the operation will fail. If no
signature file is specified or found no such safety verification is done.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--wipe-slot=</option><arg rep="repeat">SLOT</arg></term>
@ -411,7 +447,8 @@
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -17,7 +17,7 @@
<refnamediv>
<refname>systemd-measure</refname>
<refpurpose>Pre-calculate expected TPM2 PCR values for booted unified kernel images</refpurpose>
<refpurpose>Pre-calculate and sign expected TPM2 PCR values for booted unified kernel images</refpurpose>
</refnamediv>
<refsynopsisdiv>
@ -32,15 +32,17 @@
<para>Note: this command is experimental for now. While it is likely to become a regular component of
systemd, it might still change in behaviour and interface.</para>
<para><command>systemd-measure</command> is a tool that may be used to pre-calculate the expected TPM2
PCR 11 values that should be seen when a unified Linux kernel image based on
<para><command>systemd-measure</command> is a tool that may be used to pre-calculate and sign the
expected TPM2 PCR 11 values that should be seen when a unified Linux kernel image based on
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> is
booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file,
kernel command line file,
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file, and
boot splash file that make up the unified kernel image, and determines the PCR values expected to be in
place after booting the image. Calculation starts with a zero-initialized PCR 11, and is executed in a
fashion compatible with what <filename>systemd-stub</filename> does at boot.</para>
fashion compatible with what <filename>systemd-stub</filename> does at boot. The result may optionally be
signed cryptographically, to allow TPM2 policies that can only be unlocked if a certain set of kernels is
booted, for which such a PCR signature can be provided.</para>
</refsect1>
<refsect1>
@ -61,11 +63,30 @@
<varlistentry>
<term><command>calculate</command></term>
<listitem><para>Pre-calculate the expected value seen in PCR register 11 after boot-up of a unified
<listitem><para>Pre-calculate the expected values seen in PCR register 11 after boot-up of a unified
kernel image consisting of the components specified with <option>--linux=</option>,
<option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>,
<option>--splash=</option>, <option>--dtb=</option>, see below. Only <option>--linux=</option> is
mandatory.</para></listitem>
mandatory. (Alternatively, specify <option>--current</option> to use the current values of PCR
register 11 instead.)</para></listitem>
</varlistentry>
<varlistentry>
<term><command>sign</command></term>
<listitem><para>As with the <command>calculate</command> command, pre-calculate the expected value
seen in TPM2 PCR register 11 after boot-up of a unified kernel image. Then, cryptographically sign
the resulting values with the private/public key pair (RSA) configured via
<option>--private-key=</option> and <option>--public-key=</option>. This will write a JSON object to
standard output that contains signatures for all specified PCR banks (see
<option>--pcr-bank=</option>) below, which may be used to unlock encrypted credentials (see
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>) or
LUKS volumes (see
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>). This
allows binding secrets to a set of kernels for which such PCR 11 signatures can be provided.</para>
<para>Note that a TPM2 device must be available for this signing to take place, even though the
result is not tied to any TPM2 device or its state.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -84,29 +105,47 @@
<term><option>--splash=PATH</option></term>
<term><option>--dtb=PATH</option></term>
<listitem><para>When used with the <command>calculate</command> verb, configures the files to read
the unified kernel image components from. Each option corresponds with the equally named section in
the unified kernel PE file. The <option>--linux=</option> switch expects the path to the ELF kernel
file that the unified PE kernel will wrap. All switches except <option>--linux=</option> are
optional. Each option may be used at most once.</para></listitem>
<listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
configures the files to read the unified kernel image components from. Each option corresponds with
the equally named section in the unified kernel PE file. The <option>--linux=</option> switch expects
the path to the ELF kernel file that the unified PE kernel will wrap. All switches except
<option>--linux=</option> are optional. Each option may be used at most once.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--current</option></term>
<listitem><para>When used with the <command>calculate</command> verb, takes the PCR 11 values
currently in effect for the system (which should typically reflect the hashes of the currently booted
kernel). This can be used in place of <option>--linux=</option> and the other switches listed
above.</para></listitem>
<listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
takes the PCR 11 values currently in effect for the system (which should typically reflect the hashes
of the currently booted kernel). This can be used in place of <option>--linux=</option> and the other
switches listed above.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--bank=DIGEST</option></term>
<listitem><para>Controls the PCR banks to pre-calculate the PCR values for in case
<command>calculate</command> is invoked , or the banks to show in the <command>status</command>
output. May be used more then once to specify multiple banks. If not specified, defaults to the four
banks <literal>sha1</literal>, <literal>sha256</literal>, <literal>sha384</literal>,
<literal>sha512</literal>.</para></listitem>
<command>calculate</command> or <command>sign</command> is invoked , or the banks to show in the
<command>status</command> output. May be used more then once to specify multiple banks. If not
specified, defaults to the four banks <literal>sha1</literal>, <literal>sha256</literal>,
<literal>sha384</literal>, <literal>sha512</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--private-key=PATH</option></term>
<term><option>--public-key=PATH</option></term>
<listitem><para>These switches take paths to a pair of PEM encoded RSA key files, for use with
the <command>sign</command> command.</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="json" />
@ -133,7 +172,7 @@
foo.efi
# systemd-measure calculate \
--linux=vmlinux \
--osrel=os-release \
--osrel=os-release.txt \
--cmdline=cmdline.txt \
--initrd=initrd.cpio \
--splash=splash.bmp \
@ -144,6 +183,41 @@
11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da
</programlisting>
</example>
<example>
<title>Generate a private/public key pair, and a unified kernel image, and a TPM PCR 11 signature for it</title>
<programlisting># openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out tpm2-pcr-private.pem
# openssl rsa -pubout -in tpm2-pcr-private.pem -out tpm2-pcr-public.pem
# objcopy \
--add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
--add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
--add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
--add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \
--add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \
--add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \
/usr/lib/systemd/boot/efi/linuxx64.efi.stub \
foo.efi
# systemd-measure sign \
--linux=vmlinux \
--osrel=os-release.txt \
--cmdline=cmdline.txt \
--initrd=initrd.cpio \
--splash=splash.bmp \
--dtb=devicetree.dtb \
--bank=sha1 \
--bank=sha256 \
--private-key=tpm2-pcr-private.pem \
--public-key=tpm2-pcr-public.pem > tpm2-pcr-signature.json</programlisting>
<para>Later on, enroll the signed PCR policy on a LUKS volume:</para>
<programlisting># systemd-cryptenroll --tpm2-device=auto --tpm2-public-key=tpm2-pcr-public.pem --tpm2-signature=tpm2-pcr-signature.json /dev/sda5</programlisting>
<para>And then unlock the device with the signature:</para>
<programlisting># /usr/lib/systemd/systemd-cryptsetup attach myvolume /dev/sda5 - tpm2-device=auto,tpm2-signature=/path/to/tpm2-pcr-signature.json</programlisting>
</example>
</refsect1>
<refsect1>
@ -157,7 +231,9 @@
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</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>
</para>
</refsect1>

View File

@ -323,6 +323,15 @@
and have the same effect on partitions where TPM2 enrollment is requested.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-public-key=</option><arg>PATH</arg></term>
<term><option>--tpm2-public-key-pcrs=</option><arg rep="repeat">PCR</arg></term>
<listitem><para>Configures a TPM2 signed PCR policy to bind encryption to. See
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for details on these two options.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="no-pager" />

View File

@ -14,6 +14,7 @@
#include "parse-argument.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "sha256.h"
#include "terminal-util.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
@ -24,11 +25,17 @@
static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
static char **arg_banks = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static char *arg_tpm2_device = NULL;
static char *arg_private_key = NULL;
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_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 inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
@ -46,10 +53,11 @@ static int help(int argc, char *argv[], void *userdata) {
return log_oom();
printf("%1$s [OPTIONS...] COMMAND ...\n"
"\n%5$sPre-calculate PCR hash for kernel image.%6$s\n"
"\n%5$sPre-calculate and sign PCR hash for a unified kernel image.%6$s\n"
"\n%3$sCommands:%4$s\n"
" status Show current PCR values\n"
" calculate Calculate expected PCR values\n"
" status Show current PCR values\n"
" calculate Calculate expected PCR values\n"
" sign Calculate and sign expected PCR values\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
@ -62,6 +70,9 @@ static int help(int argc, char *argv[], void *userdata) {
" --dtb=PATH Path to Devicetree file\n"
" -c --current Use current PCR values\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\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"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
"\nSee the %2$s for details.\n",
@ -88,6 +99,9 @@ static int parse_argv(int argc, char *argv[]) {
_ARG_SECTION_LAST,
ARG_DTB = _ARG_SECTION_LAST,
ARG_BANK,
ARG_PRIVATE_KEY,
ARG_PUBLIC_KEY,
ARG_TPM2_DEVICE,
ARG_JSON,
};
@ -103,6 +117,9 @@ static int parse_argv(int argc, char *argv[]) {
{ "dtb", required_argument, NULL, ARG_DTB },
{ "current", no_argument, NULL, 'c' },
{ "bank", required_argument, NULL, ARG_BANK },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
{ "public-key", required_argument, NULL, ARG_PUBLIC_KEY },
{ "json", required_argument, NULL, ARG_JSON },
{}
};
@ -155,6 +172,36 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_PRIVATE_KEY:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_private_key);
if (r < 0)
return r;
break;
case ARG_PUBLIC_KEY:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key);
if (r < 0)
return r;
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 'j':
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
break;
@ -379,14 +426,9 @@ static int measure_pcr(PcrState *pcr_states, size_t n) {
return 0;
}
static int verb_calculate(int argc, char *argv[], void *userdata) {
static int pcr_states_allocate(PcrState **ret) {
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
size_t n = 0;
int r;
if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
pcr_states = new0(PcrState, strv_length(arg_banks) + 1);
if (!pcr_states)
@ -421,6 +463,25 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
};
}
*ret = TAKE_PTR(pcr_states);
return (int) n;
}
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;
size_t n;
int r;
if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
r = pcr_states_allocate(&pcr_states);
if (r < 0)
return r;
n = (size_t) r;
r = measure_pcr(pcr_states, n);
if (r < 0)
return r;
@ -466,6 +527,235 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
return 0;
}
static TPM2_ALG_ID convert_evp_md_name_to_tpm2_alg(const EVP_MD *md) {
const char *mdname;
mdname = EVP_MD_name(md);
if (strcaseeq(mdname, "sha1"))
return TPM2_ALG_SHA1;
if (strcaseeq(mdname, "sha256"))
return TPM2_ALG_SHA256;
if (strcaseeq(mdname, "sha384"))
return TPM2_ALG_SHA384;
if (strcaseeq(mdname, "sha512"))
return TPM2_ALG_SHA512;
return TPM2_ALG_ERROR;
}
static int verb_sign(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_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;
ESYS_TR session_handle = ESYS_TR_NONE;
TSS2_RC rc;
size_t n;
int r;
if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
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;
privkeyf = fopen(arg_private_key, "re");
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);
r = pcr_states_allocate(&pcr_states);
if (r < 0)
return r;
n = (size_t) r;
r = measure_pcr(pcr_states, n);
if (r < 0)
return r;
r = dlopen_tpm2();
if (r < 0)
return r;
r = tpm2_context_init(arg_tpm2_device, &c);
if (r < 0)
return r;
for (size_t i = 0; i < n; i++) {
static const TPMT_SYM_DEF symmetric = {
.algorithm = TPM2_ALG_AES,
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
};
PcrState *p = pcr_states + i;
rc = sym_Esys_StartAuthSession(
c.esys_context,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
NULL,
TPM2_SE_TRIAL,
&symmetric,
TPM2_ALG_SHA256,
&session_handle);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
/* Generate a single hash value from the PCRs included in our policy. Given that that's
* exactly one, the calculation is trivial. */
TPM2B_DIGEST intermediate_digest = {
.size = SHA256_DIGEST_SIZE,
};
assert(sizeof(intermediate_digest.buffer) >= SHA256_DIGEST_SIZE);
sha256_direct(p->value, p->value_size, intermediate_digest.buffer);
TPM2_ALG_ID tpmalg = convert_evp_md_name_to_tpm2_alg(p->md);
if (tpmalg == TPM2_ALG_ERROR) {
r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported PCR bank");
goto finish;
}
TPML_PCR_SELECTION pcr_selection;
tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection);
rc = sym_Esys_PolicyPCR(
c.esys_context,
session_handle,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
&intermediate_digest,
&pcr_selection);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to push PCR policy into TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
_cleanup_(Esys_Freep) TPM2B_DIGEST *pcr_policy_digest = NULL;
rc = sym_Esys_PolicyGetDigest(
c.esys_context,
session_handle,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
&pcr_policy_digest);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle);
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL;
mdctx = EVP_MD_CTX_new();
if (!mdctx) {
r = log_oom();
goto finish;
}
if (EVP_DigestSignInit(mdctx, NULL, p->md, NULL, privkey) != 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to initialize signature context.");
goto finish;
}
if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest->buffer, pcr_policy_digest->size) != 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to sign data.");
goto finish;
}
size_t ss;
if (EVP_DigestSignFinal(mdctx, NULL, &ss) != 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to finalize signature");
goto finish;
}
_cleanup_free_ void *sig = malloc(ss);
if (!ss) {
r = log_oom();
goto finish;
}
if (EVP_DigestSignFinal(mdctx, sig, &ss) != 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to acquire signature data");
goto finish;
}
_cleanup_free_ void *pubkey_fp = NULL;
size_t pubkey_fp_size = 0;
r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
if (r < 0)
goto finish;
_cleanup_(json_variant_unrefp) JsonVariant *bv = NULL, *a = NULL;
r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &a);
if (r < 0) {
log_error_errno(r, "Failed to build JSON PCR mask array: %m");
goto finish;
}
r = json_build(&bv, JSON_BUILD_ARRAY(
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("pcrs", JSON_BUILD_VARIANT(a)), /* PCR mask */
JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest->buffer, pcr_policy_digest->size)), /* TPM2 policy hash that is signed */
JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss))))); /* signature data */
if (r < 0) {
log_error_errno(r, "Failed to build JSON object: %m");
goto finish;
}
r = json_variant_set_field(&v, p->bank, bv);
if (r < 0) {
log_error_errno(r, "Failed to add JSON field: %m");
goto finish;
}
}
if (!v)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to find a single working PCR bank.");
if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
pager_open(arg_pager_flags);
json_variant_dump(v, arg_json_format_flags, stdout, NULL);
r = 0;
finish:
session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle);
return r;
}
static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
_cleanup_free_ char *s = NULL;
uint32_t v;
@ -643,6 +933,7 @@ static int measure_main(int argc, char *argv[]) {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "calculate", VERB_ANY, 1, 0, verb_calculate },
{ "sign", VERB_ANY, 1, 0, verb_sign },
{}
};

View File

@ -2746,7 +2746,7 @@ static int load_credential(
_cleanup_free_ void *plaintext = NULL;
size_t plaintext_size = 0;
r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, &plaintext, &plaintext_size);
if (r < 0)
return r;
@ -2920,7 +2920,7 @@ static int acquire_credentials(
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
if (sc->encrypted) {
r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, sc->data, sc->size, &plaintext, &size);
r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, NULL, sc->data, sc->size, &plaintext, &size);
if (r < 0)
return r;

View File

@ -21,6 +21,7 @@
#include "stat-util.h"
#include "string-table.h"
#include "terminal-util.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
#include "verbs.h"
@ -43,6 +44,9 @@ static int arg_newline = -1;
static sd_id128_t arg_with_key = _CRED_AUTO;
static const char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_public_key = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
static char *arg_tpm2_signature = NULL;
static const char *arg_name = NULL;
static bool arg_name_any = false;
static usec_t arg_timestamp = USEC_INFINITY;
@ -50,6 +54,9 @@ static usec_t arg_not_after = USEC_INFINITY;
static bool arg_pretty = false;
static bool arg_quiet = false;
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
static const char* transcode_mode_table[_TRANSCODE_MAX] = {
[TRANSCODE_OFF] = "off",
[TRANSCODE_BASE64] = "base64",
@ -418,6 +425,7 @@ static int verb_cat(int argc, char **argv, void *userdata) {
*cn,
timestamp,
arg_tpm2_device,
arg_tpm2_signature,
data, size,
&plaintext, &plaintext_size);
if (r < 0)
@ -490,6 +498,8 @@ static int verb_encrypt(int argc, char **argv, void *userdata) {
arg_not_after,
arg_tpm2_device,
arg_tpm2_pcr_mask,
arg_tpm2_public_key,
arg_tpm2_public_key_pcr_mask,
plaintext, plaintext_size,
&output, &output_size);
if (r < 0)
@ -577,6 +587,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) {
name,
timestamp,
arg_tpm2_device,
arg_tpm2_signature,
input, input_size,
&plaintext, &plaintext_size);
if (r < 0)
@ -682,7 +693,13 @@ static int verb_help(int argc, char **argv, void *userdata) {
" --tpm2-device=PATH\n"
" Pick TPM2 device\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" Specify TPM2 PCRs to seal against\n"
" Specify TPM2 PCRs to seal against (fixed hash)\n"
" --tpm2-public-key=PATH\n"
" Specify PEM certificate to seal against\n"
" --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
" Specify TPM2 PCRs to seal against (public key)\n"
" --tpm2-signature=PATH\n"
" Specify signature for public key PCR policy\n"
" -q --quiet Suppress output for 'has-tpm2' verb\n"
"\nSee the %2$s for details.\n"
, program_invocation_short_name
@ -707,28 +724,34 @@ static int parse_argv(int argc, char *argv[]) {
ARG_WITH_KEY,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
ARG_TPM2_PUBLIC_KEY,
ARG_TPM2_PUBLIC_KEY_PCRS,
ARG_TPM2_SIGNATURE,
ARG_NAME,
ARG_TIMESTAMP,
ARG_NOT_AFTER,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "json", required_argument, NULL, ARG_JSON },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "transcode", required_argument, NULL, ARG_TRANSCODE },
{ "newline", required_argument, NULL, ARG_NEWLINE },
{ "pretty", no_argument, NULL, 'p' },
{ "with-key", required_argument, NULL, ARG_WITH_KEY },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "name", required_argument, NULL, ARG_NAME },
{ "timestamp", required_argument, NULL, ARG_TIMESTAMP },
{ "not-after", required_argument, NULL, ARG_NOT_AFTER },
{ "quiet", no_argument, NULL, 'q' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "json", required_argument, NULL, ARG_JSON },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "transcode", required_argument, NULL, ARG_TRANSCODE },
{ "newline", required_argument, NULL, ARG_NEWLINE },
{ "pretty", no_argument, NULL, 'p' },
{ "with-key", required_argument, NULL, ARG_WITH_KEY },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{ "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
{ "name", required_argument, NULL, ARG_NAME },
{ "timestamp", required_argument, NULL, ARG_TIMESTAMP },
{ "not-after", required_argument, NULL, ARG_NOT_AFTER },
{ "quiet", no_argument, NULL, 'q' },
{}
};
@ -808,8 +831,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_with_key = CRED_AES256_GCM_BY_HOST;
else if (streq(optarg, "tpm2"))
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
else if (streq(optarg, "tpm2-with-public-key"))
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK;
else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
else if (streq(optarg, "tpm2-absent"))
arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
else
@ -832,13 +859,34 @@ static int parse_argv(int argc, char *argv[]) {
arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
break;
case ARG_TPM2_PCRS:
case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */
r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask);
if (r < 0)
return r;
break;
case ARG_TPM2_PUBLIC_KEY:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
if (r < 0)
return r;
break;
case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */
r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
if (r < 0)
return r;
break;
case ARG_TPM2_SIGNATURE:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
if (r < 0)
return r;
break;
case ARG_NAME:
if (isempty(optarg)) {
arg_name = NULL;
@ -881,6 +929,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
return 1;
}

View File

@ -4,6 +4,7 @@
#include "ask-password-api.h"
#include "cryptenroll-tpm2.h"
#include "env-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "memory-util.h"
@ -130,14 +131,17 @@ int enroll_tpm2(struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,
const char *device,
uint32_t pcr_mask,
uint32_t hash_pcr_mask,
const char *pubkey_path,
uint32_t pubkey_pcr_mask,
const char *signature_path,
bool use_pin) {
_cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
size_t secret_size, secret2_size, blob_size, hash_size;
_cleanup_free_ void *blob = NULL, *hash = NULL;
size_t secret_size, blob_size, hash_size, pubkey_size = 0;
_cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL;
uint16_t pcr_bank, primary_alg;
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
@ -147,7 +151,8 @@ int enroll_tpm2(struct crypt_device *cd,
assert(cd);
assert(volume_key);
assert(volume_key_size > 0);
assert(TPM2_PCR_MASK_VALID(pcr_mask));
assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
assert_se(node = crypt_get_device_name(cd));
@ -157,7 +162,35 @@ int enroll_tpm2(struct crypt_device *cd,
return r;
}
r = tpm2_seal(device, pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size);
if (r < 0) {
if (pubkey_path || signature_path || r != -ENOENT)
return log_error_errno(r, "Failed read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
pubkey_pcr_mask = 0;
} else {
/* Also try to load the signature JSON object, to verify that our enrollment will work. This is optional however. */
r = tpm2_load_pcr_signature(signature_path, &signature_json);
if (r < 0) {
if (signature_path || r != -ENOENT)
return log_debug_errno(r, "Failed to read TPM PCR signature: %m");
log_debug_errno(r, "Failed to read TPM2 PCR signature, proceeding without: %m");
}
}
r = tpm2_seal(device,
hash_pcr_mask,
pubkey, pubkey_size,
pubkey_pcr_mask,
pin_str,
&secret, &secret_size,
&blob, &blob_size,
&hash, &hash_size,
&pcr_bank,
&primary_alg);
if (r < 0)
return r;
@ -172,14 +205,29 @@ int enroll_tpm2(struct crypt_device *cd,
return r; /* return existing keyslot, so that wiping won't kill it */
}
/* Quick verification that everything is in order, we are not in a hurry after all. */
log_debug("Unsealing for verification...");
r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, pin_str, &secret2, &secret2_size);
if (r < 0)
return r;
/* Quick verification that everything is in order, we are not in a hurry after all.*/
if (!pubkey || signature_json) {
_cleanup_(erase_and_freep) void *secret2 = NULL;
size_t secret2_size;
if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
log_debug("Unsealing for verification...");
r = tpm2_unseal(device,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
signature_json,
pin_str,
primary_alg,
blob, blob_size,
hash, hash_size,
&secret2, &secret2_size);
if (r < 0)
return r;
if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
}
/* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
r = base64mem(secret, secret_size, &base64_encoded);
@ -200,7 +248,17 @@ int enroll_tpm2(struct crypt_device *cd,
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, flags, &v);
r = tpm2_make_luks2_json(
keyslot,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
primary_alg,
blob, blob_size,
hash, hash_size,
flags,
&v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");

View File

@ -7,9 +7,9 @@
#include "log.h"
#if HAVE_TPM2
int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin);
int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin);
#else
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin) {
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 key enrollment not supported.");
}

View File

@ -26,6 +26,7 @@
#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
@ -35,6 +36,9 @@ static char *arg_fido2_device = NULL;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static bool arg_tpm2_pin = false;
static char *arg_tpm2_public_key = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
static char *arg_tpm2_signature = NULL;
static char *arg_node = NULL;
static int *arg_wipe_slots = NULL;
static size_t arg_n_wipe_slots = 0;
@ -53,6 +57,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
STATIC_DESTRUCTOR_REGISTER(arg_wipe_slots, freep);
@ -114,6 +120,13 @@ static int help(void) {
" Enroll a TPM2 device\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" Specify TPM2 PCRs to seal against\n"
" --tpm2-public-key=PATH\n"
" Enroll signed TPM2 PCR policy against PEM public key\n"
" --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
" Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n"
" --tpm2-signature=PATH\n"
" Validate public key enrollment works with JSON signature\n"
" file\n"
" --tpm2-with-pin=BOOL\n"
" Whether to require entering a PIN to unlock the volume\n"
" --wipe-slot=SLOT1,SLOT2,…\n"
@ -138,6 +151,9 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FIDO2_DEVICE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
ARG_TPM2_PUBLIC_KEY,
ARG_TPM2_PUBLIC_KEY_PCRS,
ARG_TPM2_SIGNATURE,
ARG_TPM2_PIN,
ARG_WIPE_SLOT,
ARG_FIDO2_WITH_PIN,
@ -147,21 +163,24 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "password", no_argument, NULL, ARG_PASSWORD },
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
{ "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
{ "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
{ "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-with-pin", required_argument, NULL, ARG_TPM2_PIN },
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "password", no_argument, NULL, ARG_PASSWORD },
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
{ "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
{ "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
{ "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{ "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
{ "tpm2-with-pin", required_argument, NULL, ARG_TPM2_PIN },
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{}
};
@ -329,6 +348,27 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_TPM2_PUBLIC_KEY:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
if (r < 0)
return r;
break;
case ARG_TPM2_PUBLIC_KEY_PCRS:
r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
if (r < 0)
return r;
break;
case ARG_TPM2_SIGNATURE:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
if (r < 0)
return r;
break;
case ARG_WIPE_SLOT: {
const char *p = optarg;
@ -405,6 +445,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
return 1;
}
@ -615,7 +657,7 @@ static int run(int argc, char *argv[]) {
break;
case ENROLL_TPM2:
slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_pin);
slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin);
break;
case _ENROLL_TYPE_INVALID:

View File

@ -40,27 +40,28 @@ _public_ int cryptsetup_token_open_pin(
int token /* is always >= 0 */,
const char *pin,
size_t pin_size,
char **password, /* freed by cryptsetup_token_buffer_free */
size_t *password_len,
char **ret_password, /* freed by cryptsetup_token_buffer_free */
size_t *ret_password_len,
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
int r;
const char *json;
size_t blob_size, policy_hash_size, decrypted_key_size;
uint32_t pcr_mask;
uint16_t pcr_bank, primary_alg;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
_cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size;
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
systemd_tpm2_plugin_params params = {
.search_pcr_mask = UINT32_MAX
};
_cleanup_free_ void *blob = NULL, *policy_hash = NULL;
_cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL;
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags = 0;
const char *json;
int r;
assert(!pin || pin_size);
assert(password);
assert(password_len);
assert(token >= 0);
assert(!pin || pin_size > 0);
assert(ret_password);
assert(ret_password_len);
/* This must not fail at this moment (internal error) */
r = crypt_token_json_get(cd, token, &json);
@ -74,32 +75,44 @@ _public_ int cryptsetup_token_open_pin(
if (usrptr)
params = *(systemd_tpm2_plugin_params *)usrptr;
TPM2Flags flags = 0;
r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags);
r = json_parse(json, 0, &v, NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m");
r = tpm2_parse_luks2_json(
v,
NULL,
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob,
&blob_size,
&policy_hash,
&policy_hash_size,
&flags);
if (r < 0)
return log_debug_open_error(cd, r);
/* should not happen since cryptsetup_token_validate have passed */
r = unbase64mem(base64_blob, SIZE_MAX, &blob, &blob_size);
if (r < 0)
return log_debug_open_error(cd, r);
/* should not happen since cryptsetup_token_validate have passed */
r = unhexmem(hex_policy_hash, SIZE_MAX, &policy_hash, &policy_hash_size);
if (r < 0)
return log_debug_open_error(cd, r);
if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask)
return crypt_log_debug_errno(cd, ENXIO, "PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", hash_pcr_mask, params.search_pcr_mask);
r = acquire_luks2_key(
pcr_mask,
pcr_bank,
primary_alg,
params.device,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
params.signature_path,
pin_string,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
flags,
pin_string,
&decrypted_key,
&decrypted_key_size);
if (r < 0)
@ -111,8 +124,8 @@ _public_ int cryptsetup_token_open_pin(
return log_debug_open_error(cd, r);
/* free'd automatically by libcryptsetup */
*password_len = strlen(base64_encoded);
*password = TAKE_PTR(base64_encoded);
*ret_password_len = strlen(base64_encoded);
*ret_password = TAKE_PTR(base64_encoded);
return 0;
}
@ -133,11 +146,11 @@ _public_ int cryptsetup_token_open_pin(
_public_ int cryptsetup_token_open(
struct crypt_device *cd, /* is always LUKS2 context */
int token /* is always >= 0 */,
char **password, /* freed by cryptsetup_token_buffer_free */
size_t *password_len,
char **ret_password, /* freed by cryptsetup_token_buffer_free */
size_t *ret_password_len,
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr);
return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr);
}
/*
@ -156,45 +169,66 @@ _public_ void cryptsetup_token_dump(
struct crypt_device *cd /* is always LUKS2 context */,
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
int r;
TPM2Flags flags = 0;
uint32_t pcr_mask;
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
_cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
size_t blob_size, policy_hash_size, pubkey_size;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
size_t decoded_blob_size;
_cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL,
*pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL;
_cleanup_free_ void *decoded_blob = NULL;
TPM2Flags flags = 0;
int r;
assert(json);
r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags);
r = json_parse(json, 0, &v, NULL, NULL);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m");
for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
if ((pcr_mask & (UINT32_C(1) << i)) &&
((r = strextendf_with_separator(&pcrs_str, ", ", "%" PRIu32, i)) < 0))
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
}
r = tpm2_parse_luks2_json(
v,
NULL,
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob,
&blob_size,
&policy_hash,
&policy_hash_size,
&flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
r = unbase64mem(base64_blob, SIZE_MAX, &decoded_blob, &decoded_blob_size);
r = pcr_mask_to_string(hash_pcr_mask, &hash_pcrs_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
r = pcr_mask_to_string(pubkey_pcr_mask, &pubkey_pcrs_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
r = crypt_dump_buffer_to_hex_string(decoded_blob, decoded_blob_size, &blob_str);
r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
r = crypt_dump_hex_string(hex_policy_hash, &policy_hash_str);
r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
crypt_log(cd, "\ttpm2-pcrs: %s\n", strna(pcrs_str));
crypt_log(cd, "\ttpm2-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
}
/*

View File

@ -13,19 +13,24 @@
#include "tpm2-util.h"
int acquire_luks2_key(
uint32_t pcr_mask,
uint16_t pcr_bank,
uint16_t primary_alg,
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const void *pubkey,
size_t pubkey_size,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pin,
uint16_t primary_alg,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
const char *pin,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ char *auto_device = NULL;
int r;
@ -45,116 +50,22 @@ int acquire_luks2_key(
if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
return -ENOANO;
if (pubkey_pcr_mask != 0) {
r = tpm2_load_pcr_signature(signature_path, &signature_json);
if (r < 0)
return r;
}
return tpm2_unseal(
device,
pcr_mask, pcr_bank,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
signature_json,
pin,
primary_alg,
key_data, key_data_size,
policy_hash, policy_hash_size, pin,
policy_hash, policy_hash_size,
ret_decrypted_key, ret_decrypted_key_size);
}
/* this function expects valid "systemd-tpm2" in json */
int parse_luks2_tpm2_data(
const char *json,
uint32_t search_pcr_mask,
uint32_t *ret_pcr_mask,
uint16_t *ret_pcr_bank,
uint16_t *ret_primary_alg,
char **ret_base64_blob,
char **ret_hex_policy_hash,
TPM2Flags *ret_flags) {
int r;
JsonVariant *w;
uint32_t pcr_mask;
uint16_t pcr_bank = UINT16_MAX, primary_alg = TPM2_ALG_ECC;
_cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
TPM2Flags flags = 0;
assert(json);
assert(ret_pcr_mask);
assert(ret_pcr_bank);
assert(ret_primary_alg);
assert(ret_base64_blob);
assert(ret_hex_policy_hash);
r = json_parse(json, 0, &v, NULL, NULL);
if (r < 0)
return -EINVAL;
w = json_variant_by_key(v, "tpm2-pcrs");
if (!w)
return -EINVAL;
r = tpm2_parse_pcr_json_array(w, &pcr_mask);
if (r < 0)
return r;
if (search_pcr_mask != UINT32_MAX &&
search_pcr_mask != pcr_mask)
return -ENXIO;
w = json_variant_by_key(v, "tpm2-pcr-bank");
if (w) {
/* The PCR bank field is optional */
if (!json_variant_is_string(w))
return -EINVAL;
r = tpm2_pcr_bank_from_string(json_variant_string(w));
if (r < 0)
return r;
pcr_bank = r;
}
w = json_variant_by_key(v, "tpm2-primary-alg");
if (w) {
/* The primary key algorithm is optional */
if (!json_variant_is_string(w))
return -EINVAL;
r = tpm2_primary_alg_from_string(json_variant_string(w));
if (r < 0)
return r;
primary_alg = r;
}
w = json_variant_by_key(v, "tpm2-blob");
if (!w || !json_variant_is_string(w))
return -EINVAL;
base64_blob = strdup(json_variant_string(w));
if (!base64_blob)
return -ENOMEM;
w = json_variant_by_key(v, "tpm2-policy-hash");
if (!w || !json_variant_is_string(w))
return -EINVAL;
hex_policy_hash = strdup(json_variant_string(w));
if (!hex_policy_hash)
return -ENOMEM;
w = json_variant_by_key(v, "tpm2-pin");
if (w) {
if (!json_variant_is_boolean(w))
return -EINVAL;
if (json_variant_boolean(w))
flags |= TPM2_FLAGS_USE_PIN;
}
*ret_pcr_mask = pcr_mask;
*ret_pcr_bank = pcr_bank;
*ret_primary_alg = primary_alg;
*ret_base64_blob = TAKE_PTR(base64_blob);
*ret_hex_policy_hash = TAKE_PTR(hex_policy_hash);
*ret_flags = flags;
return 0;
}

View File

@ -7,25 +7,19 @@
struct crypt_device;
int acquire_luks2_key(
const char *device,
uint32_t pcr_mask,
uint16_t pcr_bank,
const void *pubkey,
size_t pubkey_size,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pin,
uint16_t primary_alg,
const char *device,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
const char *pin,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
int parse_luks2_tpm2_data(
const char *json,
uint32_t search_pcr_mask,
uint32_t *ret_pcr_mask,
uint16_t *ret_pcr_bank,
uint16_t *ret_primary_alg,
char **ret_base64_blob,
char **ret_hex_policy_hash,
TPM2Flags *ret_flags);

View File

@ -55,8 +55,12 @@ static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headl
int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const void *pubkey,
size_t pubkey_size,
uint32_t pubkey_pcr_mask,
const char *signature_path,
uint16_t primary_alg,
const char *key_file,
size_t key_file_size,
@ -72,6 +76,7 @@ int acquire_tpm2_key(
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ void *loaded_blob = NULL;
_cleanup_free_ char *auto_device = NULL;
size_t blob_size;
@ -111,17 +116,26 @@ int acquire_tpm2_key(
blob = loaded_blob;
}
if (pubkey_pcr_mask != 0) {
r = tpm2_load_pcr_signature(signature_path, &signature_json);
if (r < 0)
return r;
}
if (!(flags & TPM2_FLAGS_USE_PIN))
return tpm2_unseal(
device,
pcr_mask,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
signature_json,
/* pin= */ NULL,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
NULL,
ret_decrypted_key,
ret_decrypted_key_size);
@ -135,16 +149,18 @@ int acquire_tpm2_key(
if (r < 0)
return r;
r = tpm2_unseal(
device,
pcr_mask,
r = tpm2_unseal(device,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
signature_json,
pin_str,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
pin_str,
ret_decrypted_key,
ret_decrypted_key_size);
/* We get this error in case there is an authentication policy mismatch. This should
@ -162,31 +178,32 @@ int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
void **ret_pubkey,
size_t *ret_pubkey_size,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token,
TPM2Flags *ret_flags) {
int *ret_token) {
_cleanup_free_ void *blob = NULL, *policy_hash = NULL;
size_t blob_size = 0, policy_hash_size = 0;
int r, keyslot = -1, token = -1;
TPM2Flags flags = 0;
uint32_t pcr_mask = 0;
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
int r, token;
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
JsonVariant *w;
int ks;
size_t blob_size, policy_hash_size, pubkey_size;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags;
int keyslot;
r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
@ -194,119 +211,46 @@ int find_tpm2_auto_data(
if (r < 0)
return log_error_errno(r, "Failed to read JSON token data off disk: %m");
ks = cryptsetup_get_keyslot_from_token(v);
if (ks < 0) {
/* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
* us, but by the LUKS2 spec */
log_warning_errno(ks, "Failed to extract keyslot index from TPM2 JSON data token %i, skipping: %m", token);
r = tpm2_parse_luks2_json(
v,
&keyslot,
&hash_pcr_mask,
&pcr_bank,
&pubkey, &pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob, &blob_size,
&policy_hash, &policy_hash_size,
&flags);
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
}
w = json_variant_by_key(v, "tpm2-pcrs");
if (!w)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-pcrs' field.");
r = tpm2_parse_pcr_json_array(w, &pcr_mask);
if (r < 0)
return log_error_errno(r, "Failed to parse TPM2 PCR mask: %m");
return log_error_errno(r, "Failed to parse TPM2 JSON data: %m");
if (search_pcr_mask != UINT32_MAX &&
search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */
continue;
if (search_pcr_mask == UINT32_MAX ||
search_pcr_mask == hash_pcr_mask) {
assert(keyslot < 0);
keyslot = ks;
if (start_token <= 0)
log_info("Automatically discovered security TPM2 token unlocks volume.");
assert(pcr_bank == UINT16_MAX);
assert(primary_alg == TPM2_ALG_ECC);
/* The bank field is optional, since it was added in systemd 250 only. Before the bank was
* hardcoded to SHA256. */
w = json_variant_by_key(v, "tpm2-pcr-bank");
if (w) {
/* The PCR bank field is optional */
if (!json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 PCR bank is not a string.");
r = tpm2_pcr_bank_from_string(json_variant_string(w));
if (r < 0)
return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w));
pcr_bank = r;
*ret_hash_pcr_mask = hash_pcr_mask;
*ret_pcr_bank = pcr_bank;
*ret_pubkey = TAKE_PTR(pubkey);
*ret_pubkey_size = pubkey_size;
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
*ret_primary_alg = primary_alg;
*ret_blob = TAKE_PTR(blob);
*ret_blob_size = blob_size;
*ret_policy_hash = TAKE_PTR(policy_hash);
*ret_policy_hash_size = policy_hash_size;
*ret_keyslot = keyslot;
*ret_token = token;
*ret_flags = flags;
return 0;
}
/* The primary key algorithm field is optional, since it was also added in systemd 250
* only. Before the algorithm was hardcoded to ECC. */
w = json_variant_by_key(v, "tpm2-primary-alg");
if (w) {
/* The primary key algorithm is optional */
if (!json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 primary key algorithm is not a string.");
r = tpm2_primary_alg_from_string(json_variant_string(w));
if (r < 0)
return log_error_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w));
primary_alg = r;
}
assert(!blob);
w = json_variant_by_key(v, "tpm2-blob");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-blob' field.");
r = unbase64mem(json_variant_string(w), SIZE_MAX, &blob, &blob_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-blob' field.");
assert(!policy_hash);
w = json_variant_by_key(v, "tpm2-policy-hash");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field.");
r = unhexmem(json_variant_string(w), SIZE_MAX, &policy_hash, &policy_hash_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-policy-hash' field.");
w = json_variant_by_key(v, "tpm2-pin");
if (w) {
if (!json_variant_is_boolean(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 PIN policy is not a boolean.");
if (json_variant_boolean(w))
flags |= TPM2_FLAGS_USE_PIN;
}
break;
/* PCR mask doesn't match what is configured, ignore this entry, let's see next */
}
if (!blob)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"No valid TPM2 token data found.");
if (start_token <= 0)
log_info("Automatically discovered security TPM2 token unlocks volume.");
*ret_pcr_mask = pcr_mask;
*ret_blob = TAKE_PTR(blob);
*ret_blob_size = blob_size;
*ret_policy_hash = TAKE_PTR(policy_hash);
*ret_policy_hash_size = policy_hash_size;
*ret_keyslot = keyslot;
*ret_token = token;
*ret_pcr_bank = pcr_bank;
*ret_primary_alg = primary_alg;
*ret_flags = flags;
return 0;
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found.");
}

View File

@ -14,8 +14,12 @@
int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const void *pubkey,
size_t pubkey_size,
uint32_t pubkey_pcr_mask,
const char *signature_path,
uint16_t primary_alg,
const char *key_file,
size_t key_file_size,
@ -35,24 +39,31 @@ int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
void **ret_pubkey,
size_t *ret_pubkey_size,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token,
TPM2Flags *ret_flags);
int *ret_token);
#else
static inline int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const void *pubkey,
size_t pubkey_size,
uint32_t pubkey_pcr_mask,
const char *signature_path,
uint16_t primary_alg,
const char *key_file,
size_t key_file_size,
@ -76,16 +87,19 @@ static inline int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
void **ret_pubkey,
size_t *ret_pubkey_size,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token,
TPM2Flags *ret_flags) {
int *ret_token) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");

View File

@ -92,6 +92,7 @@ static char *arg_fido2_rp_id = NULL;
static char *arg_tpm2_device = NULL;
static bool arg_tpm2_device_auto = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_signature = NULL;
static bool arg_tpm2_pin = false;
static bool arg_headless = false;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
@ -105,6 +106,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
[PASSPHRASE_REGULAR] = "passphrase",
@ -398,6 +400,16 @@ static int parse_one_option(const char *option) {
if (r < 0)
return r;
} else if ((val = startswith(option, "tpm2-signature="))) {
if (!path_is_absolute(val))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 signature path \"%s\" is not absolute, refusing.", val);
r = free_and_strdup(&arg_tpm2_signature, val);
if (r < 0)
return log_oom();
} else if ((val = startswith(option, "tpm2-pin="))) {
r = parse_boolean(val);
@ -1384,7 +1396,8 @@ static int attach_luks2_by_tpm2_via_plugin(
#if HAVE_LIBCRYPTSETUP_PLUGINS
systemd_tpm2_plugin_params params = {
.search_pcr_mask = arg_tpm2_pcr_mask,
.device = arg_tpm2_device
.device = arg_tpm2_device,
.signature_path = arg_tpm2_signature,
};
if (!libcryptsetup_plugins_support())
@ -1441,10 +1454,13 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
arg_tpm2_device,
arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask,
UINT16_MAX,
0,
/* pubkey= */ NULL, /* pubkey_size= */ 0,
/* pubkey_pcr_mask= */ 0,
/* signature_path= */ NULL,
/* primary_alg= */ 0,
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
NULL, 0, /* we don't know the policy hash */
/* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
arg_tpm2_pin,
until,
arg_headless,
@ -1490,7 +1506,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
* works. */
for (;;) {
uint32_t pcr_mask;
_cleanup_free_ void *pubkey = NULL;
size_t pubkey_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
@ -1498,14 +1516,16 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
cd,
arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */
token, /* search for the token with this index, or any later index than this */
&pcr_mask,
&hash_pcr_mask,
&pcr_bank,
&pubkey, &pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob, &blob_size,
&policy_hash, &policy_hash_size,
&tpm2_flags,
&keyslot,
&token,
&tpm2_flags);
&token);
if (r == -ENXIO)
/* No further TPM2 tokens found in the LUKS2 header. */
return log_full_errno(found_some ? LOG_NOTICE : LOG_DEBUG,
@ -1523,10 +1543,13 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
r = acquire_tpm2_key(
name,
arg_tpm2_device,
pcr_mask,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
arg_tpm2_signature,
primary_alg,
NULL, 0, 0, /* no key file */
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
blob, blob_size,
policy_hash, policy_hash_size,
tpm2_flags,

View File

@ -63,6 +63,7 @@
#include "strv.h"
#include "sync-util.h"
#include "terminal-util.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
#include "user-util.h"
#include "utf8.h"
@ -112,12 +113,15 @@ static void *arg_key = NULL;
static size_t arg_key_size = 0;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_public_key = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
typedef struct Partition Partition;
typedef struct FreeArea FreeArea;
@ -2914,12 +2918,33 @@ static int partition_encrypt(
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *pubkey = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
size_t secret_size, blob_size, hash_size;
size_t secret_size, blob_size, hash_size, pubkey_size = 0;
uint16_t pcr_bank, primary_alg;
int keyslot;
r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, NULL, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
if (arg_tpm2_public_key_pcr_mask != 0) {
r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size);
if (r < 0) {
if (arg_tpm2_public_key || r != -ENOENT)
return log_error_errno(r, "Failed read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
arg_tpm2_public_key_pcr_mask = 0;
}
}
r = tpm2_seal(arg_tpm2_device,
arg_tpm2_pcr_mask,
pubkey, pubkey_size,
arg_tpm2_public_key_pcr_mask,
/* pin= */ NULL,
&secret, &secret_size,
&blob, &blob_size,
&hash, &hash_size,
&pcr_bank,
&primary_alg);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
@ -2941,7 +2966,17 @@ static int partition_encrypt(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, 0, &v);
r = tpm2_make_luks2_json(
keyslot,
arg_tpm2_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
arg_tpm2_public_key_pcr_mask,
primary_alg,
blob, blob_size,
hash, hash_size,
0,
&v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
@ -4442,6 +4477,10 @@ static int help(void) {
" --tpm2-device=PATH Path to TPM2 device node to use\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" TPM2 PCR indexes to use for TPM2 enrollment\n"
" --tpm2-public-key=PATH\n"
" Enroll signed TPM2 PCR policy against PEM public key\n"
" --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
" Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n"
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
" --size=BYTES Grow loopback file to specified size\n"
" --json=pretty|short|off\n"
@ -4476,28 +4515,32 @@ static int parse_argv(int argc, char *argv[]) {
ARG_KEY_FILE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
ARG_TPM2_PUBLIC_KEY,
ARG_TPM2_PUBLIC_KEY_PCRS,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "dry-run", required_argument, NULL, ARG_DRY_RUN },
{ "empty", required_argument, NULL, ARG_EMPTY },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "factory-reset", required_argument, NULL, ARG_FACTORY_RESET },
{ "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "seed", required_argument, NULL, ARG_SEED },
{ "pretty", required_argument, NULL, ARG_PRETTY },
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
{ "size", required_argument, NULL, ARG_SIZE },
{ "json", required_argument, NULL, ARG_JSON },
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "dry-run", required_argument, NULL, ARG_DRY_RUN },
{ "empty", required_argument, NULL, ARG_EMPTY },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "factory-reset", required_argument, NULL, ARG_FACTORY_RESET },
{ "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "seed", required_argument, NULL, ARG_SEED },
{ "pretty", required_argument, NULL, ARG_PRETTY },
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
{ "size", required_argument, NULL, ARG_SIZE },
{ "json", required_argument, NULL, ARG_JSON },
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{}
};
@ -4690,6 +4733,20 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_TPM2_PUBLIC_KEY:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
if (r < 0)
return r;
break;
case ARG_TPM2_PUBLIC_KEY_PCRS:
r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
@ -4743,6 +4800,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
if (arg_pretty < 0 && isatty(STDOUT_FILENO))
arg_pretty = true;

View File

@ -11,6 +11,7 @@
#include "blockdev-util.h"
#include "chattr-util.h"
#include "creds-util.h"
#include "def.h"
#include "efi-api.h"
#include "env-util.h"
#include "fd-util.h"
@ -27,6 +28,8 @@
#include "tpm2-util.h"
#include "virt.h"
#define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
bool credential_name_valid(const char *s) {
/* We want that credential names are both valid in filenames (since that's our primary way to pass
* them around) and as fdnames (which is how we might want to pass them around eventually) */
@ -461,6 +464,13 @@ struct _packed_ tpm2_credential_header {
/* Followed by NUL bytes until next 8 byte boundary */
};
struct _packed_ tpm2_public_key_credential_header {
le64_t pcr_mask; /* PCRs used for the public key PCR policy (usually just PCR 11, i.e. the unified kernel) */
le32_t size; /* Size of DER public key */
uint8_t data[]; /* DER public key */
/* Followed by NUL bytes until next 8 byte boundary */
};
struct _packed_ metadata_credential_header {
le64_t timestamp;
le64_t not_after;
@ -518,7 +528,9 @@ int encrypt_credential_and_warn(
usec_t timestamp,
usec_t not_after,
const char *tpm2_device,
uint32_t tpm2_pcr_mask,
uint32_t tpm2_hash_pcr_mask,
const char *tpm2_pubkey_path,
uint32_t tpm2_pubkey_pcr_mask,
const void *input,
size_t input_size,
void **ret,
@ -532,6 +544,8 @@ int encrypt_credential_and_warn(
uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0;
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
_cleanup_free_ void *pubkey = NULL;
size_t pubkey_size = 0;
uint8_t md[SHA256_DIGEST_LENGTH];
const EVP_CIPHER *cc;
sd_id128_t id;
@ -545,7 +559,9 @@ int encrypt_credential_and_warn(
_CRED_AUTO_INITRD,
CRED_AES256_GCM_BY_HOST,
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_TPM2_ABSENT))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
@ -569,7 +585,8 @@ int encrypt_credential_and_warn(
if (sd_id128_in_set(with_key,
_CRED_AUTO,
CRED_AES256_GCM_BY_HOST,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
r = get_credential_host_secret(
CREDENTIAL_SECRET_GENERATE|
@ -604,18 +621,41 @@ int encrypt_credential_and_warn(
if (!try_tpm2)
log_debug("Firmware lacks TPM2 support, not attempting to use TPM2.");
} else
try_tpm2 = sd_id128_in_set(with_key, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
try_tpm2 = sd_id128_in_set(with_key,
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
if (try_tpm2) {
if (sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
/* Load public key for PCR policies, if one is specified, or explicitly requested */
r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size);
if (r < 0) {
if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD))
return log_error_errno(r, "Failed read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
}
}
if (!pubkey)
tpm2_pubkey_pcr_mask = 0;
r = tpm2_seal(tpm2_device,
tpm2_pcr_mask,
NULL,
&tpm2_key,
&tpm2_key_size,
&tpm2_blob,
&tpm2_blob_size,
&tpm2_policy_hash,
&tpm2_policy_hash_size,
tpm2_hash_pcr_mask,
pubkey, pubkey_size,
tpm2_pubkey_pcr_mask,
/* pin= */ NULL,
&tpm2_key, &tpm2_key_size,
&tpm2_blob, &tpm2_blob_size,
&tpm2_policy_hash, &tpm2_policy_hash_size,
&tpm2_pcr_bank,
&tpm2_primary_alg);
if (r < 0) {
@ -636,9 +676,9 @@ int encrypt_credential_and_warn(
/* Let's settle the key type in auto mode now. */
if (host_key && tpm2_key)
id = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
else if (tpm2_key)
id = CRED_AES256_GCM_BY_TPM2_HMAC;
id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
else if (host_key)
id = CRED_AES256_GCM_BY_HOST;
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
@ -694,6 +734,7 @@ int encrypt_credential_and_warn(
output_size =
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
input_size + 2U * (size_t) bsz +
tsz;
@ -716,7 +757,7 @@ int encrypt_credential_and_warn(
struct tpm2_credential_header *t;
t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
t->pcr_mask = htole64(tpm2_pcr_mask);
t->pcr_mask = htole64(tpm2_hash_pcr_mask);
t->pcr_bank = htole16(tpm2_pcr_bank);
t->primary_alg = htole16(tpm2_primary_alg);
t->blob_size = htole32(tpm2_blob_size);
@ -727,6 +768,17 @@ int encrypt_credential_and_warn(
p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
}
if (pubkey) {
struct tpm2_public_key_credential_header *z;
z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p);
z->pcr_mask = htole64(tpm2_pubkey_pcr_mask);
z->size = htole32(pubkey_size);
memcpy(z->data, pubkey, pubkey_size);
p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size);
}
/* Pass the encrypted + TPM2 header as AAD */
if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
@ -798,18 +850,20 @@ int decrypt_credential_and_warn(
const char *validate_name,
usec_t validate_timestamp,
const char *tpm2_device,
const char *tpm2_signature_path,
const void *input,
size_t input_size,
void **ret,
size_t *ret_size) {
_cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
bool with_tpm2, with_host_key, is_tpm2_absent;
bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk;
const EVP_CIPHER *cc;
int r, added;
@ -823,13 +877,20 @@ int decrypt_credential_and_warn(
if (input_size < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk;
is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
if (with_tpm2_pk) {
r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
if (r < 0)
return r;
}
if (is_tpm2_absent) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
@ -868,7 +929,8 @@ int decrypt_credential_and_warn(
* lower limit only) */
if (input_size <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8((with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0)) +
ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@ -878,6 +940,7 @@ int decrypt_credential_and_warn(
if (with_tpm2) {
#if HAVE_TPM2
struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
struct tpm2_public_key_credential_header *z = NULL;
if (!TPM2_PCR_MASK_VALID(t->pcr_mask))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
@ -895,27 +958,52 @@ int decrypt_credential_and_warn(
if (input_size <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) +
le32toh(t->blob_size) +
le32toh(t->policy_hash_size));
if (with_tpm2_pk) {
z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p);
if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
if (le32toh(z->size) > PUBLIC_KEY_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
if (input_size <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) +
le32toh(z->size));
}
r = tpm2_unseal(tpm2_device,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
z ? z->data : NULL, z ? le32toh(z->size) : 0,
le64toh(z->pcr_mask),
signature_json,
/* pin= */ NULL,
le16toh(t->primary_alg),
t->policy_hash_and_blob,
le32toh(t->blob_size),
t->policy_hash_and_blob + le32toh(t->blob_size),
le32toh(t->policy_hash_size),
NULL,
&tpm2_key,
&tpm2_key_size);
if (r < 0)
return r;
p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) +
le32toh(t->blob_size) +
le32toh(t->policy_hash_size));
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Credential requires TPM2 support, but TPM2 support not available.");
#endif
@ -1071,11 +1159,11 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size) {
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}

View File

@ -52,7 +52,10 @@ int get_credential_user_password(const char *username, char **ret_password, bool
* for us to handle). */
#define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a)
#define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe)
#define CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK SD_ID128_MAKE(fa,f7,eb,93,41,e3,41,2c,a1,a4,36,f9,5a,29,36,2f)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \
SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05)
#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
@ -63,5 +66,5 @@ int get_credential_user_password(const char *username, char **ret_password, bool
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size);
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size);

View File

@ -1,12 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#if HAVE_LIBCRYPTSETUP
#include "alloc-util.h"
#include "cryptsetup-util.h"
#include "dlfcn-util.h"
#include "log.h"
#include "parse-util.h"
#if HAVE_LIBCRYPTSETUP
static void *cryptsetup_dl = NULL;
int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags);
@ -224,6 +224,28 @@ int cryptsetup_get_token_as_json(
return 0;
}
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) {
_cleanup_free_ char *text = NULL;
int r;
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_debug_errno(r, "Failed to format token data for LUKS: %m");
log_debug("Adding token text <%s>", text);
r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
if (r < 0)
return log_debug_errno(r, "Failed to write token data to LUKS: %m");
return 0;
}
#endif
int cryptsetup_get_keyslot_from_token(JsonVariant *v) {
int keyslot, r;
JsonVariant *w;
@ -252,25 +274,3 @@ int cryptsetup_get_keyslot_from_token(JsonVariant *v) {
return keyslot;
}
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) {
_cleanup_free_ char *text = NULL;
int r;
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_debug_errno(r, "Failed to format token data for LUKS: %m");
log_debug("Adding token text <%s>", text);
r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
if (r < 0)
return log_debug_errno(r, "Failed to write token data to LUKS: %m");
return 0;
}
#endif

View File

@ -74,7 +74,6 @@ void cryptsetup_enable_logging(struct crypt_device *cd);
int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd);
int cryptsetup_get_token_as_json(struct crypt_device *cd, int idx, const char *verify_type, JsonVariant **ret);
int cryptsetup_get_keyslot_from_token(JsonVariant *v);
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v);
#else
@ -87,6 +86,8 @@ static inline void sym_crypt_freep(struct crypt_device** cd) {}
#endif
int cryptsetup_get_keyslot_from_token(JsonVariant *v);
static inline const char *mangle_none(const char *s) {
/* A helper that turns cryptsetup/integritysetup/veritysetup "options" strings into NULL if they are effectively empty */
return isempty(s) || STR_IN_SET(s, "-", "none") ? NULL : s;

View File

@ -109,6 +109,64 @@ int rsa_pkey_to_suitable_key_size(
return 0;
}
int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL;
_cleanup_free_ void *d = NULL, *h = NULL;
int sz, lsz, msz;
unsigned umsz;
unsigned char *dd;
/* Calculates a message digest of the DER encoded public key */
assert(pk);
assert(md);
assert(ret);
assert(ret_size);
sz = i2d_PublicKey(pk, NULL);
if (sz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert public key to DER format: %s",
ERR_error_string(ERR_get_error(), NULL));
dd = d = malloc(sz);
if (!d)
return log_oom_debug();
lsz = i2d_PublicKey(pk, &dd);
if (lsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert public key to DER format: %s",
ERR_error_string(ERR_get_error(), NULL));
m = EVP_MD_CTX_new();
if (!m)
return log_oom_debug();
if (EVP_DigestInit_ex(m, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", EVP_MD_name(md));
if (EVP_DigestUpdate(m, d, lsz) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run %s context.", EVP_MD_name(md));
msz = EVP_MD_size(md);
assert_se(msz > 0);
assert_se(msz <= INT_MAX);
h = malloc(msz);
if (!h)
return log_oom_debug();
umsz = msz;
if (EVP_DigestFinal_ex(m, h, &umsz) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
assert_se(umsz == (unsigned) msz);
*ret = TAKE_PTR(h);
*ret_size = msz;
return 0;
}
# if PREFER_OPENSSL
int string_hashsum(
const char *s,

View File

@ -8,9 +8,17 @@
# include <openssl/bn.h>
# include <openssl/err.h>
# include <openssl/evp.h>
# include <openssl/opensslv.h>
# include <openssl/pkcs7.h>
# include <openssl/ssl.h>
# include <openssl/x509v3.h>
# ifndef OPENSSL_VERSION_MAJOR
/* OPENSSL_VERSION_MAJOR macro was added in OpenSSL 3. Thus, if it doesn't exist, we must be before OpenSSL 3. */
# define OPENSSL_VERSION_MAJOR 1
# endif
# if OPENSSL_VERSION_MAJOR >= 3
# include <openssl/core_names.h>
# endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL);
@ -39,6 +47,9 @@ int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *re
int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);
int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size);
int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size);
#endif
#if PREFER_OPENSSL

File diff suppressed because it is too large Load Diff

View File

@ -25,15 +25,19 @@ extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shan
extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes);
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_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);
extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest);
extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs);
extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle);
extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType);
extern TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask);
extern TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name);
extern TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue);
extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData);
extern TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation);
extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc);
@ -44,11 +48,31 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
int dlopen_tpm2(void);
int tpm2_seal(const char *device, uint32_t pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, const char *pin, void **ret_secret, size_t *ret_secret_size);
int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
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, void **ret_secret, size_t *ret_secret_size);
struct tpm2_context {
void *tcti_dl;
TSS2_TCTI_CONTEXT *tcti_context;
ESYS_CONTEXT *esys_context;
};
ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle);
void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret);
static inline void Esys_Freep(void *p) {
if (*(void**) p)
sym_Esys_Free(*(void**) p);
}
#else
struct tpm2_context;
#endif
int tpm2_context_init(const char *device, struct tpm2_context *ret);
void tpm2_context_destroy(struct tpm2_context *c);
int tpm2_list_devices(void);
int tpm2_find_device_auto(int log_level, char **ret);
@ -57,7 +81,8 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret);
int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, TPM2Flags *ret_flags);
#define TPM2_PCRS_MAX 24U
@ -103,6 +128,7 @@ int tpm2_primary_alg_from_string(const char *alg);
typedef struct {
uint32_t search_pcr_mask;
const char *device;
const char *signature_path;
} systemd_tpm2_plugin_params;
typedef enum Tpm2Support {
@ -118,3 +144,8 @@ typedef enum Tpm2Support {
Tpm2Support tpm2_support(void);
int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask);
int tpm2_load_pcr_signature(const char *path, JsonVariant **ret);
int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size);
int pcr_mask_to_string(uint32_t mask, char **ret);

View File

@ -20,6 +20,7 @@ test_append_files() {
install_dmevent
generate_module_dependencies
inst_binary tpm2_pcrextend
inst_binary openssl
}
TEST_70_TPM_DEVICE="tpm-tis"

View File

@ -57,6 +57,8 @@ env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $
tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1
rm $img
if [[ -e /usr/lib/systemd/systemd-measure ]]; then
echo HALLO > /tmp/tpmdata1
echo foobar > /tmp/tpmdata2
@ -69,8 +71,65 @@ if [[ -e /usr/lib/systemd/systemd-measure ]]; then
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 | 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 -j | diff -u - /tmp/result.json
else
echo "/usr/lib/systemd/systemd-measure not found, skipping the test case"
echo "/usr/lib/systemd/systemd-measure not found, skipping PCR policy test case"
fi
if [ -e /usr/lib/systemd/systemd-measure ] && \
[ -f /sys/class/tpm/tpm0/pcr-sha1/11 ] && \
[ -f /sys/class/tpm/tpm0/pcr-sha256/11 ]; then
# Generate key pair
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "/tmp/pcrsign-private.pem"
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"
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
# Invalidate PCR, decrypting should fail now
tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
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"
systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig2" | cmp - /tmp/pcrtestdata
# Now, do the same, but with a cryptsetup binding
truncate -s 20M $img
cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img /tmp/passphrase
systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" --tpm2-signature="/tmp/pcrsign.sig2" $img
# Check if we can activate that (without the token module stuff)
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup detach test-volume2
# Check if we can activate that (and a second time with the the token module stuff enabled)
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
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup detach test-volume2
# After extending the PCR things should fail
tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
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"
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
/usr/lib/systemd/systemd-cryptsetup detach test-volume2
rm $img
else
echo "/usr/lib/systemd/systemd-measure or PCR sysfs files not found, skipping signed PCR policy test case"
fi
echo OK >/testok