measure: Support binding signed policies to individual phases

systemd-measure can produce multiple signed policies for different
phases. However, a policy for a TPM resource that includes these signed
policies can currently be satisfied by any policy that is signed
with the same key.

It can be desirable to bind a resource's policy to one or more
specific phases. One way to do this could be to sign policies for
different phases or phase combinations with different keys. Another
approach is to limit the scope of signed policies using a policy
reference.

Using a policy reference works because:
- The reference is included along with the approved policy digest in the
  digest that is signed.
- The reference argument is included in the authorization policy for a
  resource via the TPM2_PolicyAuthorize assertion.
- During execution of the TPM2_PolicyAuthorize assertion, the TPM checks
  that the session's current policy digest is the approved policy digest,
  computes a digest from the approved policy digest and the supplied policy
  reference, and checks that the resulting digest is the one that was
  verified by TPM2_VerifySignature (via the returned ticket).

This adds a new --policyref argument to systemd-measure which binds all
of the signed policies to the specified policy reference. I did consider
making this more intelligent by auto-generating policy references for each
phase, but this approach provides the most flexibility for now. By making
use of the existing --append argument, a signer can produce multiple
signed policies with the same key that are bound to any individual phases
or combinations of phases.

The policy reference is a string without the NULL terminator. It is
supplied to the TPM via the TPM2B_NONCE type, which has a maximum size
equivalent to the size of the largest digest supported by the TPM. As
the signer doesn't know the capabilities of the target TPM,
systemd-measure limits the size of the policy reference to 32 bytes, to
fit within the size of a SHA256 digest.

This also includes the corresponding changes to systemd-cryptenroll and
systemd-repart (to add an equivalent --tpm2-public-key-policyref argument
so that the policy can be bound to the desired corresponding phase, and to
ensure that the policy reference is included in the LUKS2 token metadata),
and systemd-cryptsetup (to handle the policy reference stored in the LUKS2
token metadata).

This doesn't include policy reference support for credentials yet
because it requires a change to the credential headers.
This commit is contained in:
Chris Coulson
2026-07-02 21:28:42 +02:00
committed by Lennart Poettering
parent bd1bb49ffa
commit 2348941b5d
22 changed files with 485 additions and 18 deletions
+24
View File
@@ -737,6 +737,30 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-public-key-policyref=<replaceable>STRING</replaceable></option></term>
<listitem><para>Configures the policy reference to use together with the signed PCR policy enrolled via
<option>--tpm2-public-key=</option>. The policy reference is an opaque string that is incorporated
into the resulting authorization policy (via the <constant>TPM2_PolicyAuthorize</constant> assertion),
so that the slot can only be unlocked by a signed policy that was generated with the same policy
reference. This must match the policy reference passed to the <option>--policyref=</option> option of
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
when the signature was generated. It can be used to bind a slot to signatures for a specific boot
phase, even when a single signing key is used to produce signatures for multiple phases.</para>
<para>This option can be used to select a policy reference that is only bound to a signature for the
initrd phase, in order to create a slot that can only be unlocked from the initrd. For example, if
such a signature is created by supplying <option>--phase=enter-initrd</option> and
<option>--policyref=initrd</option> to
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>:
</para>
<programlisting>systemd-cryptenroll --tpm2-device=auto --tpm2-public-key-policyref=initrd /dev/sda1</programlisting>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-pcrlock=<replaceable>PATH</replaceable></option></term>
+85
View File
@@ -277,6 +277,33 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--policyref=<replaceable>STRING</replaceable></option></term>
<listitem><para>When generating a PCR JSON signature (via the <command>sign</command> command), bind
the generated signatures to the specified policy reference. The policy reference is an opaque string
that is included in the data that is signed, and is also incorporated into the authorization policy of
any TPM2 resource that is locked against these signatures (via the
<constant>TPM2_PolicyAuthorize</constant> assertion). As a result, a signed policy is only accepted
for a resource whose authorization policy was created with the same policy reference. Signed policies
created with a different policy reference are rejected, even if they are produced with the same
signing key.</para>
<para>This can be used together with <option>--phase=</option> and <option>--append=</option> to bind
signatures for different boot phases to different policy references, so that a single signing key can
produce signatures that are each scoped to an individual phase or combination of phases. The
corresponding policy reference is selected when locking a resource, for example via the
<option>--tpm2-public-key-policyref=</option> option of
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>It is recommended to use this option to create a signed policy just for the initrd boot phase
by combining it with <option>--phase=enter-initrd</option> for secrets that should only be
recoverable from the initrd, particularly FDE keys.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />
@@ -420,6 +447,64 @@ Wrote unsigned vmlinux-1.2.3.efi
bound to <filename>tpm2-pcr-initrd-public-key.pem</filename>, specify
<option>--tpm2-public-key=</option> on the command line of those tools.</para>
</example>
<example>
<title>Using a policy reference to add a signature only for the initrd boot phase</title>
<para>This example is an alternative approach to the previous one, but rather than introducing a second
signing key, we re-use the same signing key to sign a PCR policy that is restricted to the initrd boot
phase and use the <option>--policyref=</option> option so that secrets or credentials can be bound only
to this more restricted policy, using this reference.</para>
<programlisting>$ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out tpm2-pcr-private-key.pem
.+........+.+........+.......+...+...+........+....+......+..+..........
$ openssl rsa -pubout -in tpm2-pcr-private-key.pem -out tpm2-pcr-public-key.pem
$ ukify build \
--linux=vmlinux-1.2.3 \
--initrd=initrd.cpio \
--os-release=@os-release.txt \
--cmdline=@cmdline.txt \
--splash=splash.bmp \
--devicetree=devicetree.dtb \
--pcrpkey=tpm2-pcr-public-key.pem \
--pcr-private-key=tpm2-pcr-private-key.pem \
--phases=enter-initrd,enter-initrd:leave-initrd,enter-initrd:leave-initrd:sysinit,enter-initrd:leave-initrd:sysinit:ready \
--policyref= \
--pcr-banks=sha1,sha256 \
--pcr-private-key=tpm2-pcr-private-key.pem \
--phases=enter-initrd \
--policyref=initrd \
--uname=1.2.3 \
--output=vmlinux-1.2.3.efi
+ /usr/lib/systemd/systemd-measure sign --linux=vmlinux-1.2.3 \
--osrel=os-release.txt --cmdline=cmdline.txt --dtb=devicetree.dtb \
--splash=splash.bmp --pcrpkey=tpm2-pcr-public-key.pem --initrd=initrd.cpio \
--bank=sha1 --bank=sha256 --private-key=tpm2-pcr-private-key.pem \
--phase=enter-initrd --phase=enter-initrd:leave-initrd \
--phase=enter-initrd:leave-initrd:sysinit \
--phase=enter-initrd:leave-initrd:sysinit:ready --policyref=
+ /usr/lib/systemd/systemd-measure sign --linux=vmlinux-1.2.3 \
--osrel=os-release.txt --cmdline=cmdline.txt --dtb=devicetree.dtb \
--splash=splash.bmp --pcrpkey=tpm2-pcr-public-key.pem --initrd=initrd.cpio \
--bank=sha1 --bank=sha256 --private-key=tpm2-pcr-initrd-private-key.pem \
--phase=enter-initrd --policyref=initrd
Wrote unsigned vmlinux-1.2.3.efi
</programlisting>
<para><command>ukify</command> prints out both invocations of <command>systemd-measure</command> as
informative output (the lines starting with <literal>+</literal>); this allows us to see how
<command>systemd-measure</command> is called. It then merges the output of both invocations into the
<literal>.pcrsig</literal> section. <command>systemd-measure</command> may also do this merge itself
using the <option>--append=</option> option.</para>
<para>Note that in this example, the <literal>.pcrsig</literal> PE section contains signed policies
covering all boot phases bound to the same empty policy reference, and a more restricted signed policy
for just the initrd boot phase bound to the <literal>initrd</literal> policy reference. To use the
stricter policy, supply <option>--tpm2-policyref=initrd</option> to <command>systemd-cryptenroll</command>.
To use the more relaxed policy that includes all boot phases, omit the <option>--tpm2-policyref=</option>
of <command>systemd-cryptenroll</command>. Note that policies for <command>systemd-creds</command>
cannot currently bind to signed policies with a non-empty policy reference.</para>
</example>
</refsect1>
<refsect1>
+11
View File
@@ -445,6 +445,17 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-public-key-policyref=<replaceable>STRING</replaceable></option></term>
<listitem><para>Configures the policy reference to use together with the signed PCR policy configured
via <option>--tpm2-public-key=</option>. See
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for details on this option.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-pcrlock=<replaceable>PATH</replaceable></option></term>
+19
View File
@@ -723,6 +723,25 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>PolicyRef=<replaceable>STRING</replaceable></varname></term>
<term><varname>--policyref=<replaceable>STRING</replaceable></varname></term>
<listitem><para>A policy reference to bind the generated signature to.</para>
<para>The policy reference is an opaque string that is included in the data that is signed, and is
also incorporated into the authorization policy of any TPM2 resource that is locked against these
signatures (via the <constant>TPM2_PolicyAuthorize</constant> assertion). As a result, a signed
policy is only accepted for a resource whose authorization policy was created with the same
policy reference. Signed policies created with a different policy reference are rejected, even if
they are produced with the same signing key.</para>
<para>On the command line, when this argument is present, it must appear the same number of times as
the <option>--pcr-private-key=</option> option. </para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
+7
View File
@@ -189,6 +189,7 @@ int load_volume_key_tpm2(
for (;;) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_free_ char *pubkey_policy_ref = NULL;
struct iovec *blobs = NULL, *policy_hash = NULL;
size_t n_blobs = 0, n_policy_hash = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
@@ -206,6 +207,7 @@ int load_volume_key_tpm2(
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_policy_ref,
&pubkey_pcr_mask,
&primary_alg,
&blobs,
@@ -238,6 +240,7 @@ int load_volume_key_tpm2(
hash_pcr_mask,
pcr_bank,
&pubkey,
pubkey_policy_ref,
pubkey_pcr_mask,
/* signature_path= */ NULL,
/* pcrlock_path= */ NULL,
@@ -455,6 +458,7 @@ int enroll_tpm2(const EnrollContext *c,
c->tpm2_hash_pcr_values,
c->tpm2_n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL,
iovec_is_set(&pubkey) ? c->tpm2_public_key_policyref : NULL,
c->tpm2_pin,
c->tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
policy_hash + 0);
@@ -466,6 +470,7 @@ int enroll_tpm2(const EnrollContext *c,
c->tpm2_hash_pcr_values,
c->tpm2_n_hash_pcr_values,
/* public= */ NULL, /* This one is off now */
/* pubkey_policy_ref= */ NULL,
c->tpm2_pin,
&pcrlock_policy, /* And this one on instead. */
policy_hash + 1);
@@ -543,6 +548,7 @@ int enroll_tpm2(const EnrollContext *c,
hash_pcr_mask,
hash_pcr_bank,
&pubkey,
c->tpm2_public_key_policyref,
pubkey_pcr_mask,
signature_json,
pin_str,
@@ -585,6 +591,7 @@ int enroll_tpm2(const EnrollContext *c,
hash_pcr_mask,
hash_pcr_bank,
&pubkey,
c->tpm2_public_key_policyref,
pubkey_pcr_mask,
primary_alg,
blobs,
+10
View File
@@ -59,6 +59,7 @@ static size_t arg_tpm2_n_hash_pcr_values = 0;
static bool arg_tpm2_pin = false;
static char *arg_tpm2_public_key = NULL;
static bool arg_tpm2_load_public_key = true;
static char *arg_tpm2_public_key_policyref = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = 0;
static char *arg_tpm2_signature = NULL;
static char *arg_tpm2_pcrlock = NULL;
@@ -91,6 +92,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key_policyref, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep);
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
@@ -682,6 +684,13 @@ static int parse_argv(int argc, char *argv[]) {
arg_tpm2_load_public_key = true;
break;
OPTION_LONG("tpm2-public-key-policyref", "STRING",
"Enroll signed TPM2 PCR policy with the specified policy reference"):
r = free_and_strdup_warn(&arg_tpm2_public_key_policyref, opts.arg);
if (r < 0)
return r;
break;
OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+PCR3+…",
"Enroll signed TPM2 PCR policy for specified TPM2 PCRs"):
auto_public_key_pcr_mask = false;
@@ -926,6 +935,7 @@ static int enroll_context_from_args(EnrollContext *c) {
strdup_to(&c->tpm2_device, arg_tpm2_device) < 0 ||
strdup_to(&c->tpm2_device_key, arg_tpm2_device_key) < 0 ||
strdup_to(&c->tpm2_public_key, arg_tpm2_public_key) < 0 ||
strdup_to(&c->tpm2_public_key_policyref, arg_tpm2_public_key_policyref) < 0 ||
strdup_to(&c->tpm2_signature, arg_tpm2_signature) < 0 ||
strdup_to(&c->tpm2_pcrlock, arg_tpm2_pcrlock) < 0)
return log_oom();
+1
View File
@@ -77,6 +77,7 @@ typedef struct EnrollContext {
bool tpm2_pin;
char *tpm2_public_key;
bool tpm2_load_public_key;
char *tpm2_public_key_policyref;
uint32_t tpm2_public_key_pcr_mask;
char *tpm2_signature;
char *tpm2_pcrlock;
@@ -44,6 +44,7 @@ _public_ int cryptsetup_token_open_pin(
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_free_ char *pubkey_policy_ref = NULL;
_cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
@@ -92,6 +93,7 @@ _public_ int cryptsetup_token_open_pin(
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_policy_ref,
&pubkey_pcr_mask,
&primary_alg,
&blobs,
@@ -113,6 +115,7 @@ _public_ int cryptsetup_token_open_pin(
hash_pcr_mask,
pcr_bank,
&pubkey,
pubkey_policy_ref,
pubkey_pcr_mask,
params.signature_path,
pin_string,
@@ -181,7 +184,7 @@ _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 */) {
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *pubkey_str = NULL;
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *pubkey_str = NULL, *pubkey_policy_ref = NULL;
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
@@ -209,6 +212,7 @@ _public_ void cryptsetup_token_dump(
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_policy_ref,
&pubkey_pcr_mask,
&primary_alg,
&blobs,
@@ -237,6 +241,7 @@ _public_ void cryptsetup_token_dump(
crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank)));
crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
crypt_log(cd, "\ttpm2-pubkey-ref: %s\n", pubkey_policy_ref);
crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
if (primary_alg != 0)
crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg)));
@@ -19,6 +19,7 @@ int acquire_luks2_key(
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const struct iovec *pubkey,
const char *pubkey_policy_ref,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pin,
@@ -98,6 +99,7 @@ int acquire_luks2_key(
hash_pcr_mask,
pcr_bank,
pubkey,
pubkey_policy_ref,
pubkey_pcr_mask,
signature_json,
pin,
@@ -11,6 +11,7 @@ int acquire_luks2_key(
uint32_t pcr_mask,
uint16_t pcr_bank,
const struct iovec *pubkey,
const char *pubkey_policy_ref,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pin,
+4
View File
@@ -2053,6 +2053,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT_LEGACY : arg_tpm2_pcr_mask,
UINT16_MAX,
/* pubkey= */ NULL,
/* pubkey_policy_ref= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature_path= */ NULL,
/* pcrlock_path= */ NULL,
@@ -2108,6 +2109,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
for (;;) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_free_ char *pubkey_policy_ref = NULL;
struct iovec *blobs = NULL, *policy_hash = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
size_t n_blobs = 0, n_policy_hash = 0;
@@ -2124,6 +2126,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_policy_ref,
&pubkey_pcr_mask,
&primary_alg,
&blobs,
@@ -2158,6 +2161,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
hash_pcr_mask,
pcr_bank,
&pubkey,
pubkey_policy_ref,
pubkey_pcr_mask,
arg_tpm2_signature,
arg_tpm2_pcrlock,
+16 -1
View File
@@ -34,6 +34,7 @@
static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
static char **arg_banks = NULL;
static char *arg_tpm2_device = NULL;
static char *arg_policyref = NULL;
static char *arg_private_key = NULL;
static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
static char *arg_private_key_source = NULL;
@@ -49,6 +50,7 @@ static char *arg_append = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_policyref, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep);
@@ -207,6 +209,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
break;
}
OPTION_LONG("policyref", "STRING",
"Set a policyref and include this in the signatures"):
r = free_and_strdup_warn(&arg_policyref, opts.arg);
if (r < 0)
return r;
break;
OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"):
r = free_and_strdup_warn(&arg_private_key, opts.arg);
if (r < 0)
@@ -1047,7 +1056,12 @@ static int build_policy_digest(bool sign) {
/* We always use SHA256 for signing currently. Regardless of the bank. */
const EVP_MD *sha256 = ASSERT_PTR(sym_EVP_get_digestbyname("sha256"));
r = digest_and_sign(sha256, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss);
_cleanup_(iovec_done) struct iovec tbs_data = {};
r = tpm2_make_policy_authorize_tbs_data(&pcr_policy_digest, arg_policyref, &tbs_data);
if (r < 0)
return r;
r = digest_and_sign(sha256, privkey, tbs_data.iov_base, tbs_data.iov_len, &sig, &ss);
if (r == -EADDRNOTAVAIL)
return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", sym_EVP_MD_get0_name(p->md));
if (r < 0)
@@ -1071,6 +1085,7 @@ static int build_policy_digest(bool sign) {
r = sd_json_buildo(&bv,
SD_JSON_BUILD_PAIR_VARIANT("pcrs", a), /* PCR mask */
SD_JSON_BUILD_PAIR_CONDITION(pubkey_fp_size > 0, "pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
SD_JSON_BUILD_PAIR_CONDITION(!isempty(arg_policyref), "ref", SD_JSON_BUILD_STRING(arg_policyref)), /* TPM2 policy reference */
SD_JSON_BUILD_PAIR_HEX("pol", pcr_policy_digest.buffer, pcr_policy_digest.size), /* TPM2 policy hash that is signed */
SD_JSON_BUILD_PAIR_CONDITION(ss > 0, "sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */
if (r < 0)
+12
View File
@@ -193,6 +193,7 @@ static char *arg_tpm2_device_key = NULL;
static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL;
static size_t arg_tpm2_n_hash_pcr_values = 0;
static char *arg_tpm2_public_key = NULL;
static char *arg_tpm2_public_key_policyref = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = 0;
static char *arg_tpm2_pcrlock = NULL;
static bool arg_split = false;
@@ -235,6 +236,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key_policyref, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep);
STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_defer_partitions, freep);
@@ -5714,6 +5716,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
pcr_values,
n_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL,
iovec_is_set(&pubkey) ? arg_tpm2_public_key_policyref : NULL,
/* use_pin= */ false,
arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
policy_hash + 0);
@@ -5725,6 +5728,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
pcr_values,
n_pcr_values,
/* public= */ NULL, /* Turn this one off for the 2nd shard */
/* pubkey_policy_ref= */ NULL,
/* use_pin= */ false,
&pcrlock_policy, /* But turn this one on */
policy_hash + 1);
@@ -5801,6 +5805,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
hash_pcr_mask,
hash_pcr_bank,
&pubkey,
arg_tpm2_public_key_policyref,
arg_tpm2_public_key_pcr_mask,
/* primary_alg= */ 0,
blobs,
@@ -10480,6 +10485,13 @@ static int parse_argv(int argc, char *argv[]) {
break;
OPTION_LONG("tpm2-public-key-policyref", "STRING",
"Enroll signed TPM2 PCR policy with the specified policy reference"):
r = free_and_strdup_warn(&arg_tpm2_public_key_policyref, opts.arg);
if (r < 0)
return r;
break;
OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…",
"Enroll signed TPM2 PCR policy for specified TPM2 PCRs"):
auto_public_key_pcr_mask = false;
+2
View File
@@ -962,6 +962,7 @@ int encrypt_credential_and_warn(
tpm2_hash_pcr_values,
tpm2_n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL,
/* pubkey_policy_ref= */ NULL,
/* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&tpm2_policy);
@@ -1378,6 +1379,7 @@ int decrypt_credential_and_warn(
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL,
/* pubkey_policy_ref= */ NULL,
z ? le64toh(z->pcr_mask) : 0,
signature_json,
/* pin= */ NULL,
+8
View File
@@ -69,6 +69,7 @@ int acquire_tpm2_key(
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const struct iovec *pubkey,
const char *pubkey_policy_ref,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
@@ -160,6 +161,7 @@ int acquire_tpm2_key(
hash_pcr_mask,
pcr_bank,
pubkey,
pubkey_policy_ref,
pubkey_pcr_mask,
signature_json,
/* pin= */ NULL,
@@ -212,6 +214,7 @@ int acquire_tpm2_key(
hash_pcr_mask,
pcr_bank,
pubkey,
pubkey_policy_ref,
pubkey_pcr_mask,
signature_json,
b64_salted_pin,
@@ -250,6 +253,7 @@ int find_tpm2_auto_data(
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
struct iovec *ret_pubkey,
char **ret_pubkey_policy_ref,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec **ret_blobs,
@@ -270,6 +274,7 @@ int find_tpm2_auto_data(
assert(ret_hash_pcr_mask);
assert(ret_pcrlock_nv);
assert(ret_pubkey);
assert(ret_pubkey_policy_ref);
assert(ret_pubkey_pcr_mask);
assert(ret_primary_alg);
assert(ret_blobs);
@@ -285,6 +290,7 @@ int find_tpm2_auto_data(
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_free_ char *pubkey_policy_ref = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
struct iovec *blobs = NULL, *policy_hash = NULL;
size_t n_blobs = 0, n_policy_hash = 0;
@@ -308,6 +314,7 @@ int find_tpm2_auto_data(
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_policy_ref,
&pubkey_pcr_mask,
&primary_alg,
&blobs,
@@ -332,6 +339,7 @@ int find_tpm2_auto_data(
*ret_hash_pcr_mask = hash_pcr_mask;
*ret_pcr_bank = pcr_bank;
*ret_pubkey = TAKE_STRUCT(pubkey);
*ret_pubkey_policy_ref = TAKE_PTR(pubkey_policy_ref);
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
*ret_primary_alg = primary_alg;
*ret_blobs = TAKE_PTR(blobs);
+2
View File
@@ -11,6 +11,7 @@ int acquire_tpm2_key(
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const struct iovec *pubkey,
const char *pubkey_policy_ref,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
@@ -38,6 +39,7 @@ int find_tpm2_auto_data(
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
struct iovec *ret_pubkey,
char **ret_pubkey_policy_ref,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec **ret_blobs,
+103 -7
View File
@@ -4214,6 +4214,7 @@ static int find_signature(
const TPML_PCR_SELECTION *pcr_selection,
const void *fp,
size_t fp_size,
const char *policy_ref,
const void *policy,
size_t policy_size,
void *ret_signature,
@@ -4248,7 +4249,8 @@ static int find_signature(
/* Now iterate through all signatures known for this bank */
JSON_VARIANT_ARRAY_FOREACH(i, b) {
_cleanup_free_ void *fpj_data = NULL, *polj_data = NULL;
sd_json_variant *maskj, *fpj, *sigj, *polj;
const char *refj_data = NULL;
sd_json_variant *maskj, *fpj, *sigj, *polj, *refj;
size_t fpj_size, polj_size;
uint32_t parsed_mask;
@@ -4279,6 +4281,16 @@ static int find_signature(
if (memcmp_nn(fp, fp_size, fpj_data, fpj_size) != 0)
continue; /* Not for this public key */
refj = sd_json_variant_by_key(i, "ref");
if (refj) {
refj_data = sd_json_variant_string(refj);
if (!refj_data)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Policy reference is not a string.");
}
if (!streq_ptr(policy_ref, refj_data))
continue;
/* Finally, check if this is for the PCR policy we expect this to be */
polj = sd_json_variant_by_key(i, "pol");
if (!polj)
@@ -4943,7 +4955,7 @@ int tpm2_policy_pcr(
/* Extend 'digest' with the PolicyAuthorize calculated hash. */
int tpm2_calculate_policy_authorize(
const TPM2B_PUBLIC *public,
const TPM2B_DIGEST *policy_ref,
const TPM2B_NONCE *policy_ref,
TPM2B_DIGEST *digest) {
TPM2_CC command = TPM2_CC_PolicyAuthorize;
@@ -4999,11 +5011,35 @@ int tpm2_calculate_policy_authorize(
return 0;
}
static void tpm2_make_policy_ref(const char *policy_ref, TPM2B_NONCE *ret_policy_ref) {
assert(ret_policy_ref);
assert(SHA256_DIGEST_SIZE <= sizeof(ret_policy_ref->buffer));
/* A policy reference is represented by the TPM2B_NONCE type, which has a maximum size equivalent
* to the largest digest supported by the TPM. Because of this, digest the policy reference string.
* As we don't know what the maximum digest size supported by the TPM is when publishing a
* signed policy, we pick SHA256 which is universally supported on PC-Client devices. Ideally the
* algorithm ID would be prepended to the digest, but we don't know if the TPM2B_NONCE type can
* accomodate this. For this reason, the digest algorithm here should match the one used to create
* the approved digest for the corresponding policy, which is always SHA256 at the moment.
*
* Note that we return a policy ref size of 0 if no string is supplied in order to maintain
* backwards compatibility with previous systemd releases. */
if (isempty(policy_ref)) {
ret_policy_ref->size = 0;
return;
}
sha256_direct(policy_ref, strlen(policy_ref), ret_policy_ref->buffer);
ret_policy_ref->size = SHA256_DIGEST_SIZE;
}
static int tpm2_policy_authorize(
Tpm2Context *c,
const Tpm2Handle *session,
TPML_PCR_SELECTION *pcr_selection,
const TPM2B_PUBLIC *public,
const char *policy_ref,
const void *fp,
size_t fp_size,
sd_json_variant *signature_json,
@@ -5051,6 +5087,7 @@ static int tpm2_policy_authorize(
signature_json,
pcr_selection,
fp, fp_size,
policy_ref,
approved_policy->buffer,
approved_policy->size,
&signature_raw,
@@ -5060,8 +5097,13 @@ static int tpm2_policy_authorize(
/* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature,
* hence we need to do the SHA256 part ourselves, first */
TPM2B_DIGEST signature_hash = *approved_policy;
r = tpm2_digest_rehash(TPM2_ALG_SHA256, &signature_hash);
_cleanup_(iovec_done) struct iovec tbs_data = {};
r = tpm2_make_policy_authorize_tbs_data(approved_policy, policy_ref, &tbs_data);
if (r < 0)
return r;
TPM2B_DIGEST signature_hash;
r = tpm2_digest_buffer(TPM2_ALG_SHA256, &signature_hash, tbs_data.iov_base, tbs_data.iov_len, /* extend= */ false);
if (r < 0)
return r;
@@ -5101,6 +5143,9 @@ static int tpm2_policy_authorize(
check_ticket = &check_ticket_null;
}
TPM2B_NONCE policy_ref_tpm2b;
tpm2_make_policy_ref(policy_ref, &policy_ref_tpm2b);
rc = sym_Esys_PolicyAuthorize(
c->esys_context,
session->esys_handle,
@@ -5108,7 +5153,7 @@ static int tpm2_policy_authorize(
ESYS_TR_NONE,
ESYS_TR_NONE,
approved_policy,
/* policyRef= */ &(const TPM2B_NONCE) {},
&policy_ref_tpm2b,
pubkey_name,
check_ticket);
if (rc != TSS2_RC_SUCCESS)
@@ -5123,6 +5168,7 @@ int tpm2_calculate_sealing_policy(
const Tpm2PCRValue *pcr_values,
size_t n_pcr_values,
const TPM2B_PUBLIC *public,
const char *pubkey_policy_ref,
bool use_pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
TPM2B_DIGEST *digest) {
@@ -5139,7 +5185,9 @@ int tpm2_calculate_sealing_policy(
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies that combined signed PCR and pcrlock are not supported.");
if (public) {
r = tpm2_calculate_policy_authorize(public, NULL, digest);
TPM2B_NONCE policy_ref;
tpm2_make_policy_ref(pubkey_policy_ref, &policy_ref);
r = tpm2_calculate_policy_authorize(public, &policy_ref, digest);
if (r < 0)
return r;
}
@@ -5180,6 +5228,7 @@ static int tpm2_build_sealing_policy(
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const TPM2B_PUBLIC *public,
const char *pubkey_policy_ref,
const void *fp,
size_t fp_size,
uint32_t pubkey_pcr_mask,
@@ -5193,6 +5242,7 @@ static int tpm2_build_sealing_policy(
assert(c);
assert(session);
assert(pubkey_pcr_mask == 0 || public);
assert(!pubkey_policy_ref || public);
log_debug("Building sealing policy.");
@@ -5210,7 +5260,7 @@ static int tpm2_build_sealing_policy(
if (pubkey_pcr_mask != 0) {
TPML_PCR_SELECTION pcr_selection;
tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection);
r = tpm2_policy_authorize(c, session, &pcr_selection, public, fp, fp_size, signature_json, NULL);
r = tpm2_policy_authorize(c, session, &pcr_selection, public, pubkey_policy_ref, fp, fp_size, signature_json, NULL);
if (r < 0)
return r;
}
@@ -6343,6 +6393,32 @@ int tpm2_calculate_seal(
#endif
}
int tpm2_make_policy_authorize_tbs_data(
const TPM2B_DIGEST *approved_digest,
const char *policy_ref_data,
struct iovec *ret_tbs_data) {
TPM2B_NONCE policy_ref;
assert(approved_digest);
assert(ret_tbs_data);
tpm2_make_policy_ref(policy_ref_data, &policy_ref);
/* The data to be signed for a TPM2_PolicyAuthorize() is the concatenation of the approved policy
* digest and the policy reference, as per TPM2 Spec Part 3, 23.16. */
_cleanup_(iovec_done) struct iovec tbs_data = {};
if (!iovec_append(&tbs_data, &IOVEC_MAKE(approved_digest->buffer, approved_digest->size)))
return log_oom_debug();
if (!iovec_append(&tbs_data, &IOVEC_MAKE(policy_ref.buffer, policy_ref.size)))
return log_oom_debug();
*ret_tbs_data = TAKE_STRUCT(tbs_data);
return 0;
}
int tpm2_seal(Tpm2Context *c,
uint32_t seal_key_handle,
const TPM2B_DIGEST policy[],
@@ -6569,6 +6645,7 @@ int tpm2_unseal(Tpm2Context *c,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const struct iovec *pubkey,
const char *pubkey_policy_ref,
uint32_t pubkey_pcr_mask,
sd_json_variant *signature,
const char *pin,
@@ -6602,6 +6679,7 @@ int tpm2_unseal(Tpm2Context *c,
assert(n_blobs > 0);
assert(blobs);
assert(iovec_is_valid(pubkey));
assert(!pubkey_policy_ref || iovec_is_set(pubkey));
assert(ret_secret);
assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
@@ -6744,6 +6822,7 @@ int tpm2_unseal(Tpm2Context *c,
hash_pcr_mask,
pcr_bank,
shard == 0 && iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
shard == 0 ? pubkey_policy_ref : NULL,
fp.iov_base, fp.iov_len,
shard == 0 ? pubkey_pcr_mask : 0,
signature,
@@ -9642,6 +9721,7 @@ int tpm2_make_luks2_json(
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
const struct iovec *pubkey,
const char *pubkey_policy_ref,
uint32_t pubkey_pcr_mask,
uint16_t primary_alg,
const struct iovec blobs[],
@@ -9659,6 +9739,7 @@ int tpm2_make_luks2_json(
int r;
assert(iovec_is_valid(pubkey));
assert(!pubkey_policy_ref || iovec_is_set(pubkey));
assert(n_blobs >= 1);
assert(n_policy_hash >= 1);
@@ -9702,6 +9783,7 @@ int tpm2_make_luks2_json(
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK), "tpm2_pcrlock", SD_JSON_BUILD_BOOLEAN(true)),
SD_JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", SD_JSON_BUILD_VARIANT(pkmj)),
SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(pubkey), "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)),
SD_JSON_BUILD_PAIR_CONDITION(pubkey_policy_ref != NULL, "tpm2_pubkey_ref", SD_JSON_BUILD_STRING(pubkey_policy_ref)),
SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)),
SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk)),
SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv)));
@@ -9777,6 +9859,7 @@ int tpm2_parse_luks2_json(
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
struct iovec *ret_pubkey,
char **ret_pubkey_policy_ref,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec **ret_blobs,
@@ -9789,6 +9872,7 @@ int tpm2_parse_luks2_json(
TPM2Flags *ret_flags) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_free_ char *pubkey_ref = NULL;
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = 0;
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
@@ -9911,6 +9995,16 @@ int tpm2_parse_luks2_json(
} else if (pubkey_pcr_mask != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing.");
w = sd_json_variant_by_key(v, "tpm2_pubkey_ref");
if (w) {
const char *s = sd_json_variant_string(w);
if (!s)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key policy reference is not a string.");
pubkey_ref = strdup(s);
if (!pubkey_ref)
return log_oom_debug();
}
w = sd_json_variant_by_key(v, "tpm2_srk");
if (w) {
r = json_variant_unbase64_iovec(w, &srk);
@@ -9933,6 +10027,8 @@ int tpm2_parse_luks2_json(
*ret_pcr_bank = pcr_bank;
if (ret_pubkey)
*ret_pubkey = TAKE_STRUCT(pubkey);
if (ret_pubkey_policy_ref)
*ret_pubkey_policy_ref = TAKE_PTR(pubkey_ref);
if (ret_pubkey_pcr_mask)
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
if (ret_primary_alg)
+9 -5
View File
@@ -335,16 +335,18 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name);
int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name);
int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest);
int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_NONCE *policy_ref, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name);
int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *pcrlock_policy, TPM2B_DIGEST *digest);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, const char *pubkey_policy_ref, bool use_pin, const Tpm2PCRLockPolicy *pcrlock_policy, TPM2B_DIGEST *digest);
int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent);
int tpm2_make_policy_authorize_tbs_data(const TPM2B_DIGEST *approved_digest, const char *policy_ref_data, struct iovec *ret_tbs_data);
int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template);
int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
@@ -370,7 +372,7 @@ int tpm2_get_ek(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_pu
int tpm2_get_or_create_ek(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST policy_hash[], size_t n_policy, const char *pin, struct iovec *ret_secret, struct iovec **ret_blobs, size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk);
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec known_policy_hash[], size_t n_known_policy_hash, const struct iovec *srk, struct iovec *ret_secret);
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, const char *pubkey_policy_ref, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec known_policy_hash[], size_t n_known_policy_hash, const struct iovec *srk, struct iovec *ret_secret);
/* tpm2_unseal() returns a bunch of different errors for various flavours of PCR issues, let's group them */
#define ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r) IN_SET(r, -EREMCHG, -ENOANO, -EUCLEAN, -EPERM)
@@ -412,6 +414,7 @@ int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2
#define TPM2B_ENCRYPTED_SECRET_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_ENCRYPTED_SECRET, secret, size)
#define TPM2B_MAX_BUFFER_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_MAX_BUFFER, buffer, size)
#define TPM2B_NAME_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_NAME, name, size)
#define TPM2B_NONCE_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_NONCE, buffer, size)
#define TPM2B_PRIVATE_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_PRIVATE, buffer, size)
#define TPM2B_PRIVATE_KEY_RSA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_PRIVATE_KEY_RSA, buffer, size)
#define TPM2B_PUBLIC_KEY_RSA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_PUBLIC_KEY_RSA, buffer, size)
@@ -438,6 +441,7 @@ int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2
#define TPM2B_ENCRYPTED_SECRET_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_ENCRYPTED_SECRET, buffer)
#define TPM2B_MAX_BUFFER_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_MAX_BUFFER, buffer)
#define TPM2B_NAME_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_NAME, name)
#define TPM2B_NONCE_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_NONCE, buffer)
#define TPM2B_PRIVATE_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_PRIVATE, buffer)
#define TPM2B_PRIVATE_KEY_RSA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_PRIVATE_KEY_RSA, buffer)
#define TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_PUBLIC_KEY_RSA, buffer)
@@ -475,8 +479,8 @@ int tpm2_find_device_auto(char **ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, sd_json_variant **ret);
int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, const char *pubkey_policy_ref, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, char **ret_pubkey_policy_ref, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags);
/* Before v258 we used to bind to PCR 7 by default at various places if no explicit PCR mask was set. With
* v258 we stopped doing that (since the SecureBoot DB is as much subject to regular updates by tools such as
+28
View File
@@ -1019,6 +1019,32 @@ TEST(calculate_policy_authorize) {
assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628"));
}
TEST(make_policy_authorize_tbs_data) {
_cleanup_(iovec_done) struct iovec tbs = {};
DEFINE_HEX_PTR(digest, "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
TPM2B_DIGEST d = TPM2B_DIGEST_MAKE(digest, digest_len);
/* Without a policy reference the to-be-signed data is just the approved policy digest. */
ASSERT_OK_ZERO(tpm2_make_policy_authorize_tbs_data(&d, NULL, &tbs));
ASSERT_EQ(tbs.iov_len, d.size);
ASSERT_EQ(memcmp(tbs.iov_base, d.buffer, d.size), 0);
iovec_done(&tbs);
/* An empty (zero-length) policy reference should not result in a SHA256 digest being appended. */
const char empty[] = "";
ASSERT_OK_ZERO(tpm2_make_policy_authorize_tbs_data(&d, empty, &tbs));
ASSERT_EQ(tbs.iov_len, d.size);
iovec_done(&tbs);
/* A non-empty policy reference should result in a SHA256 digest being appended. */
const char ref[] = "initrd";
ASSERT_OK_ZERO(tpm2_make_policy_authorize_tbs_data(&d, ref, &tbs));
ASSERT_EQ(tbs.iov_len, (size_t) d.size + SHA256_DIGEST_SIZE);
ASSERT_EQ(memcmp(tbs.iov_base, d.buffer, d.size), 0);
ASSERT_EQ(memcmp((const uint8_t*) tbs.iov_base + d.size, SHA256_DIRECT(ref, strlen(ref)), SHA256_DIGEST_SIZE), 0);
}
TEST(calculate_policy_pcr) {
TPM2B_DIGEST d, dN[16];
@@ -1332,6 +1358,7 @@ static void calculate_seal_and_unseal(
/* hash_pcr_mask= */ 0,
/* pcr_bank= */ 0,
/* pubkey= */ NULL,
/* pubkey_policy_ref = */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature= */ NULL,
/* pin= */ NULL,
@@ -1419,6 +1446,7 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
/* hash_pcr_mask= */ 0,
/* pcr_bank= */ 0,
/* pubkey= */ NULL,
/* pubkey_policy_ref= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature= */ NULL,
/* pin= */ NULL,
+87
View File
@@ -1039,6 +1039,93 @@ def test_pcr_signing2(kernel_initrd, tmp_path):
shutil.rmtree(tmp_path)
@pytest.mark.skipif(not slow_tests, reason='slow')
def test_pcr_signing3(kernel_initrd, tmp_path):
if kernel_initrd is None:
pytest.skip('linux+initrd not found')
try:
systemd_measure()
except ValueError:
pytest.skip('systemd-measure not found')
ourdir = pathlib.Path(__file__).parent
pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64')
priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64')
# simulate a microcode file
with open(f'{tmp_path}/microcode', 'wb') as microcode:
microcode.write(b'1234567890')
output = f'{tmp_path}/signed.efi'
assert kernel_initrd[0] == '--linux'
opts = ukify.parse_args(
[
'build',
*kernel_initrd[:2],
f'--initrd={microcode.name}',
*kernel_initrd[2:],
f'--output={output}',
'--uname=1.2.3',
'--cmdline=ARG1 ARG2 ARG3',
'--os-release=ID=foobar\n',
'--pcr-banks=sha384',
f'--pcrpkey={pub.name}',
f'--pcr-private-key={priv.name}',
'--phases=enter-initrd enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit enter-initrd:leave-initrd:sysinit:ready',
'--policyref=',
f'--pcr-private-key={priv.name}',
'--phases=enter-initrd',
'--policyref=initrd',
]
+ arg_tools
)
try:
ukify.check_inputs(opts)
except OSError as e:
pytest.skip(str(e))
ukify.make_uki(opts)
# let's check that objdump likes the resulting file
dump = subprocess.check_output(['objdump', '-h', output], text=True)
for sect in 'text osrel cmdline linux initrd uname pcrsig'.split():
assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE)
subprocess.check_call(
[
'objcopy',
*(
f'--dump-section=.{n}={tmp_path}/out.{n}'
for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')
),
output,
tmp_path / 'dummy',
],
text=True,
)
assert open(tmp_path / 'out.pcrpkey').read() == open(pub.name).read()
assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n'
assert open(tmp_path / 'out.uname').read() == '1.2.3'
assert open(tmp_path / 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
assert open(tmp_path / 'out.initrd', 'rb').read(10) == b'1234567890'
sig = open(tmp_path / 'out.pcrsig').read()
sig = json.loads(sig)
assert list(sig.keys()) == ['sha384']
assert len(sig['sha384']) == 5 # five items for five phases paths
assert 'ref' not in sig['sha384'][0]
assert 'ref' not in sig['sha384'][1]
assert 'ref' not in sig['sha384'][2]
assert 'ref' not in sig['sha384'][3]
assert 'ref' in sig['sha384'][4]
assert sig['sha384'][4]['ref'] == 'initrd'
shutil.rmtree(tmp_path)
def test_key_cert_generation(tmp_path):
opts = ukify.parse_args(
[
+23 -4
View File
@@ -292,6 +292,7 @@ class UkifyConfig:
pcrsig: Union[str, Path, None]
join_pcrsig: Optional[Path]
phase_path_groups: Optional[list[str]]
policyrefs: Optional[list[str]]
policy_digest: bool
profile: Optional[str]
sb_cert: Union[str, Path, None]
@@ -701,7 +702,7 @@ def check_cert_and_keys_nonexistent(opts: UkifyConfig) -> None:
# Raise if any of the keys and certs are found on disk
paths: Iterator[Union[str, Path, None]] = itertools.chain(
(opts.sb_key, opts.sb_cert),
*((priv_key, pub_key, cert) for priv_key, pub_key, cert, _ in key_path_groups(opts)),
*((priv_key, pub_key, cert) for priv_key, pub_key, cert, _, _ in key_path_groups(opts)),
)
for path in paths:
if path and Path(path).exists():
@@ -739,7 +740,9 @@ def combine_signatures(pcrsigs: list[dict[str, str]]) -> str:
return json.dumps(combined)
def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Optional[str], Optional[str]]]:
def key_path_groups(
opts: UkifyConfig,
) -> Iterator[tuple[str, Optional[str], Optional[str], Optional[str], Optional[str]]]:
if not opts.pcr_private_keys:
return
@@ -747,12 +750,14 @@ def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Opt
pub_keys = opts.pcr_public_keys or []
certs = opts.pcr_certificates or []
pp_groups = opts.phase_path_groups or []
policyrefs = opts.policyrefs or []
yield from itertools.zip_longest(
opts.pcr_private_keys,
pub_keys[:n_priv],
certs[:n_priv],
pp_groups[:n_priv],
policyrefs[:n_priv],
fillvalue=None,
)
@@ -874,7 +879,7 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
*(f'--bank={bank}' for bank in banks),
]
for priv_key, pub_key, cert, group in key_path_groups(opts):
for priv_key, pub_key, cert, group, ref in key_path_groups(opts):
extra = [f'--private-key={priv_key}']
if opts.signing_engine is not None:
assert pub_key or cert
@@ -899,6 +904,8 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
extra += [f'--certificate-source=provider:{opts.certificate_provider}']
extra += [f'--phase={phase_path}' for phase_path in group or ()]
if ref is not None:
extra += [f'--policyref={ref}']
print('+', shell_join(cmd + extra), file=sys.stderr) # type: ignore
output = subprocess.check_output(cmd + extra, text=True) # type: ignore
@@ -1678,7 +1685,7 @@ def generate_keys(opts: UkifyConfig) -> None:
work = True
for priv_key, pub_key, _, _ in key_path_groups(opts):
for priv_key, pub_key, _, _, _ in key_path_groups(opts):
priv_key_pem, pub_key_pem = generate_priv_pub_key_pair()
print(f'Writing private key for PCR signing to {priv_key}', file=sys.stderr)
@@ -2250,6 +2257,15 @@ CONFIG_ITEMS = [
config_key='PCRSignature:/Phases',
config_push=ConfigItem.config_set_group,
),
ConfigItem(
'--policyref',
dest='policyrefs',
metavar='STRING',
action='append',
help='policy references to bind signatures to',
config_key='PCRSignature:/PolicyRef',
config_push=ConfigItem.config_set_group,
),
ConfigItem(
'--tools',
type=Path,
@@ -2446,6 +2462,7 @@ def finalize_options(opts: argparse.Namespace) -> None:
n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys)
n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys)
n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups)
n_policyrefs = None if opts.policyrefs is None else len(opts.policyrefs)
if opts.policy_digest and n_pcr_priv is not None:
raise ValueError('--pcr-private-key= cannot be specified with --policy-digest')
if (
@@ -2462,6 +2479,8 @@ def finalize_options(opts: argparse.Namespace) -> None:
raise ValueError('--pcr-public-key= and --pcr-certificate= cannot be used at the same time')
if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv:
raise ValueError('--phases= specifications must match --pcr-private-key=')
if n_policyrefs is not None and n_policyrefs != n_pcr_priv:
raise ValueError('--policyref= specifications must match --pcr-private-key=')
opts.cmdline = resolve_at_path(opts.cmdline)
+25
View File
@@ -128,6 +128,7 @@ tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000
systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig2" | cmp - /tmp/pcrtestdata
# Now, do the same, but with a cryptsetup binding
dd if=/dev/urandom of=/tmp/passphrase bs=32 count=1
truncate -s 20M "$IMAGE"
cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase
# Ensure that an unrelated signature, when not requested, is not used
@@ -175,4 +176,28 @@ systemd-cryptsetup detach test-volume2
"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig6" >"/tmp/pcrsign.sig7"
cmp "/tmp/pcrsign.sig5" "/tmp/pcrsign.sig7"
# Enroll a keyslot bound to a policyref
systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE"
systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" --tpm2-public-key-policyref="foo" "$IMAGE"
# Unlocking should fail initially as there is no signature for this policyref
(! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1)
(! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1)
# Create a signature for a new phase with the enrolled policyref
"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: --policyref="foo" --append="/tmp/pcrsign.sig3" >"/tmp/pcrsign.sig8"
(! cmp "/tmp/pcrsign.sig3" "/tmp/pcrsign.sig8")
# Unlocking should work now
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig8",headless=1
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup detach test-volume2
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig8",headless=1
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup detach test-volume2
# After extending the PCR things should fail
tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
(! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig8",headless=1)
(! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig8",headless=1)
rm -f "$IMAGE"