diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml
index 9f090ec94b..7cbc783edc 100644
--- a/man/systemd-cryptenroll.xml
+++ b/man/systemd-cryptenroll.xml
@@ -737,6 +737,30 @@
+
+
+
+ Configures the policy reference to use together with the signed PCR policy enrolled via
+ . The policy reference is an opaque string that is incorporated
+ into the resulting authorization policy (via the TPM2_PolicyAuthorize 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 of
+ systemd-measure1
+ 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.
+
+ 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 and
+ to
+ systemd-measure1:
+
+
+ systemd-cryptenroll --tpm2-device=auto --tpm2-public-key-policyref=initrd /dev/sda1
+
+
+
+
diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml
index ce19840088..a81b311bda 100644
--- a/man/systemd-measure.xml
+++ b/man/systemd-measure.xml
@@ -277,6 +277,33 @@
+
+
+
+ When generating a PCR JSON signature (via the sign 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
+ TPM2_PolicyAuthorize 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.
+
+ This can be used together with and 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 of
+ systemd-cryptenroll1
+ and
+ systemd-repart8.
+
+ It is recommended to use this option to create a signed policy just for the initrd boot phase
+ by combining it with for secrets that should only be
+ recoverable from the initrd, particularly FDE keys.
+
+
+
@@ -420,6 +447,64 @@ Wrote unsigned vmlinux-1.2.3.efi
bound to tpm2-pcr-initrd-public-key.pem, specify
on the command line of those tools.
+
+
+ Using a policy reference to add a signature only for the initrd boot phase
+
+ 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 so that secrets or credentials can be bound only
+ to this more restricted policy, using this reference.
+
+ $ 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
+
+
+ ukify prints out both invocations of systemd-measure as
+ informative output (the lines starting with +); this allows us to see how
+ systemd-measure is called. It then merges the output of both invocations into the
+ .pcrsig section. systemd-measure may also do this merge itself
+ using the option.
+
+ Note that in this example, the .pcrsig 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 initrd policy reference. To use the
+ stricter policy, supply to systemd-cryptenroll.
+ To use the more relaxed policy that includes all boot phases, omit the
+ of systemd-cryptenroll. Note that policies for systemd-creds
+ cannot currently bind to signed policies with a non-empty policy reference.
+
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 271366e5ef..dbf9edad21 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -445,6 +445,17 @@
+
+
+
+ Configures the policy reference to use together with the signed PCR policy configured
+ via . See
+ systemd-cryptenroll1
+ for details on this option.
+
+
+
+
diff --git a/man/ukify.xml b/man/ukify.xml
index 094ecdf9f6..f1eb00617a 100644
--- a/man/ukify.xml
+++ b/man/ukify.xml
@@ -723,6 +723,25 @@
+
+
+ PolicyRef=STRING
+ --policyref=STRING
+
+ A policy reference to bind the generated signature to.
+
+ 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 TPM2_PolicyAuthorize 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.
+
+ On the command line, when this argument is present, it must appear the same number of times as
+ the option.
+
+
+
diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c
index eb08abdec6..f31ed2e7ea 100644
--- a/src/cryptenroll/cryptenroll-tpm2.c
+++ b/src/cryptenroll/cryptenroll-tpm2.c
@@ -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,
diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c
index 67386f31d8..9211efef6f 100644
--- a/src/cryptenroll/cryptenroll.c
+++ b/src/cryptenroll/cryptenroll.c
@@ -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();
diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h
index 5fdb4e713c..9bc51bcead 100644
--- a/src/cryptenroll/cryptenroll.h
+++ b/src/cryptenroll/cryptenroll.h
@@ -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;
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
index 70113268b6..552d81da17 100644
--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
@@ -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)));
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
index ab7d01f137..edc0e7de5b 100644
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
@@ -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,
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
index 63c2321ae7..7668d4bc93 100644
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
@@ -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,
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index 50f78a2004..5f02e64da8 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -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,
diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c
index 70971c11ad..0e5762a682 100644
--- a/src/measure/measure-tool.c
+++ b/src/measure/measure-tool.c
@@ -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)
diff --git a/src/repart/repart.c b/src/repart/repart.c
index 1b1ad673c9..e0e494a6f2 100644
--- a/src/repart/repart.c
+++ b/src/repart/repart.c
@@ -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;
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index 739973aa46..f28fc2d758 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -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,
diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c
index bd27d3e6d6..5d68f9bba4 100644
--- a/src/shared/cryptsetup-tpm2.c
+++ b/src/shared/cryptsetup-tpm2.c
@@ -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);
diff --git a/src/shared/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h
index a3d456af4f..f27204dda1 100644
--- a/src/shared/cryptsetup-tpm2.h
+++ b/src/shared/cryptsetup-tpm2.h
@@ -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,
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index fdb97d27a8..f0e86ad364 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -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)
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 042d5d7d18..eb910b1e84 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -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
diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c
index 863092b9ce..2b8c6fb051 100644
--- a/src/test/test-tpm2.c
+++ b/src/test/test-tpm2.c
@@ -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,
diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py
index bed9da4dd2..58ad37c752 100755
--- a/src/ukify/test/test_ukify.py
+++ b/src/ukify/test/test_ukify.py
@@ -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(
[
diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py
index 074575902c..da156d31b3 100755
--- a/src/ukify/ukify.py
+++ b/src/ukify/ukify.py
@@ -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)
diff --git a/test/units/TEST-70-TPM2.measure.sh b/test/units/TEST-70-TPM2.measure.sh
index 90d6390da0..55d6fef156 100755
--- a/test/units/TEST-70-TPM2.measure.sh
+++ b/test/units/TEST-70-TPM2.measure.sh
@@ -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"