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:
commit
8432b0cd20
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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.");
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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.");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user