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"