1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-13 17:18:18 +03:00

measure: add 'sign' verb

This commit is contained in:
Lennart Poettering 2022-08-17 18:40:42 +02:00
parent e8ccb5c7e1
commit cdaaa62ca1
2 changed files with 398 additions and 31 deletions

View File

@ -17,7 +17,7 @@
<refnamediv> <refnamediv>
<refname>systemd-measure</refname> <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> </refnamediv>
<refsynopsisdiv> <refsynopsisdiv>
@ -32,15 +32,17 @@
<para>Note: this command is experimental for now. While it is likely to become a regular component of <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> 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 <para><command>systemd-measure</command> is a tool that may be used to pre-calculate and sign the
PCR 11 values that should be seen when a unified Linux kernel image based on 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 <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, booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file,
kernel command line file, kernel command line file,
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file, and <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 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 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>
<refsect1> <refsect1>
@ -61,11 +63,30 @@
<varlistentry> <varlistentry>
<term><command>calculate</command></term> <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>, kernel image consisting of the components specified with <option>--linux=</option>,
<option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>, <option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>,
<option>--splash=</option>, <option>--dtb=</option>, see below. Only <option>--linux=</option> is <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> </varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>
@ -84,29 +105,47 @@
<term><option>--splash=PATH</option></term> <term><option>--splash=PATH</option></term>
<term><option>--dtb=PATH</option></term> <term><option>--dtb=PATH</option></term>
<listitem><para>When used with the <command>calculate</command> verb, configures the files to read <listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
the unified kernel image components from. Each option corresponds with the equally named section in configures the files to read the unified kernel image components from. Each option corresponds with
the unified kernel PE file. The <option>--linux=</option> switch expects the path to the ELF kernel the equally named section in the unified kernel PE file. The <option>--linux=</option> switch expects
file that the unified PE kernel will wrap. All switches except <option>--linux=</option> are the path to the ELF kernel file that the unified PE kernel will wrap. All switches except
optional. Each option may be used at most once.</para></listitem> <option>--linux=</option> are optional. Each option may be used at most once.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><option>--current</option></term> <term><option>--current</option></term>
<listitem><para>When used with the <command>calculate</command> verb, takes the PCR 11 values <listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
currently in effect for the system (which should typically reflect the hashes of the currently booted takes the PCR 11 values currently in effect for the system (which should typically reflect the hashes
kernel). This can be used in place of <option>--linux=</option> and the other switches listed of the currently booted kernel). This can be used in place of <option>--linux=</option> and the other
above.</para></listitem> switches listed above.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><option>--bank=DIGEST</option></term> <term><option>--bank=DIGEST</option></term>
<listitem><para>Controls the PCR banks to pre-calculate the PCR values for in case <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> <command>calculate</command> or <command>sign</command> is invoked , or the banks to show in the
output. May be used more then once to specify multiple banks. If not specified, defaults to the four <command>status</command> output. May be used more then once to specify multiple banks. If not
banks <literal>sha1</literal>, <literal>sha256</literal>, <literal>sha384</literal>, specified, defaults to the four banks <literal>sha1</literal>, <literal>sha256</literal>,
<literal>sha512</literal>.</para></listitem> <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> </varlistentry>
<xi:include href="standard-options.xml" xpointer="json" /> <xi:include href="standard-options.xml" xpointer="json" />
@ -133,7 +172,7 @@
foo.efi foo.efi
# systemd-measure calculate \ # systemd-measure calculate \
--linux=vmlinux \ --linux=vmlinux \
--osrel=os-release \ --osrel=os-release.txt \
--cmdline=cmdline.txt \ --cmdline=cmdline.txt \
--initrd=initrd.cpio \ --initrd=initrd.cpio \
--splash=splash.bmp \ --splash=splash.bmp \
@ -144,6 +183,41 @@
11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da 11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da
</programlisting> </programlisting>
</example> </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>
<refsect1> <refsect1>
@ -157,7 +231,9 @@
<para> <para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</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> </para>
</refsect1> </refsect1>

View File

@ -14,6 +14,7 @@
#include "parse-argument.h" #include "parse-argument.h"
#include "parse-util.h" #include "parse-util.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "sha256.h"
#include "terminal-util.h" #include "terminal-util.h"
#include "tpm-pcr.h" #include "tpm-pcr.h"
#include "tpm2-util.h" #include "tpm2-util.h"
@ -24,11 +25,17 @@
static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
static char **arg_banks = NULL; 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 PagerFlags arg_pager_flags = 0;
static bool arg_current = false; static bool arg_current = false;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); 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]) { static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) 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(); return log_oom();
printf("%1$s [OPTIONS...] COMMAND ...\n" 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" "\n%3$sCommands:%4$s\n"
" status Show current PCR values\n" " status Show current PCR values\n"
" calculate Calculate expected PCR values\n" " calculate Calculate expected PCR values\n"
" sign Calculate and sign expected PCR values\n"
"\n%3$sOptions:%4$s\n" "\n%3$sOptions:%4$s\n"
" -h --help Show this help\n" " -h --help Show this help\n"
" --version Print version\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" " --dtb=PATH Path to Devicetree file\n"
" -c --current Use current PCR values\n" " -c --current Use current PCR values\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\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" " --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n" " -j Same as --json=pretty on tty, --json=short otherwise\n"
"\nSee the %2$s for details.\n", "\nSee the %2$s for details.\n",
@ -88,6 +99,9 @@ static int parse_argv(int argc, char *argv[]) {
_ARG_SECTION_LAST, _ARG_SECTION_LAST,
ARG_DTB = _ARG_SECTION_LAST, ARG_DTB = _ARG_SECTION_LAST,
ARG_BANK, ARG_BANK,
ARG_PRIVATE_KEY,
ARG_PUBLIC_KEY,
ARG_TPM2_DEVICE,
ARG_JSON, ARG_JSON,
}; };
@ -103,6 +117,9 @@ static int parse_argv(int argc, char *argv[]) {
{ "dtb", required_argument, NULL, ARG_DTB }, { "dtb", required_argument, NULL, ARG_DTB },
{ "current", no_argument, NULL, 'c' }, { "current", no_argument, NULL, 'c' },
{ "bank", required_argument, NULL, ARG_BANK }, { "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 }, { "json", required_argument, NULL, ARG_JSON },
{} {}
}; };
@ -155,6 +172,36 @@ static int parse_argv(int argc, char *argv[]) {
break; 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': case 'j':
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
break; break;
@ -377,14 +424,9 @@ static int measure_pcr(PcrState *pcr_states, size_t n) {
return 0; 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_(pcr_state_free_all) PcrState *pcr_states = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
size_t n = 0; 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); pcr_states = new0(PcrState, strv_length(arg_banks) + 1);
if (!pcr_states) if (!pcr_states)
@ -419,6 +461,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); r = measure_pcr(pcr_states, n);
if (r < 0) if (r < 0)
return r; return r;
@ -464,6 +525,235 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
return 0; 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) { static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
_cleanup_free_ char *s = NULL; _cleanup_free_ char *s = NULL;
uint32_t v; uint32_t v;
@ -641,6 +931,7 @@ static int measure_main(int argc, char *argv[]) {
{ "help", VERB_ANY, VERB_ANY, 0, help }, { "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "calculate", VERB_ANY, 1, 0, verb_calculate }, { "calculate", VERB_ANY, 1, 0, verb_calculate },
{ "sign", VERB_ANY, 1, 0, verb_sign },
{} {}
}; };