diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index db83ddc6e550b..ca1b433201af7 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -10,6 +10,7 @@ #include "errno-util.h" #include "fileio.h" #include "hexdecoct.h" +#include "json-util.h" #include "log.h" #include "memory-util.h" #include "random-util.h" @@ -18,20 +19,20 @@ static int search_policy_hash( struct crypt_device *cd, - const struct iovec *hash) { + const struct iovec policy_hash[], + size_t n_policy_hash) { int r; assert(cd); - assert(iovec_is_valid(hash)); - if (!iovec_is_set(hash)) + /* Searches among the already enrolled TPM2 tokens for one that matches the exact set of policies specified */ + + if (n_policy_hash == 0) return -ENOENT; for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_free_ void *thash = NULL; - size_t thash_size = 0; int keyslot; sd_json_variant *w; @@ -54,12 +55,45 @@ static int search_policy_hash( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); - r = sd_json_variant_unhex(w, &thash, &thash_size); - if (r < 0) - return log_error_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); + /* This is either an array of strings (for sharded enrollments), or a single string */ + if (sd_json_variant_is_array(w)) { + + if (sd_json_variant_elements(w) == n_policy_hash) { + sd_json_variant *i; + bool match = true; + size_t j = 0; + + JSON_VARIANT_ARRAY_FOREACH(i, w) { + _cleanup_(iovec_done) struct iovec thash = {}; + + r = sd_json_variant_unhex(i, &thash.iov_base, &thash.iov_len); + if (r < 0) + return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field item : %m"); + + if (iovec_memcmp(policy_hash + j, &thash) != 0) { + match = false; + break; + } + + j++; + } + + assert(j == n_policy_hash); - if (memcmp_nn(hash->iov_base, hash->iov_len, thash, thash_size) == 0) - return keyslot; /* Found entry with same hash. */ + if (match) /* Found entry with the exact same set of hashes */ + return keyslot; + } + + } else if (n_policy_hash == 1) { + _cleanup_(iovec_done) struct iovec thash = {}; + + r = sd_json_variant_unhex(w, &thash.iov_base, &thash.iov_len); + if (r < 0) + return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field: %m"); + + if (iovec_memcmp(policy_hash + 0, &thash) == 0) + return keyslot; /* Found entry with same hash. */ + } } return -ENOENT; /* Not found */ @@ -154,12 +188,16 @@ int load_volume_key_tpm2( for (;;) { _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; - _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; + struct iovec *blobs = NULL, *policy_hash = NULL; + size_t n_blobs = 0, n_policy_hash = 0; uint32_t hash_pcr_mask, pubkey_pcr_mask; uint16_t pcr_bank, primary_alg; TPM2Flags tpm2_flags; int keyslot; + CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + r = find_tpm2_auto_data( cd, UINT32_MAX, @@ -169,8 +207,10 @@ int load_volume_key_tpm2( &pubkey, &pubkey_pcr_mask, &primary_alg, - &blob, + &blobs, + &n_blobs, &policy_hash, + &n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -202,8 +242,10 @@ int load_volume_key_tpm2( /* pcrlock_path= */ NULL, primary_alg, /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ - &blob, - &policy_hash, + blobs, + n_blobs, + policy_hash, + n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -257,7 +299,7 @@ int enroll_tpm2(struct crypt_device *cd, _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *signature_json = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; - _cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {}; + _cleanup_(iovec_done) struct iovec srk = {}, pubkey = {}; _cleanup_(iovec_done_erase) struct iovec secret = {}; const char *node; _cleanup_(erase_and_freep) char *pin_str = NULL; @@ -304,10 +346,7 @@ int enroll_tpm2(struct crypt_device *cd, } TPM2B_PUBLIC public = {}; - /* Load the PCR public key if specified explicitly, or if no pcrlock policy was specified and - * automatic loading of PCR public keys wasn't disabled explicitly. The reason we turn this off when - * pcrlock is configured is simply that we currently not support both in combination. */ - if (pcr_pubkey_path || (load_pcr_pubkey && !pcrlock_path)) { + if (pcr_pubkey_path || load_pcr_pubkey) { r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); if (r < 0) { if (pcr_pubkey_path || signature_path || r != -ENOENT) @@ -398,42 +437,88 @@ int enroll_tpm2(struct crypt_device *cd, return log_error_errno(r, "Failed to determine best PCR bank: %m"); } - TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + /* Unfortunately TPM2 policy semantics make it very hard to combine PolicyAuthorize (which we need + * for signed PCR policies) and PolicyAuthorizeNV (which we need for pcrlock policies). Hence, let's + * use a "sharded" secret, and lock the first shard to the signed PCR policy, and the 2nd to the + * pcrlock – if both are requested. */ + + TPM2B_DIGEST policy_hash[2] = { + TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), + TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), + }; + size_t n_policy_hash = 1; + + /* If both PCR public key unlock and pcrlock unlock is selected, then we create the one for PCR public key unlock first. */ r = tpm2_calculate_sealing_policy( hash_pcr_values, n_hash_pcr_values, iovec_is_set(&pubkey) ? &public : NULL, use_pin, - pcrlock_path ? &pcrlock_policy : NULL, - &policy); + pcrlock_path && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, + policy_hash + 0); if (r < 0) return r; - if (device_key) + if (pcrlock_path && iovec_is_set(&pubkey)) { + r = tpm2_calculate_sealing_policy( + hash_pcr_values, + n_hash_pcr_values, + /* public= */ NULL, /* This one is off now */ + use_pin, + &pcrlock_policy, /* And this one on instead. */ + policy_hash + 1); + if (r < 0) + return r; + + n_policy_hash ++; + } + + struct iovec *blobs = NULL; + size_t n_blobs = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + + if (device_key) { + if (n_policy_hash > 1) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently."); + + blobs = new0(struct iovec, 1); + if (!blobs) + return log_oom(); + + n_blobs = 1; + r = tpm2_calculate_seal( seal_key_handle, &device_key_public, /* attributes= */ NULL, /* secret= */ NULL, - &policy, + policy_hash + 0, pin_str, &secret, - &blob, + blobs + 0, &srk); - else + } else r = tpm2_seal(tpm2_context, seal_key_handle, - &policy, + policy_hash, + n_policy_hash, pin_str, &secret, - &blob, + &blobs, + &n_blobs, /* ret_primary_alg= */ NULL, &srk); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); + struct iovec policy_hash_as_iovec[2] = { + IOVEC_MAKE(policy_hash[0].buffer, policy_hash[0].size), + IOVEC_MAKE(policy_hash[1].buffer, policy_hash[1].size), + }; + /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */ - r = search_policy_hash(cd, &IOVEC_MAKE(policy.buffer, policy.size)); + r = search_policy_hash(cd, policy_hash_as_iovec, n_policy_hash); if (r == -ENOENT) log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); else if (r < 0) @@ -461,8 +546,10 @@ int enroll_tpm2(struct crypt_device *cd, pin_str, pcrlock_path ? &pcrlock_policy : NULL, /* primary_alg= */ 0, - &blob, - &IOVEC_MAKE(policy.buffer, policy.size), + blobs, + n_blobs, + policy_hash_as_iovec, + n_policy_hash, &srk, &secret2); if (r < 0) @@ -498,8 +585,10 @@ int enroll_tpm2(struct crypt_device *cd, &pubkey, pubkey_pcr_mask, /* primary_alg= */ 0, - &blob, - &IOVEC_MAKE(policy.buffer, policy.size), + blobs, + n_blobs, + policy_hash_as_iovec, + n_policy_hash, use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, &srk, pcrlock_path ? &pcrlock_policy.nv_handle : NULL, diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 340d7f77362ec..46058dfb19fd8 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -42,7 +42,7 @@ _public_ int cryptsetup_token_open_pin( void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL; - _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; _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; @@ -76,6 +76,11 @@ _public_ int cryptsetup_token_open_pin( if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); + struct iovec *blobs = NULL, *policy_hash = NULL; + size_t n_blobs = 0, n_policy_hash = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + r = tpm2_parse_luks2_json( v, /* ret_keyslot= */ NULL, @@ -84,8 +89,10 @@ _public_ int cryptsetup_token_open_pin( &pubkey, &pubkey_pcr_mask, &primary_alg, - &blob, + &blobs, + &n_blobs, &policy_hash, + &n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -106,8 +113,10 @@ _public_ int cryptsetup_token_open_pin( pin_string, params.pcrlock_path, primary_alg, - &blob, - &policy_hash, + blobs, + n_blobs, + policy_hash, + n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -167,8 +176,8 @@ _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, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL; - _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *pubkey_str = 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; uint16_t pcr_bank, primary_alg; @@ -181,6 +190,11 @@ _public_ void cryptsetup_token_dump( if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); + struct iovec *blobs = NULL, *policy_hash = NULL; + size_t n_blobs = 0, n_policy_hash = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + r = tpm2_parse_luks2_json( v, NULL, @@ -189,8 +203,10 @@ _public_ void cryptsetup_token_dump( &pubkey, &pubkey_pcr_mask, &primary_alg, - &blob, + &blobs, + &n_blobs, &policy_hash, + &n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -206,30 +222,40 @@ _public_ void cryptsetup_token_dump( if (!pubkey_pcrs_str) return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); - r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str); - if (r < 0) - return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); - r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); - r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str); - if (r < 0) - return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); - 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-pcrs: %s\n", strna(pubkey_pcrs_str)); crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); - crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); - crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt))); crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk))); crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv))); + + FOREACH_ARRAY(p, policy_hash, n_policy_hash) { + _cleanup_free_ char *policy_hash_str = NULL; + + r = crypt_dump_buffer_to_hex_string(p->iov_base, p->iov_len, &policy_hash_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); + } + + FOREACH_ARRAY(b, blobs, n_blobs) { + _cleanup_free_ char *blob_str = NULL; + + r = crypt_dump_buffer_to_hex_string(b->iov_base, b->iov_len, &blob_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); + } } /* @@ -313,9 +339,18 @@ _public_ int cryptsetup_token_validate( return 1; } - r = sd_json_variant_unbase64(w, NULL, NULL); - if (r < 0) - return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); + if (sd_json_variant_is_array(w)) { + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, w) { + r = sd_json_variant_unbase64(i, /* ret= */ NULL, /* ret_size= */ NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); + } + } else { + r = sd_json_variant_unbase64(w, /* ret= */ NULL, /* ret_size= */ NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); + } w = sd_json_variant_by_key(v, "tpm2-policy-hash"); if (!w) { @@ -323,9 +358,18 @@ _public_ int cryptsetup_token_validate( return 1; } - r = sd_json_variant_unhex(w, NULL, NULL); - if (r < 0) - return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); + if (sd_json_variant_is_array(w)) { + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, w) { + r = sd_json_variant_unhex(i, /* ret= */ NULL, /* ret_size= */ NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid hex data in 'tpm2-policy-hash' field: %m"); + } + } else { + r = sd_json_variant_unhex(w, /* ret= */ NULL, /* ret_size= */ NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid hex data in 'tpm2-policy-hash' field: %m"); + } w = sd_json_variant_by_key(v, "tpm2-pin"); if (w) { diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 4881012b0f320..85fc12f09ad72 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -24,8 +24,10 @@ int acquire_luks2_key( const char *pin, const char *pcrlock_path, uint16_t primary_alg, - const struct iovec *blob, - const struct iovec *policy_hash, + 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, @@ -101,8 +103,10 @@ int acquire_luks2_key( pin, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, primary_alg, - blob, + blobs, + n_blobs, policy_hash, + n_policy_hash, srk, ret_decrypted_key); if (r < 0) diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index c3a01dfb3e547..63c2321ae7e33 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -16,8 +16,10 @@ int acquire_luks2_key( const char *pin, const char *pcrlock_path, uint16_t primary_alg, - const struct iovec *key_data, - const struct iovec *policy_hash, + 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, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 8fddff2a8b9b2..0620b1cffb11c 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1862,8 +1862,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( /* pcrlock_path= */ NULL, /* primary_alg= */ 0, key_file, arg_keyfile_size, arg_keyfile_offset, - key_data, + key_data, /* n_blobs= */ 1, /* policy_hash= */ NULL, /* we don't know the policy hash */ + /* n_policy_hash= */ 0, /* salt= */ NULL, /* srk= */ NULL, /* pcrlock_nv= */ NULL, @@ -1911,11 +1912,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( for (;;) { _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; - _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; + struct iovec *blobs = NULL, *policy_hash = NULL; uint32_t hash_pcr_mask, pubkey_pcr_mask; + size_t n_blobs = 0, n_policy_hash = 0; uint16_t pcr_bank, primary_alg; TPM2Flags tpm2_flags; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + r = find_tpm2_auto_data( cd, arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */ @@ -1925,8 +1930,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( &pubkey, &pubkey_pcr_mask, &primary_alg, - &blob, + &blobs, + &n_blobs, &policy_hash, + &n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -1960,8 +1967,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( arg_tpm2_pcrlock, primary_alg, /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ - &blob, - &policy_hash, + blobs, + n_blobs, + policy_hash, + n_policy_hash, &salt, &srk, &pcrlock_nv, diff --git a/src/partition/repart.c b/src/partition/repart.c index 8d8d133c0529b..118f7be6180d5 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -4236,7 +4236,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { #if HAVE_TPM2 - _cleanup_(iovec_done) struct iovec pubkey = {}, blob = {}, srk = {}; + _cleanup_(iovec_done) struct iovec pubkey = {}, srk = {}; _cleanup_(iovec_done_erase) struct iovec secret = {}; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; ssize_t base64_encoded_size; @@ -4309,35 +4309,71 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return log_error_errno(r, "Could not get hash mask: %m"); } - TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + TPM2B_DIGEST policy_hash[2] = { + TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), + TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), + }; + size_t n_policy_hash = 1; + + /* If both PCR public key unlock and pcrlock unlock is selected, then shard the encryption key. */ r = tpm2_calculate_sealing_policy( arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, iovec_is_set(&pubkey) ? &public : NULL, /* use_pin= */ false, - arg_tpm2_pcrlock ? &pcrlock_policy : NULL, - &policy); + arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, + policy_hash + 0); if (r < 0) - return log_error_errno(r, "Could not calculate sealing policy digest: %m"); + return log_error_errno(r, "Could not calculate sealing policy digest for shard 0: %m"); + + if (arg_tpm2_pcrlock && iovec_is_set(&pubkey)) { + r = tpm2_calculate_sealing_policy( + arg_tpm2_hash_pcr_values, + arg_tpm2_n_hash_pcr_values, + /* pubkey= */ NULL, /* Turn this one off for the 2nd shard */ + /* use_pin= */ false, + &pcrlock_policy, /* But turn this one on */ + policy_hash + 1); + if (r < 0) + return log_error_errno(r, "Could not calculate sealing policy digest for shard 1: %m"); + + n_policy_hash++; + } + + struct iovec *blobs = NULL; + size_t n_blobs = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + + if (arg_tpm2_device_key) { + if (n_policy_hash > 1) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently."); + + blobs = new0(struct iovec, 1); + if (!blobs) + return log_oom(); + + n_blobs = 1; - if (arg_tpm2_device_key) r = tpm2_calculate_seal( arg_tpm2_seal_key_handle, &device_key_public, /* attributes= */ NULL, /* secret= */ NULL, - &policy, + policy_hash + 0, /* pin= */ NULL, &secret, - &blob, + blobs + 0, &srk); - else + } else r = tpm2_seal(tpm2_context, arg_tpm2_seal_key_handle, - &policy, + policy_hash, + n_policy_hash, /* pin= */ NULL, &secret, - &blob, + &blobs, + &n_blobs, /* ret_primary_alg= */ NULL, &srk); if (r < 0) @@ -4361,6 +4397,11 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new TPM2 key: %m"); + struct iovec policy_hash_as_iovec[2] = { + IOVEC_MAKE(policy_hash[0].buffer, policy_hash[0].size), + IOVEC_MAKE(policy_hash[1].buffer, policy_hash[1].size), + }; + r = tpm2_make_luks2_json( keyslot, hash_pcr_mask, @@ -4368,8 +4409,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta &pubkey, arg_tpm2_public_key_pcr_mask, /* primary_alg= */ 0, - &blob, - &IOVEC_MAKE(policy.buffer, policy.size), + blobs, + n_blobs, + policy_hash_as_iovec, + n_policy_hash, /* salt= */ NULL, /* no salt because tpm2_seal has no pin */ &srk, &pcrlock_policy.nv_handle, diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 7c7ef66b99db0..c7a5026737497 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -957,12 +957,18 @@ int encrypt_credential_and_warn( if (r < 0) return log_error_errno(r, "Could not calculate sealing policy digest: %m"); + struct iovec *blobs = NULL; + size_t n_blobs = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + r = tpm2_seal(tpm2_context, /* seal_key_handle= */ 0, &tpm2_policy, + /* n_policy_hash= */ 1, /* pin= */ NULL, &tpm2_key, - &tpm2_blob, + &blobs, + &n_blobs, &tpm2_primary_alg, /* ret_srk= */ NULL); if (r < 0) { @@ -977,6 +983,9 @@ int encrypt_credential_and_warn( if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash)) return log_oom(); + assert(n_blobs == 1); + tpm2_blob = TAKE_STRUCT(blobs[0]); + assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); } @@ -1341,7 +1350,9 @@ int decrypt_credential_and_warn( /* pcrlock_policy= */ NULL, le16toh(t->primary_alg), &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)), + /* n_blobs= */ 1, &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)), + /* n_policy_hash= */ 1, /* srk= */ NULL, &tpm2_key); if (r < 0) diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c index 95c01678aa95c..bc4fb100bb156 100644 --- a/src/shared/cryptsetup-tpm2.c +++ b/src/shared/cryptsetup-tpm2.c @@ -70,8 +70,10 @@ int acquire_tpm2_key( const char *key_file, size_t key_file_size, uint64_t key_file_offset, - const struct iovec *key_data, - const struct iovec *policy_hash, + 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, @@ -82,9 +84,8 @@ int acquire_tpm2_key( struct iovec *ret_decrypted_key) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL; - _cleanup_free_ void *loaded_blob = NULL; + _cleanup_(iovec_done) struct iovec loaded_blob = {}; _cleanup_free_ char *auto_device = NULL; - struct iovec blob; int r; assert(iovec_is_valid(salt)); @@ -99,9 +100,7 @@ int acquire_tpm2_key( device = auto_device; } - if (iovec_is_set(key_data)) - blob = *key_data; - else { + if (n_blobs == 0) { _cleanup_free_ char *bindname = NULL; /* If we read the salt via AF_UNIX, make this client recognizable */ @@ -114,11 +113,12 @@ int acquire_tpm2_key( key_file_size == 0 ? SIZE_MAX : key_file_size, READ_FULL_FILE_CONNECT_SOCKET, bindname, - (char**) &loaded_blob, &blob.iov_len); + (char**) &loaded_blob.iov_base, &loaded_blob.iov_len); if (r < 0) return r; - blob.iov_base = loaded_blob; + blobs = &loaded_blob; + n_blobs = 1; } if (pubkey_pcr_mask != 0) { @@ -158,8 +158,10 @@ int acquire_tpm2_key( /* pin= */ NULL, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, primary_alg, - &blob, + blobs, + n_blobs, policy_hash, + n_policy_hash, srk, ret_decrypted_key); if (r < 0) @@ -204,8 +206,10 @@ int acquire_tpm2_key( b64_salted_pin, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, primary_alg, - &blob, + blobs, + n_blobs, policy_hash, + n_policy_hash, srk, ret_decrypted_key); if (r < 0) { @@ -230,8 +234,10 @@ int find_tpm2_auto_data( struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, - struct iovec *ret_blob, - struct iovec *ret_policy_hash, + 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, @@ -242,15 +248,35 @@ int find_tpm2_auto_data( int r, token; assert(cd); + assert(ret_hash_pcr_mask); + assert(ret_pcrlock_nv); + assert(ret_pubkey); + assert(ret_pubkey_pcr_mask); + assert(ret_primary_alg); + assert(ret_blobs); + assert(ret_n_blobs); + assert(ret_policy_hash); + assert(ret_n_policy_hash); + assert(ret_salt); + assert(ret_srk); + assert(ret_pcrlock_nv); + assert(ret_flags); + assert(ret_keyslot); + assert(ret_token); for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { - _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; _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; uint32_t hash_pcr_mask, pubkey_pcr_mask; uint16_t pcr_bank, primary_alg; TPM2Flags flags; int keyslot; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) continue; @@ -265,8 +291,10 @@ int find_tpm2_auto_data( &pubkey, &pubkey_pcr_mask, &primary_alg, - &blob, + &blobs, + &n_blobs, &policy_hash, + &n_policy_hash, &salt, &srk, &pcrlock_nv, @@ -287,8 +315,10 @@ int find_tpm2_auto_data( *ret_pubkey = TAKE_STRUCT(pubkey); *ret_pubkey_pcr_mask = pubkey_pcr_mask; *ret_primary_alg = primary_alg; - *ret_blob = TAKE_STRUCT(blob); - *ret_policy_hash = TAKE_STRUCT(policy_hash); + *ret_blobs = TAKE_PTR(blobs); + *ret_n_blobs = n_blobs; + *ret_policy_hash = TAKE_PTR(policy_hash); + *ret_n_policy_hash = n_policy_hash; *ret_salt = TAKE_STRUCT(salt); *ret_keyslot = keyslot; *ret_token = token; diff --git a/src/shared/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h index b9905f4f4b9fd..240a09077fdb6 100644 --- a/src/shared/cryptsetup-tpm2.h +++ b/src/shared/cryptsetup-tpm2.h @@ -24,8 +24,10 @@ int acquire_tpm2_key( const char *key_file, size_t key_file_size, uint64_t key_file_offset, - const struct iovec *key_data, - const struct iovec *policy_hash, + 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, @@ -44,8 +46,10 @@ int find_tpm2_auto_data( struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, - struct iovec *ret_blob, - struct iovec *ret_policy_hash, + 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, @@ -68,8 +72,10 @@ static inline int acquire_tpm2_key( const char *key_file, size_t key_file_size, uint64_t key_file_offset, - const struct iovec *key_data, - const struct iovec *policy_hash, + 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, @@ -92,8 +98,10 @@ static inline int find_tpm2_auto_data( struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, - struct iovec *ret_blob, - struct iovec *ret_policy_hash, + 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, diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 9b5683433f381..b93b744d8b7cf 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -4179,8 +4179,11 @@ int tpm2_calculate_sealing_policy( assert(pcr_values || n_pcr_values == 0); assert(digest); + /* The combination of signed PCR policies and pcrlock is not supported (because we cannot combine + * PolicyAuthorize and PolicyAuthorizeNV in one policy). Callers need to use "sharding" of the + * symmetric FDE unlock key to make policies like that work. */ if (public && pcrlock_policy) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies with both signed PCR and pcrlock are currently not supported."); + 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); @@ -5375,10 +5378,12 @@ int tpm2_calculate_seal( int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, - const TPM2B_DIGEST *policy, + const TPM2B_DIGEST policy[], + size_t n_policy, const char *pin, struct iovec *ret_secret, - struct iovec *ret_blob, + struct iovec **ret_blobs, + size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk) { @@ -5386,7 +5391,8 @@ int tpm2_seal(Tpm2Context *c, int r; assert(ret_secret); - assert(ret_blob); + assert(ret_blobs); + assert(ret_n_blobs); /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We @@ -5424,7 +5430,6 @@ int tpm2_seal(Tpm2Context *c, .objectAttributes = hmac_attributes, .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, .unique.keyedHash.size = SHA256_DIGEST_SIZE, - .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), }; TPMS_SENSITIVE_CREATE hmac_sensitive = { @@ -5443,12 +5448,6 @@ int tpm2_seal(Tpm2Context *c, (void) tpm2_credit_random(c); - log_debug("Generating secret key data."); - - r = crypto_random_bytes(hmac_sensitive.data.buffer, hmac_sensitive.data.size); - if (r < 0) - return log_debug_errno(r, "Failed to generate secret key: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; if (ret_srk) { _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; @@ -5526,24 +5525,51 @@ int tpm2_seal(Tpm2Context *c, if (r < 0) return r; - _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; - _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; - r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private); - if (r < 0) - return r; + log_debug("Generating secret key data."); + /* At least one shard, and if we have multiple policies, then we need one shard for each */ + size_t n_shards = MAX(n_policy, 1U); + + /* Create a large secret which covers all shards we need */ _cleanup_(iovec_done_erase) struct iovec secret = {}; - secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); - if (!secret.iov_base) - return log_oom_debug(); - secret.iov_len = hmac_sensitive.data.size; + r = crypto_random_bytes_allocate_iovec(hmac_sensitive.data.size * n_shards, &secret); + if (r < 0) + return log_debug_errno(r, "Failed to generate secret key: %m"); - log_debug("Marshalling private and public part of HMAC key."); + struct iovec *blobs = new0(struct iovec, n_shards); + size_t n_blobs = 0; - _cleanup_(iovec_done) struct iovec blob = {}; - r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len); - if (r < 0) - return log_debug_errno(r, "Could not create sealed blob: %m"); + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + + for (size_t shard = 0; shard < n_shards; shard++) { + + /* Patch this shard's policy into the template */ + if (shard < n_policy) + hmac_template.authPolicy = policy[shard]; + else + hmac_template.authPolicy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + /* Copy in this shard's secret key */ + memcpy(hmac_sensitive.data.buffer, + (const uint8_t*) secret.iov_base + (hmac_sensitive.data.size * shard), + hmac_sensitive.data.size); + + log_debug("Creating HMAC key on TPM for shard %zu.", shard); + + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private); + if (r < 0) + return r; + + log_debug("Marshalling private and public part of HMAC key for shard %zu.", shard); + + r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blobs[n_blobs].iov_base, &blobs[n_blobs].iov_len); + if (r < 0) + return log_debug_errno(r, "Could not create sealed blob: %m"); + + n_blobs++; + } if (DEBUG_LOGGING) log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); @@ -5571,7 +5597,8 @@ int tpm2_seal(Tpm2Context *c, } *ret_secret = TAKE_STRUCT(secret); - *ret_blob = TAKE_STRUCT(blob); + *ret_blobs = TAKE_PTR(blobs); + *ret_n_blobs = n_blobs; if (ret_primary_alg) *ret_primary_alg = primary_alg; @@ -5590,16 +5617,17 @@ int tpm2_unseal(Tpm2Context *c, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, - const struct iovec *blob, - const struct iovec *known_policy_hash, + 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) { TSS2_RC rc; int r; - assert(iovec_is_set(blob)); - assert(iovec_is_valid(known_policy_hash)); + assert(n_blobs > 0); assert(iovec_is_valid(pubkey)); assert(ret_secret); @@ -5616,12 +5644,11 @@ int tpm2_unseal(Tpm2Context *c, usec_t start = now(CLOCK_MONOTONIC); - TPM2B_PUBLIC public; - TPM2B_PRIVATE private; - TPM2B_ENCRYPTED_SECRET seed = {}; - r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed); - if (r < 0) - return log_debug_errno(r, "Could not extract parts from blob: %m"); + size_t n_shards = pcrlock_policy && iovec_is_set(pubkey) ? 2 : 1; + if (n_blobs != n_shards) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided key blobs (%zu) does not match policy requirements (%zu).", n_blobs, n_shards); + if (n_known_policy_hash > 0 && n_known_policy_hash != n_shards) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided known policy hashes (%zu) does not match policy requirements (%zu or 0).", n_known_policy_hash, n_shards); /* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use, * so we need to handle that legacy situation. */ @@ -5657,37 +5684,6 @@ int tpm2_unseal(Tpm2Context *c, return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "No SRK or primary alg provided."); - if (seed.size > 0) { - /* This is a calculated (or duplicated) sealed object, and must be imported. */ - _cleanup_free_ TPM2B_PRIVATE *imported_private = NULL; - r = tpm2_import(c, - primary_handle, - /* session= */ NULL, - &public, - &private, - &seed, - /* encryption_key= */ NULL, - /* symmetric= */ NULL, - &imported_private); - if (r < 0) - return r; - - private = *imported_private; - } - - log_debug("Loading HMAC key into TPM."); - - /* - * Nothing sensitive on the bus, no need for encryption. Even if an attacker - * gives you back a different key, the session initiation will fail. In the - * SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey - * provides protections. - */ - _cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL; - r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key); - if (r < 0) - return r; - TPM2B_PUBLIC pubkey_tpm2b; _cleanup_(iovec_done) struct iovec fp = {}; if (iovec_is_set(pubkey)) { @@ -5700,100 +5696,145 @@ int tpm2_unseal(Tpm2Context *c, return log_debug_errno(r, "Could not get key fingerprint: %m"); } - /* - * if a pin is set for the seal object, use it to bind the session - * key to that object. This prevents active bus interposers from - * faking a TPM and seeing the unsealed value. An active interposer - * could fake a TPM, satisfying the encrypted session, and just - * forward everything to the *real* TPM. - */ - r = tpm2_set_auth(c, hmac_key, pin); - if (r < 0) - return r; - - _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; + _cleanup_(iovec_done_erase) struct iovec secret = {}; for (unsigned i = RETRY_UNSEAL_MAX;; i--) { - _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; - r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session); - if (r < 0) - return r; + bool retry = false; + iovec_done_erase(&secret); /* clear data from previous unseal attempt */ + + for (size_t shard = 0; shard < n_blobs; shard++) { + TPM2B_PUBLIC public; + TPM2B_PRIVATE private; + TPM2B_ENCRYPTED_SECRET seed = {}; + r = tpm2_unmarshal_blob(blobs[shard].iov_base, blobs[shard].iov_len, &public, &private, &seed); + if (r < 0) + return log_debug_errno(r, "Could not extract parts from blob: %m"); + + if (seed.size > 0) { + /* This is a calculated (or duplicated) sealed object, and must be imported. */ + _cleanup_free_ TPM2B_PRIVATE *imported_private = NULL; + r = tpm2_import(c, + primary_handle, + /* session= */ NULL, + &public, + &private, + &seed, + /* encryption_key= */ NULL, + /* symmetric= */ NULL, + &imported_private); + if (r < 0) + return r; - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; - r = tpm2_make_policy_session( - c, - primary_handle, - encryption_session, - &policy_session); - if (r < 0) - return r; + private = *imported_private; + } - r = tpm2_build_sealing_policy( - c, - policy_session, - hash_pcr_mask, - pcr_bank, - iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL, - fp.iov_base, fp.iov_len, - pubkey_pcr_mask, - signature, - !!pin, - pcrlock_policy, - &policy_digest); - if (r < 0) - return r; + log_debug("Loading HMAC key into TPM for shard %zu.", shard); + + /* Nothing sensitive on the bus, no need for encryption. Even if an attacker gives + * you back a different key, the session initiation will fail. In the SRK model, the + * tpmKey is verified. In the non-srk model, with pin, the bindKey provides + * protections. */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL; + r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key); + if (r < 0) + return r; + + /* If a PIN is set for the seal object, use it to bind the session key to that + * object. This prevents active bus interposers from faking a TPM and seeing the + * unsealed value. An active interposer could fake a TPM, satisfying the encrypted + * session, and just forward everything to the *real* TPM. */ + r = tpm2_set_auth(c, hmac_key, pin); + if (r < 0) + return r; + + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session); + if (r < 0) + return r; + + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + r = tpm2_make_policy_session( + c, + primary_handle, + encryption_session, + &policy_session); + if (r < 0) + return r; - /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not - * wait until the TPM2 tells us to go away. */ - if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer, + /* If both public PCR key and pcrlock policies are requested, then generate the + * public PCR policy for the first shared, and the pcrlock policy for the 2nd */ + r = tpm2_build_sealing_policy( + c, + policy_session, + hash_pcr_mask, + pcr_bank, + shard == 0 && iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL, + fp.iov_base, fp.iov_len, + shard == 0 ? pubkey_pcr_mask : 0, + signature, + !!pin, + (shard == 1 || !iovec_is_set(pubkey)) ? pcrlock_policy : NULL, + &policy_digest); + if (r < 0) + return r; + + /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not + * wait until the TPM2 tells us to go away. */ + if (n_known_policy_hash > 0 && memcmp_nn(policy_digest->buffer, policy_digest->size, - known_policy_hash->iov_base, - known_policy_hash->iov_len) != 0) { + known_policy_hash[shard].iov_base, + known_policy_hash[shard].iov_len) != 0) { #if HAVE_OPENSSL - if (iovec_is_set(pubkey) && - pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA && - pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) { - /* Due to bug #30546, if using RSA pubkey with the default exponent, we may - * need to set the exponent to the TPM special-case value of 0 and retry. */ - log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0."); - pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0; - continue; - } else + if (shard == 0 && + iovec_is_set(pubkey) && + pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA && + pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) { + /* Due to bug #30546, if using RSA pubkey with the default exponent, we may + * need to set the exponent to the TPM special-case value of 0 and retry. */ + log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0."); + pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0; + retry = true; + break; + } #endif return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Current policy digest does not match stored policy digest, cancelling " "TPM2 authentication attempt."); - } + } - log_debug("Unsealing HMAC key."); + log_debug("Unsealing HMAC key for shard %zu.", shard); + + _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; + rc = sym_Esys_Unseal( + c->esys_context, + hmac_key->esys_handle, + policy_session->esys_handle, + encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + &unsealed); + if (rc == TPM2_RC_PCR_CHANGED && i > 0) { + log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); + retry = true; + break; + } + if (rc != TPM2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); - rc = sym_Esys_Unseal( - c->esys_context, - hmac_key->esys_handle, - policy_session->esys_handle, - encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */ - ESYS_TR_NONE, - &unsealed); - if (rc == TSS2_RC_SUCCESS) + if (!iovec_append(&secret, &IOVEC_MAKE(unsealed->buffer, unsealed->size))) + return log_oom_debug(); + + explicit_bzero_safe(unsealed->buffer, unsealed->size); + } + + if (!retry) break; - if (rc != TPM2_RC_PCR_CHANGED || i == 0) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); - log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); } - _cleanup_(iovec_done_erase) struct iovec secret = {}; - secret.iov_base = memdup(unsealed->buffer, unsealed->size); - explicit_bzero_safe(unsealed->buffer, unsealed->size); - if (!secret.iov_base) - return log_oom_debug(); - secret.iov_len = unsealed->size; - if (DEBUG_LOGGING) log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); *ret_secret = TAKE_STRUCT(secret); - return 0; } @@ -7344,6 +7385,46 @@ int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret) { return 0; } +static int tpm2_make_shard_array( + const struct iovec data[], + size_t n_data, + int (*encode_iovec)(sd_json_variant **, const void*, size_t n), /* pass sd_json_variant_new_base64() or sd_json_variant_new_hex() */ + sd_json_variant **ret) { + + int r; + + /* Turns a series of struct iovec into either an array of base64/hex strings, or a single string + * thereof. Used for generated "tpm2-blob" or "tpm2-policy-hash" fields. */ + + assert(data); + assert(n_data > 0); + assert(encode_iovec); + assert(ret); + + /* Only one item? Then create only one encoded string, for compatibility with older versions which + * didn't support the "sharding" scheme */ + if (n_data == 1) + return encode_iovec(ret, data[0].iov_base, data[0].iov_len); + + /* Multiple items? Then generate an array of encoded strings */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + + FOREACH_ARRAY(d, data, n_data) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *item = NULL; + + r = encode_iovec(&item, d->iov_base, d->iov_len); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&j, item); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(j); + return 0; +} + int tpm2_make_luks2_json( int keyslot, uint32_t hash_pcr_mask, @@ -7351,8 +7432,10 @@ int tpm2_make_luks2_json( const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, - const struct iovec *blob, - const struct iovec *policy_hash, + 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, @@ -7364,8 +7447,8 @@ int tpm2_make_luks2_json( int r; assert(iovec_is_valid(pubkey)); - assert(iovec_is_valid(blob)); - assert(iovec_is_valid(policy_hash)); + assert(n_blobs >= 1); + assert(n_policy_hash >= 1); if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return -ENOMEM; @@ -7380,6 +7463,16 @@ int tpm2_make_luks2_json( return r; } + _cleanup_(sd_json_variant_unrefp) sd_json_variant *phj = NULL; + r = tpm2_make_shard_array(policy_hash, n_policy_hash, sd_json_variant_new_hex, &phj); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *bj = NULL; + r = tpm2_make_shard_array(blobs, n_blobs, sd_json_variant_new_base64, &bj); + if (r < 0) + return r; + /* Note: We made the mistake of using "-" in the field names, which isn't particular compatible with * other programming languages. Let's not make things worse though, i.e. future additions to the JSON * object should use "_" rather than "-" in field names. */ @@ -7388,11 +7481,11 @@ int tpm2_make_luks2_json( &v, SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")), SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))), - SD_JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)), + SD_JSON_BUILD_PAIR("tpm2-blob", SD_JSON_BUILD_VARIANT(bj)), SD_JSON_BUILD_PAIR("tpm2-pcrs", SD_JSON_BUILD_VARIANT(hmj)), SD_JSON_BUILD_PAIR_CONDITION(pcr_bank != 0 && tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))), SD_JSON_BUILD_PAIR_CONDITION(primary_alg != 0 && tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", SD_JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), - SD_JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)), + SD_JSON_BUILD_PAIR("tpm2-policy-hash", SD_JSON_BUILD_VARIANT(phj)), SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PIN), "tpm2-pin", SD_JSON_BUILD_BOOLEAN(true)), 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)), @@ -7409,6 +7502,63 @@ int tpm2_make_luks2_json( return keyslot; } +static int tpm2_parse_shard_array( + sd_json_variant *v, + const char *name, + int (*decode_iovec)(sd_json_variant*, struct iovec *ret), /* pass json_variant_unbase64_iovec() or json_variant_unhex_iovec() */ + struct iovec **ret_data, + size_t *ret_n_data) { + + int r; + + assert(v); + assert(name); + assert(decode_iovec); + assert(ret_data); + assert(ret_n_data); + + /* Parses the "tpm2-blob" or "tpm2-policy-hash" fields of our LUKS JSON serialization. This can + * either be an array of base64/hex strings, or a single such string. The former to allow for sharded + * keys. The latter mostly for compatibility with older versions where we didn't support sharded + * keys. */ + + struct iovec *data = NULL; + size_t n_data = 0; + CLEANUP_ARRAY(data, n_data, iovec_array_free); + + if (sd_json_variant_is_array(v)) { + if (sd_json_variant_elements(v) == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data contains empty '%s' array.", name); + + data = new0(struct iovec, sd_json_variant_elements(v)); + if (!data) + return log_oom_debug(); + + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, v) { + r = decode_iovec(i, data + n_data); + if (r < 0) + return log_debug_errno(r, "Invalid data in '%s' field.", name); + + n_data++; + } + } else { + data = new0(struct iovec, 1); + if (!data) + return log_oom_debug(); + + r = decode_iovec(v, data + 0); + if (r < 0) + return log_debug_errno(r, "Invalid data in '%s' field.", name); + + n_data = 1; + } + + *ret_data = TAKE_PTR(data); + *ret_n_data = n_data; + return 0; +} + int tpm2_parse_luks2_json( sd_json_variant *v, int *ret_keyslot, @@ -7417,14 +7567,16 @@ int tpm2_parse_luks2_json( struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, - struct iovec *ret_blob, - struct iovec *ret_policy_hash, + 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) { - _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0; uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */ uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ @@ -7489,17 +7641,25 @@ int tpm2_parse_luks2_json( if (!w) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field."); - r = json_variant_unbase64_iovec(w, &blob); + struct iovec *blobs = NULL; + size_t n_blobs = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + + r = tpm2_parse_shard_array(w, "tpm2-blob", json_variant_unbase64_iovec, &blobs, &n_blobs); if (r < 0) - return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field."); + return r; w = sd_json_variant_by_key(v, "tpm2-policy-hash"); if (!w) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); - r = json_variant_unhex_iovec(w, &policy_hash); + struct iovec *policy_hash = NULL; + size_t n_policy_hash = 0; + CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + + r = tpm2_parse_shard_array(w, "tpm2-policy-hash", json_variant_unhex_iovec, &policy_hash, &n_policy_hash); if (r < 0) - return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field."); + return r; w = sd_json_variant_by_key(v, "tpm2-pin"); if (w) { @@ -7565,10 +7725,14 @@ int tpm2_parse_luks2_json( *ret_pubkey_pcr_mask = pubkey_pcr_mask; if (ret_primary_alg) *ret_primary_alg = primary_alg; - if (ret_blob) - *ret_blob = TAKE_STRUCT(blob); + if (ret_blobs) + *ret_blobs = TAKE_PTR(blobs); + if (ret_n_blobs) + *ret_n_blobs = n_blobs; if (ret_policy_hash) - *ret_policy_hash = TAKE_STRUCT(policy_hash); + *ret_policy_hash = TAKE_PTR(policy_hash); + if (ret_n_policy_hash) + *ret_n_policy_hash = n_policy_hash; if (ret_salt) *ret_salt = TAKE_STRUCT(salt); if (ret_srk) diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 3a8565bac5d75..6ab6c00af3ae5 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -291,8 +291,8 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_get_or_create_srk(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, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, 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 *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret); +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 policy_hash[], size_t n_policy_hash, const struct iovec *srk, struct iovec *ret_secret); #if HAVE_OPENSSL int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); @@ -391,8 +391,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 *blob, const struct iovec *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_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *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, 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 *pcrlock_nv, TPM2Flags *ret_flags); /* Default to PCR 7 only */ #define TPM2_PCR_INDEX_DEFAULT UINT32_C(7) diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index 2be104d853468..de2889b8d28b8 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1163,7 +1163,9 @@ static void calculate_seal_and_unseal( /* pcrlock_policy= */ NULL, /* primary_alg= */ 0, &blob, + /* n_blobs= */ 1, /* known_policy_hash= */ NULL, + /* n_known_policy_hash= */ 0, &serialized_parent, &unsealed_secret) >= 0); @@ -1222,14 +1224,20 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { log_debug("Check seal/unseal for handle 0x%" PRIx32, handle); - _cleanup_(iovec_done) struct iovec secret = {}, blob = {}, srk = {}, unsealed_secret = {}; + _cleanup_(iovec_done) struct iovec secret = {}, srk = {}, unsealed_secret = {}; + struct iovec *blobs = NULL; + size_t n_blobs = 0; + CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); + assert_se(tpm2_seal( c, handle, &policy, + 1, /* pin= */ NULL, &secret, - &blob, + &blobs, + &n_blobs, /* ret_primary_alg= */ NULL, &srk) >= 0); @@ -1243,8 +1251,10 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { /* pin= */ NULL, /* pcrlock_policy= */ NULL, /* primary_alg= */ 0, - &blob, + blobs, + n_blobs, /* policy_hash= */ NULL, + /* n_policy_hash= */ 0, &srk, &unsealed_secret) >= 0); diff --git a/test/units/TEST-70-TPM2.pcrlock.sh b/test/units/TEST-70-TPM2.pcrlock.sh index 10fa7a92c27d8..72b95897dab00 100755 --- a/test/units/TEST-70-TPM2.pcrlock.sh +++ b/test/units/TEST-70-TPM2.pcrlock.sh @@ -10,9 +10,10 @@ export SYSTEMD_LOG_LEVEL=debug export PAGER= SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend" SD_PCRLOCK="/usr/lib/systemd/systemd-pcrlock" +SD_MEASURE="/usr/lib/systemd/systemd-measure" -if [[ ! -x "${SD_PCREXTEND:?}" ]] || [[ ! -x "${SD_PCRLOCK:?}" ]] ; then - echo "$SD_PCREXTEND or $SD_PCRLOCK not found, skipping pcrlock tests" +if [[ ! -x "${SD_PCREXTEND:?}" ]] || [[ ! -x "${SD_PCRLOCK:?}" ]] || [[ ! -x "${SD_MEASURE:?}" ]] ; then + echo "$SD_PCREXTEND or $SD_PCRLOCK or $SD_MEASURE not found, skipping pcrlock tests" exit 0 fi @@ -127,6 +128,17 @@ echo -n test70-take-two | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/92 systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless systemd-cryptsetup detach pcrlock +# Now combined pcrlock and signed PCR +# Generate key pair +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "$img".private.pem +openssl rsa -pubout -in "$img".private.pem -out "$img".public.pem +systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --tpm2-public-key="$img".public.pem --wipe-slot=tpm2 "$img" +"$SD_MEASURE" sign --current --bank=sha256 --private-key="$img".private.pem --public-key="$img".public.pem --phase=: | tee "$img".pcrsign +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach pcrlock "$img" - "tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,tpm2-signature=$img.pcrsign,headless" +systemd-cryptsetup detach pcrlock +systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --wipe-slot=tpm2 "$img" +rm "$img".public.pem "$img".private.pem "$img".pcrsign + # Now use the root fs support, i.e. make the tool write a copy of the pcrlock # file as service credential to some temporary dir and remove the local copy, so that # it has to use the credential version.