diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 6ca850e0ef..cea2072c47 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -391,13 +391,22 @@ PCR Configures the TPM2 PCRs (Platform Configuration Registers) to bind to when - enrollment is requested via . Takes a list of PCR names or numeric - indices in the range 0…23. Multiple PCR indexes are separated by +. If not - specified, the default is to use PCR 7 only. If an empty string is specified, binds the enrollment to - no PCRs at all. See the table above for a list of available PCRs. + enrollment is requested via . Takes a list of PCR entries, where each + entry starts with a name or numeric index in the range 0…23, optionally followed by + : and a hash algorithm name (specifying the PCR bank), optionally followed by + = and a hash digest value. Multiple PCR entries are separated by + +. If not specified, the default is to use PCR 7 only. If an empty string is + specified, binds the enrollment to no PCRs at all. See the table above for a list of available + PCRs. Example: specifies that PCR registers 4, 1, and 5 should be used. + Example: specifies that PCR register 7 from the SHA256 + bank should be used. + Example: + specifies that PCR register 4 from the SHA1 bank should be used, and a hash digest value of + 0x3A3F780F11A4B49969FCAA80CD6E3957C33B2275 will be used instead of reading the current PCR + value. diff --git a/src/basic/macro.h b/src/basic/macro.h index 13620a60bc..d9df3b7365 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -335,6 +335,15 @@ static inline int __coverity_check_and_return__(int condition) { } \ } +/* When func() doesn't return the appropriate type, and is also a macro, set variable to empty afterwards. */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, func, empty) \ + static inline void func##p(type *p) { \ + if (*p != (empty)) { \ + func(*p); \ + *p = (empty); \ + } \ + } + #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ scope type *name##_ref(type *p) { \ if (!p) \ diff --git a/src/boot/measure.c b/src/boot/measure.c index e388fb666e..518c41f65c 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -831,23 +831,13 @@ static int verb_sign(int argc, char *argv[], void *userdata) { if (tpmalg < 0) return log_error_errno(tpmalg, "Unsupported PCR bank"); - TPML_PCR_SELECTION pcr_selection; - tpm2_tpml_pcr_selection_from_mask(1 << TPM_PCR_INDEX_KERNEL_IMAGE, - tpmalg, - &pcr_selection); + Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM_PCR_INDEX_KERNEL_IMAGE, + tpmalg, + TPM2B_DIGEST_MAKE(p->value, p->value_size)); - TPM2B_DIGEST pcr_values = { - .size = p->value_size, - }; - assert(sizeof(pcr_values.buffer) >= p->value_size); - memcpy_safe(pcr_values.buffer, p->value, p->value_size); + TPM2B_DIGEST pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - TPM2B_DIGEST pcr_policy_digest; - r = tpm2_digest_init(TPM2_ALG_SHA256, &pcr_policy_digest); - if (r < 0) - return r; - - r = tpm2_calculate_policy_pcr(&pcr_selection, &pcr_values, 1, &pcr_policy_digest); + r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest); if (r < 0) return r; diff --git a/src/creds/creds.c b/src/creds/creds.c index 679b0750de..36531d7d18 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -860,7 +860,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */ - r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); if (r < 0) return r; @@ -874,7 +874,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */ - r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 4dc3c1794d..86aed31838 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -133,7 +133,8 @@ int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, - uint32_t hash_pcr_mask, + Tpm2PCRValue *hash_pcr_values, + size_t n_hash_pcr_values, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, @@ -143,9 +144,8 @@ int enroll_tpm2(struct crypt_device *cd, _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_free_ void *srk_buf = NULL; - size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; - _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL; - uint16_t pcr_bank, primary_alg; + size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; + _cleanup_free_ void *blob = NULL, *pubkey = NULL; const char *node; _cleanup_(erase_and_freep) char *pin_str = NULL; ssize_t base64_encoded_size; @@ -163,7 +163,7 @@ int enroll_tpm2(struct crypt_device *cd, assert(cd); assert(volume_key); assert(volume_key_size > 0); - assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); + assert(TPM2_PCR_VALUES_VALID(hash_pcr_values, n_hash_pcr_values)); assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); assert_se(node = crypt_get_device_name(cd)); @@ -206,23 +206,69 @@ int enroll_tpm2(struct crypt_device *cd, return log_debug_errno(r, "Failed to read TPM PCR signature: %m"); } - r = tpm2_seal(device, - hash_pcr_mask, - pubkey, pubkey_size, - pubkey_pcr_mask, + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(device, &tpm2_context); + if (r < 0) + return r; + + bool pcr_value_specified = false; + for (size_t i = 0; i < n_hash_pcr_values; i++) + if (hash_pcr_values[i].value.size > 0) { + pcr_value_specified = true; + break; + } + + r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); + if (r < 0) + return r; + + uint16_t hash_pcr_bank = 0; + uint32_t hash_pcr_mask = 0; + if (n_hash_pcr_values > 0) { + size_t hash_count; + r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count); + if (r < 0) + return log_error_errno(r, "Could not get hash count: %m"); + + if (hash_count > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); + + hash_pcr_bank = hash_pcr_values[0].hash; + r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); + if (r < 0) + return log_error_errno(r, "Could not get hash mask: %m"); + } + + TPM2B_PUBLIC public; + if (pubkey) { + r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (r < 0) + return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); + } + + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_sealing_policy( + hash_pcr_values, + n_hash_pcr_values, + pubkey ? &public : NULL, + use_pin, + &policy); + if (r < 0) + return r; + + r = tpm2_seal(tpm2_context, + &policy, pin_str, &secret, &secret_size, &blob, &blob_size, - &hash, &hash_size, - &pcr_bank, - &primary_alg, + /* ret_primary_alg= */ NULL, &srk_buf, &srk_buf_size); if (r < 0) return r; /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */ - r = search_policy_hash(cd, hash, hash_size); + r = search_policy_hash(cd, policy.buffer, policy.size); if (r == -ENOENT) log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); else if (r < 0) @@ -233,21 +279,21 @@ int enroll_tpm2(struct crypt_device *cd, } /* Quick verification that everything is in order, we are not in a hurry after all. */ - if (!pubkey || signature_json) { + if ((!pubkey || signature_json) && !pcr_value_specified) { _cleanup_(erase_and_freep) void *secret2 = NULL; size_t secret2_size; log_debug("Unsealing for verification..."); r = tpm2_unseal(device, hash_pcr_mask, - pcr_bank, + hash_pcr_bank, pubkey, pubkey_size, pubkey_pcr_mask, signature_json, pin_str, - primary_alg, + /* primary_alg= */ 0, blob, blob_size, - hash, hash_size, + policy.buffer, policy.size, srk_buf, srk_buf_size, &secret2, &secret2_size); if (r < 0) @@ -279,12 +325,12 @@ int enroll_tpm2(struct crypt_device *cd, r = tpm2_make_luks2_json( keyslot, hash_pcr_mask, - pcr_bank, + hash_pcr_bank, pubkey, pubkey_size, pubkey_pcr_mask, - primary_alg, + /* primary_alg= */ 0, blob, blob_size, - hash, hash_size, + policy.buffer, policy.size, use_pin ? binary_salt : NULL, use_pin ? sizeof(binary_salt) : 0, srk_buf, srk_buf_size, diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index b6e0c2808e..d43a9a8ffe 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -5,11 +5,12 @@ #include "cryptsetup-util.h" #include "log.h" +#include "tpm2-util.h" #if HAVE_TPM2 -int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); +int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); #else -static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { +static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 key enrollment not supported."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index fe25619d85..1ac2c6951e 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -28,7 +28,6 @@ #include "strv.h" #include "terminal-util.h" #include "tpm-pcr.h" -#include "tpm2-util.h" static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; static char *arg_unlock_keyfile = NULL; @@ -37,10 +36,13 @@ static char *arg_unlock_fido2_device = NULL; static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; static char *arg_tpm2_device = NULL; -static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; +static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; +static size_t arg_tpm2_n_hash_pcr_values = 0; +static bool arg_tpm2_hash_pcr_values_use_default = true; static bool arg_tpm2_pin = false; static char *arg_tpm2_public_key = NULL; -static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX; +static uint32_t arg_tpm2_public_key_pcr_mask = 0; +static bool arg_tpm2_public_key_pcr_mask_use_default = true; static char *arg_tpm2_signature = NULL; static char *arg_node = NULL; static int *arg_wipe_slots = NULL; @@ -61,6 +63,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); STATIC_DESTRUCTOR_REGISTER(arg_node, freep); @@ -356,7 +359,8 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask); + arg_tpm2_hash_pcr_values_use_default = false; + r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; @@ -377,7 +381,8 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM2_PUBLIC_KEY_PCRS: - r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask); + arg_tpm2_public_key_pcr_mask_use_default = false; + r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; @@ -476,10 +481,15 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - if (arg_tpm2_pcr_mask == UINT32_MAX) - arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; - if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) - arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE; + if (arg_tpm2_public_key_pcr_mask_use_default && arg_tpm2_public_key) + arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM_PCR_INDEX_KERNEL_IMAGE); + + if (arg_tpm2_hash_pcr_values_use_default && !GREEDY_REALLOC_APPEND( + arg_tpm2_hash_pcr_values, + arg_tpm2_n_hash_pcr_values, + &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), + 1)) + return log_oom(); return 1; } @@ -655,7 +665,7 @@ static int run(int argc, char *argv[]) { break; case ENROLL_TPM2: - slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); + slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); break; case _ENROLL_TYPE_INVALID: diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index aab3a4b4c0..94d568c17f 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -278,7 +278,7 @@ _public_ int cryptsetup_token_validate( } u = json_variant_unsigned(e); - if (!TPM2_PCR_VALID(u)) { + if (!TPM2_PCR_INDEX_VALID(u)) { crypt_log_debug(cd, "TPM2 PCR number out of range."); return 1; } diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index f62401b9bc..b5aa396cde 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -399,7 +399,7 @@ static int parse_one_option(const char *option) { } else if ((val = startswith(option, "tpm2-pcrs="))) { - r = tpm2_parse_pcr_argument(val, &arg_tpm2_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(val, &arg_tpm2_pcr_mask); if (r < 0) return r; @@ -435,7 +435,7 @@ static int parse_one_option(const char *option) { } pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX; - } else if (!TPM2_PCR_VALID(pcr)) { + } else if (!TPM2_PCR_INDEX_VALID(pcr)) { log_warning("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); return 0; } diff --git a/src/partition/repart.c b/src/partition/repart.c index 4fe2c63a46..1a316012e7 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -140,9 +140,12 @@ static size_t arg_key_size = 0; static EVP_PKEY *arg_private_key = NULL; static X509 *arg_certificate = NULL; static char *arg_tpm2_device = NULL; -static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; +static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; +static size_t arg_tpm2_n_hash_pcr_values = 0; +static bool arg_tpm2_hash_pcr_values_use_default = true; static char *arg_tpm2_public_key = NULL; -static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX; +static uint32_t arg_tpm2_public_key_pcr_mask = 0; +static bool arg_tpm2_public_key_pcr_mask_use_default = true; static bool arg_split = false; static GptPartitionType *arg_filter_partitions = NULL; static size_t arg_n_filter_partitions = 0; @@ -162,6 +165,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); @@ -3639,10 +3643,9 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_free_ void *pubkey = NULL; - _cleanup_free_ void *blob = NULL, *hash = NULL, *srk_buf = NULL; - size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; + _cleanup_free_ void *blob = NULL, *srk_buf = NULL; + size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; ssize_t base64_encoded_size; - uint16_t pcr_bank, primary_alg; int keyslot; if (arg_tpm2_public_key_pcr_mask != 0) { @@ -3656,18 +3659,51 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta } } - r = tpm2_seal(arg_tpm2_device, - arg_tpm2_pcr_mask, - pubkey, pubkey_size, - arg_tpm2_public_key_pcr_mask, + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(arg_tpm2_device, &tpm2_context); + if (r < 0) + return r; + + TPM2B_PUBLIC public; + if (pubkey) { + r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (r < 0) + return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); + } + + r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); + if (r < 0) + return r; + + uint16_t hash_pcr_bank = 0; + uint32_t hash_pcr_mask = 0; + if (arg_tpm2_n_hash_pcr_values > 0) { + size_t hash_count; + r = tpm2_pcr_values_hash_count(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &hash_count); + if (r < 0) + return log_error_errno(r, "Could not get hash count: %m"); + + if (hash_count > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); + + hash_pcr_bank = arg_tpm2_hash_pcr_values[0].hash; + r = tpm2_pcr_values_to_mask(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); + if (r < 0) + return log_error_errno(r, "Could not get hash mask: %m"); + } + + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_sealing_policy(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &public, /* use_pin= */ false, &policy); + if (r < 0) + return r; + + r = tpm2_seal(tpm2_context, + &policy, /* pin= */ NULL, &secret, &secret_size, &blob, &blob_size, - &hash, &hash_size, - &pcr_bank, - &primary_alg, - &srk_buf, - &srk_buf_size); + /* ret_primary_alg= */ NULL, + &srk_buf, &srk_buf_size); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); @@ -3691,13 +3727,13 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta r = tpm2_make_luks2_json( keyslot, - arg_tpm2_pcr_mask, - pcr_bank, + hash_pcr_mask, + hash_pcr_bank, pubkey, pubkey_size, arg_tpm2_public_key_pcr_mask, - primary_alg, + /* primary_alg= */ 0, blob, blob_size, - hash, hash_size, + policy.buffer, policy.size, NULL, 0, /* no salt because tpm2_seal has no pin */ srk_buf, srk_buf_size, 0, @@ -6447,7 +6483,8 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask); + arg_tpm2_hash_pcr_values_use_default = false; + r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; @@ -6461,7 +6498,8 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM2_PUBLIC_KEY_PCRS: - r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask); + arg_tpm2_public_key_pcr_mask_use_default = false; + r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; @@ -6597,10 +6635,15 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A path to a loopback file must be specified when --split is used."); - if (arg_tpm2_pcr_mask == UINT32_MAX) - arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; - if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) - arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE; + if (arg_tpm2_public_key_pcr_mask_use_default && arg_tpm2_public_key) + arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM_PCR_INDEX_KERNEL_IMAGE); + + if (arg_tpm2_hash_pcr_values_use_default && !GREEDY_REALLOC_APPEND( + arg_tpm2_hash_pcr_values, + arg_tpm2_n_hash_pcr_values, + &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), + 1)) + return log_oom(); if (arg_pretty < 0 && isatty(STDOUT_FILENO)) arg_pretty = true; diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 01f79331b9..2580c2333c 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -14,7 +14,7 @@ #include "sort-util.h" #include "string-table.h" -#if PREFER_OPENSSL +#if PREFER_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 16df01b9ca..8a5240e5f1 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -825,18 +825,49 @@ int encrypt_credential_and_warn( if (!pubkey) tpm2_pubkey_pcr_mask = 0; - r = tpm2_seal(tpm2_device, - tpm2_hash_pcr_mask, - pubkey, pubkey_size, - tpm2_pubkey_pcr_mask, + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(tpm2_device, &tpm2_context); + if (r < 0) + return r; + + r = tpm2_get_best_pcr_bank(tpm2_context, tpm2_hash_pcr_mask | tpm2_pubkey_pcr_mask, &tpm2_pcr_bank); + if (r < 0) + return r; + + TPML_PCR_SELECTION tpm2_hash_pcr_selection; + tpm2_tpml_pcr_selection_from_mask(tpm2_hash_pcr_mask, tpm2_pcr_bank, &tpm2_hash_pcr_selection); + + _cleanup_free_ Tpm2PCRValue *tpm2_hash_pcr_values = NULL; + size_t tpm2_n_hash_pcr_values; + r = tpm2_pcr_read(tpm2_context, &tpm2_hash_pcr_selection, &tpm2_hash_pcr_values, &tpm2_n_hash_pcr_values); + if (r < 0) + return r; + + TPM2B_PUBLIC public; + if (pubkey) { + r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (r < 0) + return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); + } + + TPM2B_DIGEST tpm2_policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_sealing_policy( + tpm2_hash_pcr_values, + tpm2_n_hash_pcr_values, + pubkey ? &public : NULL, + /* use_pin= */ false, + &tpm2_policy); + if (r < 0) + return r; + + r = tpm2_seal(tpm2_context, + &tpm2_policy, /* pin= */ NULL, &tpm2_key, &tpm2_key_size, &tpm2_blob, &tpm2_blob_size, - &tpm2_policy_hash, &tpm2_policy_hash_size, - &tpm2_pcr_bank, &tpm2_primary_alg, /* ret_srk_buf= */ NULL, - /* ret_srk_buf_size= */ 0); + /* ret_srk_buf_size= */ NULL); if (r < 0) { if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); @@ -846,6 +877,12 @@ int encrypt_credential_and_warn( log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m"); } + tpm2_policy_hash_size = tpm2_policy.size; + tpm2_policy_hash = malloc(tpm2_policy_hash_size); + if (!tpm2_policy_hash) + return log_oom(); + memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size); + assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX); assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX); } diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index c7fcbd9ea4..c3f8f91eb3 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1,10 +1,29 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "fd-util.h" #include "openssl-util.h" #include "alloc-util.h" #include "hexdecoct.h" #if HAVE_OPENSSL +int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { + assert(pem); + assert(ret); + + _cleanup_fclose_ FILE *f = NULL; + f = fmemopen((void*) pem, pem_size, "r"); + if (!f) + return log_oom_debug(); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); + if (!pkey) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM."); + + *ret = TAKE_PTR(pkey); + + return 0; +} + int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, @@ -109,6 +128,342 @@ int rsa_pkey_to_suitable_key_size( return 0; } +/* Generate RSA public key from provided "n" and "e" values. Note that if "e" is a number (e.g. uint32_t), it + * must be provided here big-endian, e.g. wrap it with htobe32(). */ +int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) { + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + + assert(n); + assert(e); + assert(ret); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); + if (!bn_n) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create BIGNUM for RSA n."); + + _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); + if (!bn_e) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create BIGNUM for RSA e."); + +#if OPENSSL_VERSION_MAJOR >= 3 + if (EVP_PKEY_fromdata_init(ctx) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + if (!bld) + return log_oom_debug(); + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA OSSL_PKEY_PARAM_RSA_N."); + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA OSSL_PKEY_PARAM_RSA_E."); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to build RSA OSSL_PARAM."); + + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create RSA EVP_PKEY."); +#else + _cleanup_(RSA_freep) RSA *rsa_key = RSA_new(); + if (!rsa_key) + return log_oom_debug(); + + if (!RSA_set0_key(rsa_key, bn_n, bn_e, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA n/e."); + /* rsa_key owns these now, don't free */ + TAKE_PTR(bn_n); + TAKE_PTR(bn_e); + + pkey = EVP_PKEY_new(); + if (!pkey) + return log_oom_debug(); + + if (!EVP_PKEY_assign_RSA(pkey, rsa_key)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign RSA key."); + /* pkey owns this now, don't free */ + TAKE_PTR(rsa_key); +#endif + + *ret = TAKE_PTR(pkey); + + return 0; +} + +/* Get the "n" and "e" values from the pkey. The values are returned in "bin" format, i.e. BN_bn2bin(). */ +int rsa_pkey_to_n_e( + const EVP_PKEY *pkey, + void **ret_n, + size_t *ret_n_size, + void **ret_e, + size_t *ret_e_size) { + + assert(pkey); + assert(ret_n); + assert(ret_n_size); + assert(ret_e); + assert(ret_e_size); + +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(BN_freep) BIGNUM *bn_n = NULL; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA n."); + + _cleanup_(BN_freep) BIGNUM *bn_e = NULL; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA e."); +#else + const RSA *rsa = EVP_PKEY_get0_RSA((EVP_PKEY*) pkey); + if (!rsa) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get RSA key from public key."); + + const BIGNUM *bn_n = RSA_get0_n(rsa); + if (!bn_n) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA n."); + + const BIGNUM *bn_e = RSA_get0_e(rsa); + if (!bn_e) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA e."); +#endif + + size_t n_size = BN_num_bytes(bn_n), e_size = BN_num_bytes(bn_e); + _cleanup_free_ void *n = malloc(n_size), *e = malloc(e_size); + if (!n || !e) + return log_oom_debug(); + + assert(BN_bn2bin(bn_n, n) == (int) n_size); + assert(BN_bn2bin(bn_e, e) == (int) e_size); + + *ret_n = TAKE_PTR(n); + *ret_n_size = n_size; + *ret_e = TAKE_PTR(e); + *ret_e_size = e_size; + + return 0; +} + +/* Generate a new RSA key with the specified number of bits. */ +int rsa_pkey_new(size_t bits, EVP_PKEY **ret) { + assert(ret); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) + return log_oom_debug(); + + if (EVP_PKEY_keygen_init(ctx) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int) bits) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA bits to %zu.", bits); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate ECC key."); + + *ret = TAKE_PTR(pkey); + + return 0; +} + +/* Generate ECC public key from provided curve ID and x/y points. */ +int ecc_pkey_from_curve_x_y( + int curve_id, + const void *x, + size_t x_size, + const void *y, + size_t y_size, + EVP_PKEY **ret) { + + assert(x); + assert(y); + assert(ret); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (!ctx) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL), *bn_y = BN_bin2bn(y, y_size, NULL); + if (!bn_x || !bn_y) + return log_oom_debug(); + + _cleanup_(EC_GROUP_freep) EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_id); + if (!group) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "ECC curve id %d not supported.", curve_id); + + _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); + if (!point) + return log_oom_debug(); + + if (!EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC coordinates."); + +#if OPENSSL_VERSION_MAJOR >= 3 + if (EVP_PKEY_fromdata_init(ctx) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize EVP_PKEY_CTX."); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + if (!bld) + return log_oom_debug(); + + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) OSSL_EC_curve_nid2name(curve_id), 0)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME."); + + _cleanup_(OPENSSL_freep) void *pbuf = NULL; + size_t pbuf_len = 0; + pbuf_len = EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); + if (pbuf_len == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert ECC point to buffer."); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY."); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to build ECC OSSL_PARAM."); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Failed to create ECC EVP_PKEY."); +#else + _cleanup_(EC_KEY_freep) EC_KEY *eckey = EC_KEY_new(); + if (!eckey) + return log_oom_debug(); + + if (!EC_KEY_set_group(eckey, group)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC group."); + + if (!EC_KEY_set_public_key(eckey, point)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC point."); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = EVP_PKEY_new(); + if (!pkey) + return log_oom_debug(); + + if (!EVP_PKEY_assign_EC_KEY(pkey, eckey)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign ECC key."); + /* pkey owns this now, don't free */ + TAKE_PTR(eckey); +#endif + + *ret = TAKE_PTR(pkey); + + return 0; +} + +int ecc_pkey_to_curve_x_y( + const EVP_PKEY *pkey, + int *ret_curve_id, + void **ret_x, + size_t *ret_x_size, + void **ret_y, + size_t *ret_y_size) { + + _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL; + int curve_id; + + assert(pkey); + +#if OPENSSL_VERSION_MAJOR >= 3 + size_t name_size; + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name size."); + + _cleanup_free_ char *name = malloc(name_size + 1); + if (!name) + return log_oom_debug(); + + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name."); + + curve_id = OBJ_sn2nid(name); + if (curve_id == NID_undef) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC curve id."); + + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC point x."); + + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC point y."); +#else + const EC_KEY *eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY*) pkey); + if (!eckey) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_KEY."); + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if (!group) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_GROUP."); + + curve_id = EC_GROUP_get_curve_name(group); + if (curve_id == NID_undef) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC curve id."); + + const EC_POINT *point = EC_KEY_get0_public_key(eckey); + if (!point) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_POINT."); + + bn_x = BN_new(); + bn_y = BN_new(); + if (!bn_x || !bn_y) + return log_oom_debug(); + + if (!EC_POINT_get_affine_coordinates(group, point, bn_x, bn_y, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC x/y."); +#endif + + size_t x_size = BN_num_bytes(bn_x), y_size = BN_num_bytes(bn_y); + _cleanup_free_ void *x = malloc(x_size), *y = malloc(y_size); + if (!x || !y) + return log_oom_debug(); + + assert(BN_bn2bin(bn_x, x) == (int) x_size); + assert(BN_bn2bin(bn_y, y) == (int) y_size); + + if (ret_curve_id) + *ret_curve_id = curve_id; + if (ret_x) + *ret_x = TAKE_PTR(x); + if (ret_x_size) + *ret_x_size = x_size; + if (ret_y) + *ret_y = TAKE_PTR(y); + if (ret_y_size) + *ret_y_size = y_size; + + return 0; +} + +/* Generate a new ECC key for the specified ECC curve id. */ +int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { + assert(ret); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (!ctx) + return log_oom_debug(); + + if (EVP_PKEY_keygen_init(ctx) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); + + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC curve %d.", curve_id); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate ECC key."); + + *ret = TAKE_PTR(pkey); + + return 0; +} + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; _cleanup_free_ void *d = NULL, *h = NULL; diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 4fa0a95966..90158f589b 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -9,6 +9,7 @@ #if HAVE_OPENSSL # include # include +# include # include # include # include @@ -21,8 +22,10 @@ # endif # if OPENSSL_VERSION_MAJOR >= 3 # include +# include # endif +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL); @@ -35,6 +38,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); +#if OPENSSL_VERSION_MAJOR >= 3 +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); +#else +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); +#endif static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { if (!sk || !*sk) @@ -43,12 +53,26 @@ static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { sk_X509_pop_free(*sk, X509_free); } +int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); + int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *ret_hash, size_t *ret_hash_len); int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); +int rsa_pkey_new(size_t bits, EVP_PKEY **ret); + +int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret); + +int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); + +int ecc_pkey_from_curve_x_y(int curve_id, const void *x, size_t x_size, const void *y, size_t y_size, EVP_PKEY **ret); + +int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, size_t *ret_x_size, void **ret_y, size_t *ret_y_size); + +int ecc_pkey_new(int curve_id, EVP_PKEY **ret); + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); #else diff --git a/src/shared/tests.h b/src/shared/tests.h index 6b87026661..27467fabb6 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -39,6 +39,12 @@ bool have_namespaces(void); #define CAN_MEMLOCK_SIZE (512 * 1024U) bool can_memlock(void); +/* Define void* buffer and size_t length variables from a hex string. */ +#define DEFINE_HEX_PTR(name, hex) \ + _cleanup_free_ void *name = NULL; \ + size_t name##_len = 0; \ + assert_se(unhexmem(hex, strlen(hex), &name, &name##_len) >= 0); + #define TEST_REQ_RUNNING_SYSTEMD(x) \ if (sd_booted() > 0) { \ x; \ diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index bc4f7f3c90..21ee4dc8e0 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -19,10 +19,10 @@ #include "logarithm.h" #include "memory-util.h" #include "nulstr-util.h" -#include "openssl-util.h" #include "parse-util.h" #include "random-util.h" #include "sha256.h" +#include "sort-util.h" #include "stat-util.h" #include "string-table.h" #include "time-util.h" @@ -1204,15 +1204,14 @@ static int tpm2_get_or_create_srk( /* Utility functions for TPMS_PCR_SELECTION. */ /* Convert a TPMS_PCR_SELECTION object to a mask. */ -void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret) { +uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s) { assert(s); assert(s->sizeofSelect <= sizeof(s->pcrSelect)); - assert(ret); uint32_t mask = 0; for (unsigned i = 0; i < s->sizeofSelect; i++) SET_FLAG(mask, (uint32_t)s->pcrSelect[i] << (i * 8), true); - *ret = mask; + return mask; } /* Convert a mask and hash alg to a TPMS_PCR_SELECTION object. */ @@ -1233,16 +1232,36 @@ void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TP }; } +/* Test if all bits in the mask are set in the TPMS_PCR_SELECTION. */ +bool tpm2_tpms_pcr_selection_has_mask(const TPMS_PCR_SELECTION *s, uint32_t mask) { + assert(s); + + return FLAGS_SET(tpm2_tpms_pcr_selection_to_mask(s), mask); +} + +static void tpm2_tpms_pcr_selection_update_mask(TPMS_PCR_SELECTION *s, uint32_t mask, bool b) { + assert(s); + + tpm2_tpms_pcr_selection_from_mask(UPDATE_FLAG(tpm2_tpms_pcr_selection_to_mask(s), mask, b), s->hash, s); +} + +/* Add all PCR selections in the mask. */ +void tpm2_tpms_pcr_selection_add_mask(TPMS_PCR_SELECTION *s, uint32_t mask) { + tpm2_tpms_pcr_selection_update_mask(s, mask, 1); +} + +/* Remove all PCR selections in the mask. */ +void tpm2_tpms_pcr_selection_sub_mask(TPMS_PCR_SELECTION *s, uint32_t mask) { + tpm2_tpms_pcr_selection_update_mask(s, mask, 0); +} + /* Add all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { assert(a); assert(b); assert(a->hash == b->hash); - uint32_t maska, maskb; - tpm2_tpms_pcr_selection_to_mask(a, &maska); - tpm2_tpms_pcr_selection_to_mask(b, &maskb); - tpm2_tpms_pcr_selection_from_mask(maska | maskb, a->hash, a); + tpm2_tpms_pcr_selection_add_mask(a, tpm2_tpms_pcr_selection_to_mask(b)); } /* Remove all PCR selections in 'b' from 'a'. Both must have the same hash alg. */ @@ -1251,10 +1270,7 @@ void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION assert(b); assert(a->hash == b->hash); - uint32_t maska, maskb; - tpm2_tpms_pcr_selection_to_mask(a, &maska); - tpm2_tpms_pcr_selection_to_mask(b, &maskb); - tpm2_tpms_pcr_selection_from_mask(maska & ~maskb, a->hash, a); + tpm2_tpms_pcr_selection_sub_mask(a, tpm2_tpms_pcr_selection_to_mask(b)); } /* Move all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ @@ -1269,11 +1285,7 @@ void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) #define FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) \ _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, UNIQ) #define _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, uniq) \ - FOREACH_PCR_IN_MASK(pcr, \ - ({ uint32_t UNIQ_T(_mask, uniq); \ - tpm2_tpms_pcr_selection_to_mask(tpms, &UNIQ_T(_mask, uniq)); \ - UNIQ_T(_mask, uniq); \ - })) + FOREACH_PCR_IN_MASK(pcr, tpm2_tpms_pcr_selection_to_mask(tpms)) #define FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, UNIQ) @@ -1295,21 +1307,17 @@ char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s) { const char *algstr = strna(tpm2_hash_alg_to_string(s->hash)); - uint32_t mask; - tpm2_tpms_pcr_selection_to_mask(s, &mask); - _cleanup_free_ char *maskstr = tpm2_pcr_mask_to_string(mask); - if (!maskstr) + _cleanup_free_ char *mask = tpm2_pcr_mask_to_string(tpm2_tpms_pcr_selection_to_mask(s)); + if (!mask) return NULL; - return strjoin(algstr, "(", maskstr, ")"); + return strjoin(algstr, "(", mask, ")"); } size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s) { assert(s); - uint32_t mask; - tpm2_tpms_pcr_selection_to_mask(s, &mask); - return popcount(mask); + return popcount(tpm2_tpms_pcr_selection_to_mask(s)); } /* Utility functions for TPML_PCR_SELECTION. */ @@ -1360,10 +1368,9 @@ static TPMS_PCR_SELECTION *tpm2_tpml_pcr_selection_get_tpms_pcr_selection( return selection; } -/* Convert a TPML_PCR_SELECTION object to a mask. Returns -ENOENT if 'hash_alg' is not in the object. */ -int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg, uint32_t *ret) { +/* Convert a TPML_PCR_SELECTION object to a mask. Returns empty mask (i.e. 0) if 'hash_alg' is not in the object. */ +uint32_t tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg) { assert(l); - assert(ret); /* Make a copy, as tpm2_tpml_pcr_selection_get_tpms_pcr_selection() will modify the object if there * are multiple entries with the requested hash alg. */ @@ -1372,10 +1379,9 @@ int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH h TPMS_PCR_SELECTION *s; s = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(&lcopy, hash_alg); if (!s) - return SYNTHETIC_ERRNO(ENOENT); + return 0; - tpm2_tpms_pcr_selection_to_mask(s, ret); - return 0; + return tpm2_tpms_pcr_selection_to_mask(s); } /* Convert a mask and hash alg to a TPML_PCR_SELECTION object. */ @@ -1439,6 +1445,33 @@ void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const tpm2_tpms_pcr_selection_sub(selection, s); } +/* Test if all bits in the mask for the hash are set in the TPML_PCR_SELECTION. */ +bool tpm2_tpml_pcr_selection_has_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask) { + assert(l); + + return FLAGS_SET(tpm2_tpml_pcr_selection_to_mask(l, hash), mask); +} + +/* Add the PCR selections in the mask, with the provided hash. */ +void tpm2_tpml_pcr_selection_add_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask) { + TPMS_PCR_SELECTION tpms; + + assert(l); + + tpm2_tpms_pcr_selection_from_mask(mask, hash, &tpms); + tpm2_tpml_pcr_selection_add_tpms_pcr_selection(l, &tpms); +} + +/* Remove the PCR selections in the mask, with the provided hash. */ +void tpm2_tpml_pcr_selection_sub_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask) { + TPMS_PCR_SELECTION tpms; + + assert(l); + + tpm2_tpms_pcr_selection_from_mask(mask, hash, &tpms); + tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(l, &tpms); +} + /* Add all PCR selections in 'b' to 'a'. */ void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b) { assert(a); @@ -1487,7 +1520,321 @@ size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) { return weight; } -static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) { +bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value) { + int r; + + assert(pcr_value); + + if (!TPM2_PCR_INDEX_VALID(pcr_value->index)) { + log_debug("PCR index %u invalid.", pcr_value->index); + return false; + } + + /* If it contains a value, the value size must match the hash size. */ + if (pcr_value->value.size > 0) { + r = tpm2_hash_alg_to_size(pcr_value->hash); + if (r < 0) + return false; + + if ((int) pcr_value->value.size != r) { + log_debug("PCR hash 0x%" PRIx16 " expected size %d does not match actual size %" PRIu16 ".", + pcr_value->hash, r, pcr_value->value.size); + return false; + } + } + + return true; +} + +/* Verify all entries are valid, and consistent with each other. The requirements for consistency are: + * + * 1) all entries must be sorted in ascending order (e.g. using tpm2_sort_pcr_values()) + * 2) all entries must be unique, i.e. there cannot be 2 entries with the same hash and index + */ +bool TPM2_PCR_VALUES_VALID(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + assert(pcr_values || n_pcr_values == 0); + + for (size_t i = 0; i < n_pcr_values; i++) { + const Tpm2PCRValue *v = &pcr_values[i]; + + if (!TPM2_PCR_VALUE_VALID(v)) + return false; + + if (i == 0) + continue; + + const Tpm2PCRValue *l = &pcr_values[i - 1]; + + /* Hashes must be sorted in ascending order */ + if (v->hash < l->hash) { + log_debug("PCR values not in ascending order, hash %" PRIu16 " is after %" PRIu16 ".", + v->hash, l->hash); + return false; + } + + if (v->hash == l->hash) { + /* Indexes (for the same hash) must be sorted in ascending order */ + if (v->index < l->index) { + log_debug("PCR values not in ascending order, hash %" PRIu16 " index %u is after %u.", + v->hash, v->index, l->index); + return false; + } + + /* Indexes (for the same hash) must not be duplicates */ + if (v->index == l->index) { + log_debug("PCR values contain duplicates for hash %" PRIu16 " index %u.", + v->hash, v->index); + return false; + } + } + } + + return true; +} + +static int cmp_pcr_values(const Tpm2PCRValue *a, const Tpm2PCRValue *b) { + assert(a); + assert(b); + + return CMP(a->hash, b->hash) ?: CMP(a->index, b->index); +} + +/* Sort the array of Tpm2PCRValue entries in-place. This sorts first in ascending order of hash algorithm + * (sorting simply by the TPM2 hash algorithm number), and then sorting by pcr index. */ +void tpm2_sort_pcr_values(Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + typesafe_qsort(pcr_values, n_pcr_values, cmp_pcr_values); +} + +int tpm2_pcr_values_from_mask(uint32_t mask, TPMI_ALG_HASH hash, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values = 0; + + assert(ret_pcr_values); + assert(ret_n_pcr_values); + + FOREACH_PCR_IN_MASK(index, mask) + if (!GREEDY_REALLOC_APPEND( + pcr_values, + n_pcr_values, + &TPM2_PCR_VALUE_MAKE(index, hash, {}), + 1)) + return log_oom_debug(); + + *ret_pcr_values = TAKE_PTR(pcr_values); + *ret_n_pcr_values = n_pcr_values; + + return 0; +} + +int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPMI_ALG_HASH hash, uint32_t *ret_mask) { + uint32_t mask = 0; + + assert(pcr_values || n_pcr_values == 0); + assert(ret_mask); + + if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PCR values."); + + for (size_t i = 0; i < n_pcr_values; i++) + if (pcr_values[i].hash == hash) + SET_BIT(mask, pcr_values[i].index); + + *ret_mask = mask; + + return 0; +} + +int tpm2_tpml_pcr_selection_from_pcr_values( + const Tpm2PCRValue *pcr_values, + size_t n_pcr_values, + TPML_PCR_SELECTION *ret_selection, + TPM2B_DIGEST **ret_values, + size_t *ret_n_values) { + + TPML_PCR_SELECTION selection = {}; + _cleanup_free_ TPM2B_DIGEST *values = NULL; + size_t n_values = 0; + + assert(pcr_values || n_pcr_values == 0); + + if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "PCR values are not valid."); + + for (size_t i = 0; i < n_pcr_values; i++) { + unsigned index = pcr_values[i].index; + TPMI_ALG_HASH hash = pcr_values[i].hash; + const TPM2B_DIGEST *digest = &pcr_values[i].value; + + tpm2_tpml_pcr_selection_add_mask(&selection, hash, INDEX_TO_MASK(uint32_t, index)); + + if (!GREEDY_REALLOC_APPEND(values, n_values, digest, 1)) + return log_oom_debug(); + } + + if (ret_selection) + *ret_selection = selection; + if (ret_values) + *ret_values = TAKE_PTR(values); + if (ret_n_values) + *ret_n_values = n_values; + + return 0; +} + +/* Count the number of different hash algorithms for all the entries. */ +int tpm2_pcr_values_hash_count(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, size_t *ret_count) { + TPML_PCR_SELECTION selection; + int r; + + assert(pcr_values); + assert(ret_count); + + r = tpm2_tpml_pcr_selection_from_pcr_values( + pcr_values, + n_pcr_values, + &selection, + /* ret_values= */ NULL, + /* ret_n_values= */ NULL); + if (r < 0) + return r; + + *ret_count = selection.count; + + return 0; +} + +/* Parse a string argument into a Tpm2PCRValue object. + * + * The format is [:hash[=value]] where index is the index number (or name) of the PCR, e.g. 0 (or + * platform-code), hash is the name of the hash algorithm (e.g. sha256) and value is the hex hash digest + * value, optionally with a leading 0x. This does not check for validity of the fields. */ +int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + Tpm2PCRValue pcr_value = {}; + const char *p = arg; + int r; + + assert(arg); + assert(ret_pcr_value); + + _cleanup_free_ char *index = NULL; + r = extract_first_word(&p, &index, ":", /* flags= */ 0); + if (r < 1) + return log_error_errno(r, "Could not parse pcr value '%s': %m", p); + + r = pcr_index_from_string(index); + if (r < 0) + return log_error_errno(r, "Invalid pcr index '%s': %m", index); + pcr_value.index = (unsigned) r; + + if (!isempty(p)) { + _cleanup_free_ char *hash = NULL; + r = extract_first_word(&p, &hash, "=", /* flags= */ 0); + if (r < 1) + return log_error_errno(r, "Could not parse pcr hash algorithm '%s': %m", p); + + r = tpm2_hash_alg_from_string(hash); + if (r < 0) + return log_error_errno(r, "Invalid pcr hash algorithm '%s': %m", hash); + pcr_value.hash = (TPMI_ALG_HASH) r; + } + + if (!isempty(p)) { + /* Remove leading 0x if present */ + p = startswith_no_case(p, "0x") ?: p; + + _cleanup_free_ void *buf = NULL; + size_t buf_size = 0; + r = unhexmem(p, strlen(p), &buf, &buf_size); + if (r < 0) + return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); + + r = TPM2B_DIGEST_CHECK_SIZE(buf_size); + if (r < 0) + return log_error_errno(r, "PCR hash value size %zu too large.", buf_size); + + pcr_value.value = TPM2B_DIGEST_MAKE(buf, buf_size); + } + + *ret_pcr_value = pcr_value; + + return 0; +} + +/* Return a string for the PCR value. The format is described in tpm2_pcr_value_from_string(). Note that if + * the hash algorithm is not recognized, neither hash name nor hash digest value is included in the + * string. This does not check for validity. */ +char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value) { + _cleanup_free_ char *index = NULL, *value = NULL; + int r; + + r = asprintf(&index, "%u", pcr_value->index); + if (r < 0) + return NULL; + + const char *hash = tpm2_hash_alg_to_string(pcr_value->hash); + + if (hash && pcr_value->value.size > 0) { + value = hexmem(pcr_value->value.buffer, pcr_value->value.size); + if (!value) + return NULL; + } + + return strjoin(index, hash ? ":" : "", hash ?: "", value ? "=" : "", value ?: ""); +} + +/* Parse a string argument into an array of Tpm2PCRValue objects. + * + * The format is zero or more entries separated by ',' or '+'. The format of each entry is described in + * tpm2_pcr_value_from_string(). This does not check for validity of the entries. */ +int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { + const char *p = arg; + int r; + + assert(arg); + assert(ret_pcr_values); + assert(ret_n_pcr_values); + + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values = 0; + + for (;;) { + _cleanup_free_ char *pcr_arg = NULL; + r = extract_first_word(&p, &pcr_arg, ",+", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Could not parse pcr values '%s': %m", p); + if (r == 0) + break; + + Tpm2PCRValue pcr_value; + r = tpm2_pcr_value_from_string(pcr_arg, &pcr_value); + if (r < 0) + return r; + + if (!GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, &pcr_value, 1)) + return log_oom(); + } + + *ret_pcr_values = TAKE_PTR(pcr_values); + *ret_n_pcr_values = n_pcr_values; + + return 0; +} + +/* Return a string representing the array of PCR values. The format is as described in + * tpm2_pcr_values_from_string(). This does not check for validity. */ +char *tpm2_pcr_values_to_string(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + _cleanup_free_ char *s = NULL; + + for (size_t i = 0; i < n_pcr_values; i++) { + _cleanup_free_ char *pcrstr = tpm2_pcr_value_to_string(&pcr_values[i]); + if (!pcrstr || !strextend_with_separator(&s, "+", pcrstr)) + return NULL; + } + + return s ? TAKE_PTR(s) : strdup(""); +} + +void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) { if (!DEBUG_LOGGING || !l) return; @@ -1495,7 +1842,15 @@ static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const log_debug("%s: %s", msg ?: "PCR selection", strna(s)); } -static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { +void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg) { + if (!DEBUG_LOGGING || !pcr_value) + return; + + _cleanup_free_ char *s = tpm2_pcr_value_to_string(pcr_value); + log_debug("%s: %s", msg ?: "PCR value", strna(s)); +} + +void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { if (!DEBUG_LOGGING || !buffer || size == 0) return; @@ -1503,12 +1858,12 @@ static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *m log_debug("%s: %s", msg ?: "Buffer", strna(h)); } -static void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) { +void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) { if (digest) tpm2_log_debug_buffer(digest->buffer, digest->size, msg ?: "Digest"); } -static void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg) { +void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg) { if (name) tpm2_log_debug_buffer(name->name, name->size, msg ?: "Name"); } @@ -1890,22 +2245,27 @@ int tpm2_create_loaded( return 0; } -static int tpm2_pcr_read( +/* Read hash values from the specified PCR selection. Provides a Tpm2PCRValue array that contains all + * requested PCR values, in the order provided by the TPM. Normally, the provided pcr values will match + * exactly what is in the provided selection, but the TPM may ignore some selected PCRs (for example, if an + * unimplemented PCR index is requested), in which case those PCRs will be absent from the provided pcr + * values. */ +int tpm2_pcr_read( Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, - TPML_PCR_SELECTION *ret_pcr_selection, - TPM2B_DIGEST **ret_pcr_values, + Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { - _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; - TPML_PCR_SELECTION remaining, total_read = {}; + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; size_t n_pcr_values = 0; TSS2_RC rc; assert(c); assert(pcr_selection); + assert(ret_pcr_values); + assert(ret_n_pcr_values); - remaining = *pcr_selection; + TPML_PCR_SELECTION remaining = *pcr_selection; while (!tpm2_tpml_pcr_selection_is_empty(&remaining)) { _cleanup_(Esys_Freep) TPML_PCR_SELECTION *current_read = NULL; _cleanup_(Esys_Freep) TPML_DIGEST *current_values = NULL; @@ -1926,44 +2286,103 @@ static int tpm2_pcr_read( return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc)); + tpm2_log_debug_tpml_pcr_selection(current_read, "Read PCR selection"); + if (tpm2_tpml_pcr_selection_is_empty(current_read)) { log_warning("TPM2 refused to read possibly unimplemented PCRs, ignoring."); break; } + unsigned i = 0; + FOREACH_PCR_IN_TPML_PCR_SELECTION(index, tpms, current_read) { + assert(i < current_values->count); + Tpm2PCRValue pcr_value = { + .index = index, + .hash = tpms->hash, + .value = current_values->digests[i++], + }; + + tpm2_log_debug_pcr_value(&pcr_value, /* msg= */ NULL); + + if (!GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, &pcr_value, 1)) + return log_oom(); + } + assert(i == current_values->count); + tpm2_tpml_pcr_selection_sub(&remaining, current_read); - tpm2_tpml_pcr_selection_add(&total_read, current_read); + } - if (!GREEDY_REALLOC(pcr_values, n_pcr_values + current_values->count)) - return log_oom(); + tpm2_sort_pcr_values(pcr_values, n_pcr_values); - memcpy_safe(&pcr_values[n_pcr_values], current_values->digests, - current_values->count * sizeof(TPM2B_DIGEST)); - n_pcr_values += current_values->count; + if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PCR values read from TPM are not valid."); - if (DEBUG_LOGGING) { - unsigned i = 0; - FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, current_read) { - assert(i < current_values->count); + *ret_pcr_values = TAKE_PTR(pcr_values); + *ret_n_pcr_values = n_pcr_values; - TPM2B_DIGEST *d = ¤t_values->digests[i]; - i++; + return 0; +} - TPML_PCR_SELECTION l; - tpm2_tpml_pcr_selection_from_mask(INDEX_TO_MASK(uint32_t, pcr), s->hash, &l); +/* Read the PCR value for each TPM2PCRValue entry in the array that does not have a value set. If all entries + * have an unset hash (i.e. hash == 0), this first detects the "best" PCR bank to use; otherwise, all entries + * must have a valid hash set. All entries must have a valid index. If this cannot read a PCR value for all + * appropriate entries, this returns an error. This does not check the array for validity. */ +int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + TPMI_ALG_HASH pcr_bank = 0; + int r; - _cleanup_free_ char *desc = tpm2_tpml_pcr_selection_to_string(&l); - tpm2_log_debug_digest(d, strna(desc)); - } + assert(c); + assert(pcr_values || n_pcr_values == 0); + + if (n_pcr_values > 0) { + size_t hash_count; + r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count); + if (r < 0) + return log_error_errno(r, "Could not get hash count from pcr values: %m"); + + if (hash_count == 1 && pcr_values[0].hash == 0) { + uint32_t mask; + r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, 0, &mask); + if (r < 0) + return r; + + r = tpm2_get_best_pcr_bank(c, mask, &pcr_bank); + if (r < 0) + return r; } } - if (ret_pcr_selection) - *ret_pcr_selection = total_read; - if (ret_pcr_values) - *ret_pcr_values = TAKE_PTR(pcr_values); - if (ret_n_pcr_values) - *ret_n_pcr_values = n_pcr_values; + for (size_t i = 0; i < n_pcr_values; i++) { + Tpm2PCRValue *v = &pcr_values[i]; + + if (v->hash == 0) + v->hash = pcr_bank; + + if (v->value.size > 0) + continue; + + TPML_PCR_SELECTION selection; + r = tpm2_tpml_pcr_selection_from_pcr_values(v, 1, &selection, NULL, NULL); + if (r < 0) + return r; + + _cleanup_free_ Tpm2PCRValue *read_values = NULL; + size_t n_read_values; + r = tpm2_pcr_read(c, &selection, &read_values, &n_read_values); + if (r < 0) + return r; + + if (n_read_values == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Could not read PCR hash 0x%" PRIu16 " index %u", + v->hash, v->index); + + assert(n_read_values == 1); + assert(read_values[0].hash == v->hash); + assert(read_values[0].index == v->index); + + v->value = read_values[0].value; + } return 0; } @@ -1973,9 +2392,7 @@ static int tpm2_pcr_mask_good( TPMI_ALG_HASH bank, uint32_t mask) { - _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; TPML_PCR_SELECTION selection; - size_t n_pcr_values = 0; int r; assert(c); @@ -1986,22 +2403,18 @@ static int tpm2_pcr_mask_good( tpm2_tpml_pcr_selection_from_mask(mask, bank, &selection); - r = tpm2_pcr_read(c, &selection, &selection, &pcr_values, &n_pcr_values); + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values; + r = tpm2_pcr_read(c, &selection, &pcr_values, &n_pcr_values); if (r < 0) return r; /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */ - unsigned i = 0; - FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, &selection) { - assert(i < n_pcr_values); - - if (!memeqbyte(0x00, pcr_values[i].buffer, pcr_values[i].size) && - !memeqbyte(0xFF, pcr_values[i].buffer, pcr_values[i].size)) + for (unsigned i = 0; i < n_pcr_values; i++) + if (!memeqbyte(0x00, pcr_values[i].value.buffer, pcr_values[i].value.size) && + !memeqbyte(0xFF, pcr_values[i].value.buffer, pcr_values[i].value.size)) return true; - i++; - } - return false; } @@ -2035,7 +2448,7 @@ static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { return valid; } -static int tpm2_get_best_pcr_bank( +int tpm2_get_best_pcr_bank( Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret) { @@ -2494,124 +2907,6 @@ static int tpm2_make_policy_session( return 0; } -static int openssl_pubkey_to_tpm2_pubkey( - const void *pubkey, - size_t pubkey_size, - TPM2B_PUBLIC *output, - void **ret_fp, - size_t *ret_fp_size) { - -#if HAVE_OPENSSL -#if OPENSSL_VERSION_MAJOR >= 3 - _cleanup_(BN_freep) BIGNUM *n = NULL, *e = NULL; -#else - const BIGNUM *n = NULL, *e = NULL; - const RSA *rsa = NULL; -#endif - int r, n_bytes, e_bytes; - - assert(pubkey); - assert(pubkey_size > 0); - assert(output); - - /* Converts an OpenSSL public key to a structure that the TPM chip can process. */ - - _cleanup_fclose_ FILE *f = NULL; - f = fmemopen((void*) pubkey, pubkey_size, "r"); - if (!f) - return log_oom(); - - _cleanup_(EVP_PKEY_freep) EVP_PKEY *input = NULL; - input = PEM_read_PUBKEY(f, NULL, NULL, NULL); - if (!input) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PEM public key."); - - if (EVP_PKEY_base_id(input) != EVP_PKEY_RSA) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided public key is not an RSA key."); - -#if OPENSSL_VERSION_MAJOR >= 3 - if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_N, &n)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key."); -#else - rsa = EVP_PKEY_get0_RSA(input); - if (!rsa) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to extract RSA key from public key."); - - n = RSA_get0_n(rsa); - if (!n) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key."); -#endif - - n_bytes = BN_num_bytes(n); - assert_se(n_bytes > 0); - if ((size_t) n_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.unique.rsa.buffer)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA modulus too large for TPM2 public key object."); - -#if OPENSSL_VERSION_MAJOR >= 3 - if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_E, &e)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key."); -#else - e = RSA_get0_e(rsa); - if (!e) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key."); -#endif - - e_bytes = BN_num_bytes(e); - assert_se(e_bytes > 0); - if ((size_t) e_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.parameters.rsaDetail.exponent)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA exponent too large for TPM2 public key object."); - - *output = (TPM2B_PUBLIC) { - .size = sizeof(TPMT_PUBLIC), - .publicArea = { - .type = TPM2_ALG_RSA, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH, - .parameters.rsaDetail = { - .scheme = { - .scheme = TPM2_ALG_NULL, - .details.anySig.hashAlg = TPM2_ALG_NULL, - }, - .symmetric = { - .algorithm = TPM2_ALG_NULL, - .mode.sym = TPM2_ALG_NULL, - }, - .keyBits = n_bytes * 8, - /* .exponent will be filled in below. */ - }, - .unique = { - .rsa.size = n_bytes, - /* .rsa.buffer will be filled in below. */ - }, - }, - }; - - if (BN_bn2bin(n, output->publicArea.unique.rsa.buffer) <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA modulus."); - - if (BN_bn2bin(e, (unsigned char*) &output->publicArea.parameters.rsaDetail.exponent) <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA exponent."); - - if (ret_fp) { - _cleanup_free_ void *fp = NULL; - size_t fp_size; - - assert(ret_fp_size); - - r = pubkey_fingerprint(input, EVP_sha256(), &fp, &fp_size); - if (r < 0) - return log_error_errno(r, "Failed to calculate public key fingerprint: %m"); - - *ret_fp = TAKE_PTR(fp); - *ret_fp_size = fp_size; - } - - return 0; -#else /* HAVE_OPENSSL */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); -#endif -} - static int find_signature( JsonVariant *v, const TPML_PCR_SELECTION *pcr_selection, @@ -2634,10 +2929,7 @@ static int find_signature( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object."); uint16_t pcr_bank = pcr_selection->pcrSelections[0].hash; - uint32_t pcr_mask; - r = tpm2_tpml_pcr_selection_to_mask(pcr_selection, pcr_bank, &pcr_mask); - if (r < 0) - return r; + uint32_t pcr_mask = tpm2_tpml_pcr_selection_to_mask(pcr_selection, pcr_bank); k = tpm2_hash_alg_to_string(pcr_bank); if (!k) @@ -2867,8 +3159,7 @@ static int tpm2_policy_auth_value( /* Extend 'digest' with the PolicyPCR calculated hash. */ int tpm2_calculate_policy_pcr( - const TPML_PCR_SELECTION *pcr_selection, - const TPM2B_DIGEST pcr_values[], + const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest) { @@ -2876,7 +3167,6 @@ int tpm2_calculate_policy_pcr( TSS2_RC rc; int r; - assert(pcr_selection); assert(pcr_values || n_pcr_values == 0); assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); @@ -2885,13 +3175,20 @@ int tpm2_calculate_policy_pcr( if (r < 0) return log_error_errno(r, "TPM2 support not installed: %m"); + TPML_PCR_SELECTION pcr_selection; + _cleanup_free_ TPM2B_DIGEST *values = NULL; + size_t n_values; + r = tpm2_tpml_pcr_selection_from_pcr_values(pcr_values, n_pcr_values, &pcr_selection, &values, &n_values); + if (r < 0) + return log_error_errno(r, "Could not convert PCR values to TPML_PCR_SELECTION: %m"); + TPM2B_DIGEST hash = {}; - r = tpm2_digest_many_digests(TPM2_ALG_SHA256, &hash, pcr_values, n_pcr_values, /* extend= */ false); + r = tpm2_digest_many_digests(TPM2_ALG_SHA256, &hash, values, n_values, /* extend= */ false); if (r < 0) return r; _cleanup_free_ uint8_t *buf = NULL; - size_t size = 0, maxsize = sizeof(command) + sizeof(*pcr_selection); + size_t size = 0, maxsize = sizeof(command) + sizeof(pcr_selection); buf = malloc(maxsize); if (!buf) @@ -2902,7 +3199,7 @@ int tpm2_calculate_policy_pcr( return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to marshal PolicyPCR command: %s", sym_Tss2_RC_Decode(rc)); - rc = sym_Tss2_MU_TPML_PCR_SELECTION_Marshal(pcr_selection, buf, maxsize, &size); + rc = sym_Tss2_MU_TPML_PCR_SELECTION_Marshal(&pcr_selection, buf, maxsize, &size); if (rc != TSS2_RC_SUCCESS) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to marshal PCR selection: %s", sym_Tss2_RC_Decode(rc)); @@ -3074,16 +3371,17 @@ static int tpm2_policy_authorize( if (r < 0) return r; + r = TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(signature_size); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); + TPMT_SIGNATURE policy_signature = { .sigAlg = TPM2_ALG_RSASSA, .signature.rsassa = { .hash = TPM2_ALG_SHA256, - .sig.size = signature_size, + .sig = TPM2B_PUBLIC_KEY_RSA_MAKE(signature_raw, signature_size), }, }; - if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); - memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size); rc = sym_Esys_VerifySignature( c->esys_context, @@ -3127,16 +3425,16 @@ static int tpm2_policy_authorize( } /* Extend 'digest' with the calculated policy hash. */ -static int tpm2_calculate_sealing_policy( - const TPML_PCR_SELECTION *hash_pcr_selection, - const TPM2B_DIGEST *hash_pcr_values, - size_t n_hash_pcr_values, +int tpm2_calculate_sealing_policy( + const Tpm2PCRValue *pcr_values, + size_t n_pcr_values, const TPM2B_PUBLIC *public, - const char *pin, + bool use_pin, TPM2B_DIGEST *digest) { int r; + assert(pcr_values || n_pcr_values == 0); assert(digest); if (public) { @@ -3145,13 +3443,13 @@ static int tpm2_calculate_sealing_policy( return r; } - if (hash_pcr_selection && !tpm2_tpml_pcr_selection_is_empty(hash_pcr_selection)) { - r = tpm2_calculate_policy_pcr(hash_pcr_selection, hash_pcr_values, n_hash_pcr_values, digest); + if (n_pcr_values > 0) { + r = tpm2_calculate_policy_pcr(pcr_values, n_pcr_values, digest); if (r < 0) return r; } - if (pin) { + if (use_pin) { r = tpm2_calculate_policy_auth_value(digest); if (r < 0) return r; @@ -3218,38 +3516,231 @@ static int tpm2_build_sealing_policy( return 0; } -int tpm2_seal(const char *device, - uint32_t hash_pcr_mask, - const void *pubkey, - const size_t pubkey_size, - uint32_t pubkey_pcr_mask, +#if HAVE_OPENSSL +static int tpm2_ecc_curve_from_openssl_curve_id(int curve_id, TPM2_ECC_CURVE *ret) { + assert(ret); + + switch (curve_id) { + case NID_X9_62_prime192v1: *ret = TPM2_ECC_NIST_P192; return 0; + case NID_secp224r1: *ret = TPM2_ECC_NIST_P192; return 0; + case NID_X9_62_prime256v1: *ret = TPM2_ECC_NIST_P256; return 0; + case NID_secp384r1: *ret = TPM2_ECC_NIST_P384; return 0; + case NID_secp521r1: *ret = TPM2_ECC_NIST_P521; return 0; + case NID_sm2: *ret = TPM2_ECC_SM2_P256; return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Openssl ECC curve id %d not supported.", curve_id); +} + +static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE curve, int *ret) { + assert(ret); + + switch (curve) { + case TPM2_ECC_NIST_P192: *ret = NID_X9_62_prime192v1; return 0; + case TPM2_ECC_NIST_P224: *ret = NID_secp224r1; return 0; + case TPM2_ECC_NIST_P256: *ret = NID_X9_62_prime256v1; return 0; + case TPM2_ECC_NIST_P384: *ret = NID_secp384r1; return 0; + case TPM2_ECC_NIST_P521: *ret = NID_secp521r1; return 0; + case TPM2_ECC_SM2_P256: *ret = NID_sm2; return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 ECC curve %u not supported.", curve); +} + +#define TPM2_RSA_DEFAULT_EXPONENT UINT32_C(0x10001) + +int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret) { + int r; + + assert(public); + assert(ret); + + const TPMT_PUBLIC *p = &public->publicArea; + if (p->type == TPM2_ALG_ECC) { + int curve_id; + r = tpm2_ecc_curve_to_openssl_curve_id(p->parameters.eccDetail.curveID, &curve_id); + if (r < 0) + return r; + + const TPMS_ECC_POINT *point = &p->unique.ecc; + return ecc_pkey_from_curve_x_y( + curve_id, + point->x.buffer, + point->x.size, + point->y.buffer, + point->y.size, + ret); + } + + if (p->type == TPM2_ALG_RSA) { + /* TPM specification Part 2 ("Structures") section for TPMS_RSA_PARAMS states "An exponent of + * zero indicates that the exponent is the default of 2^16 + 1". */ + uint32_t exponent = htobe32(p->parameters.rsaDetail.exponent ?: TPM2_RSA_DEFAULT_EXPONENT); + return rsa_pkey_from_n_e( + p->unique.rsa.buffer, + p->unique.rsa.size, + &exponent, + sizeof(exponent), + ret); + } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 asymmetric algorithm 0x%" PRIx16 " not supported.", p->type); +} + +int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) { + int key_id, r; + + assert(pkey); + assert(ret); + + TPMT_PUBLIC public = { + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH, + .parameters.asymDetail = { + .symmetric.algorithm = TPM2_ALG_NULL, + .scheme.scheme = TPM2_ALG_NULL, + }, + }; + +#if OPENSSL_VERSION_MAJOR >= 3 + key_id = EVP_PKEY_get_id(pkey); +#else + key_id = EVP_PKEY_id(pkey); +#endif + + if (key_id == EVP_PKEY_EC) { + public.type = TPM2_ALG_ECC; + + int curve_id; + _cleanup_free_ void *x = NULL, *y = NULL; + size_t x_size, y_size; + r = ecc_pkey_to_curve_x_y(pkey, &curve_id, &x, &x_size, &y, &y_size); + if (r < 0) + return log_error_errno(r, "Could not get ECC key curve/x/y: %m"); + + TPM2_ECC_CURVE curve; + r = tpm2_ecc_curve_from_openssl_curve_id(curve_id, &curve); + if (r < 0) + return r; + + public.parameters.eccDetail.curveID = curve; + + public.parameters.eccDetail.kdf.scheme = TPM2_ALG_NULL; + + r = TPM2B_ECC_PARAMETER_CHECK_SIZE(x_size); + if (r < 0) + return log_error_errno(r, "ECC key x size %zu too large.", x_size); + + public.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(x, x_size); + + r = TPM2B_ECC_PARAMETER_CHECK_SIZE(y_size); + if (r < 0) + return log_error_errno(r, "ECC key y size %zu too large.", y_size); + + public.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(y, y_size); + } else if (key_id == EVP_PKEY_RSA) { + public.type = TPM2_ALG_RSA; + + _cleanup_free_ void *n = NULL, *e = NULL; + size_t n_size, e_size; + r = rsa_pkey_to_n_e(pkey, &n, &n_size, &e, &e_size); + if (r < 0) + return log_error_errno(r, "Could not get RSA key n/e: %m"); + + r = TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(n_size); + if (r < 0) + return log_error_errno(r, "RSA key n size %zu too large.", n_size); + + public.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(n, n_size); + public.parameters.rsaDetail.keyBits = n_size * 8; + + if (sizeof(uint32_t) < e_size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "RSA key e size %zu too large.", e_size); + + uint32_t exponent = 0; + memcpy((void*) &exponent, e, e_size); + exponent = be32toh(exponent) >> (32 - e_size * 8); + if (exponent == TPM2_RSA_DEFAULT_EXPONENT) + exponent = 0; + public.parameters.rsaDetail.exponent = exponent; + } else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "EVP_PKEY type %d not supported.", key_id); + + *ret = (TPM2B_PUBLIC) { + .size = sizeof(public), + .publicArea = public, + }; + + return 0; +} +#endif + +int tpm2_tpm2b_public_to_fingerprint( + const TPM2B_PUBLIC *public, + void **ret_fingerprint, + size_t *ret_fingerprint_size) { + +#if HAVE_OPENSSL + int r; + + assert(public); + assert(ret_fingerprint); + assert(ret_fingerprint_size); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + r = tpm2_tpm2b_public_to_openssl_pkey(public, &pkey); + if (r < 0) + return r; + + /* Hardcode fingerprint to SHA256 */ + return pubkey_fingerprint(pkey, EVP_sha256(), ret_fingerprint, ret_fingerprint_size); +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + +int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { +#if HAVE_OPENSSL + int r; + + assert(pem); + assert(ret); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + r = openssl_pkey_from_pem(pem, pem_size, &pkey); + if (r < 0) + return r; + + return tpm2_tpm2b_public_from_openssl_pkey(pkey, ret); +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + +int tpm2_seal(Tpm2Context *c, + const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, - void **ret_pcr_hash, - size_t *ret_pcr_hash_size, - uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size) { + uint16_t primary_alg = 0; TSS2_RC rc; int r; - assert(pubkey || pubkey_size == 0); - assert(ret_secret); assert(ret_secret_size); assert(ret_blob); assert(ret_blob_size); - assert(ret_pcr_hash); - assert(ret_pcr_hash_size); - assert(ret_pcr_bank); - - assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); - assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); /* 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 @@ -3269,54 +3760,6 @@ int tpm2_seal(const char *device, usec_t start = now(CLOCK_MONOTONIC); - _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; - r = tpm2_context_new(device, &c); - if (r < 0) - return r; - - TPMI_ALG_HASH pcr_bank = 0; - if (hash_pcr_mask | pubkey_pcr_mask) { - /* Some TPM2 devices only can do SHA1. Prefer SHA256 but allow SHA1. */ - r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); - if (r < 0) - return r; - } - - TPML_PCR_SELECTION hash_pcr_selection = {}; - _cleanup_free_ TPM2B_DIGEST *hash_pcr_values = NULL; - size_t n_hash_pcr_values = 0; - if (hash_pcr_mask) { - /* For now, we just read the current values from the system; we need to be able to specify - * expected values, eventually. */ - tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, pcr_bank, &hash_pcr_selection); - r = tpm2_pcr_read(c, &hash_pcr_selection, &hash_pcr_selection, &hash_pcr_values, &n_hash_pcr_values); - if (r < 0) - return r; - } - - TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; - if (pubkey) { - r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, NULL, NULL); - if (r < 0) - return r; - authorize_key = &pubkey_tpm2; - } - - TPM2B_DIGEST policy_digest; - r = tpm2_digest_init(TPM2_ALG_SHA256, &policy_digest); - if (r < 0) - return r; - - r = tpm2_calculate_sealing_policy( - &hash_pcr_selection, - hash_pcr_values, - n_hash_pcr_values, - authorize_key, - pin, - &policy_digest); - if (r < 0) - return r; - /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ @@ -3326,7 +3769,7 @@ int tpm2_seal(const char *device, .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, .unique.keyedHash.size = SHA256_DIGEST_SIZE, - .authPolicy = policy_digest, + .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), }; TPMS_SENSITIVE_CREATE hmac_sensitive = { @@ -3351,21 +3794,33 @@ int tpm2_seal(const char *device, if (r < 0) return log_error_errno(r, "Failed to generate secret key: %m"); - _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; if (ret_srk_buf) { - r = tpm2_get_or_create_srk(c, NULL, &primary_public, NULL, NULL, &primary_handle); + _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; + r = tpm2_get_or_create_srk( + c, + /* session= */ NULL, + &primary_public, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &primary_handle); if (r < 0) return r; + + primary_alg = primary_public->publicArea.type; } else { /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ + primary_alg = TPM2_ALG_ECC; + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; - r = tpm2_get_legacy_template(TPM2_ALG_ECC, &template.publicArea); + r = tpm2_get_legacy_template(primary_alg, &template.publicArea); if (r < 0) return log_error_errno(r, "Could not get legacy ECC template: %m"); if (!tpm2_supports_tpmt_public(c, &template.publicArea)) { - r = tpm2_get_legacy_template(TPM2_ALG_RSA, &template.publicArea); + primary_alg = TPM2_ALG_RSA; + + r = tpm2_get_legacy_template(primary_alg, &template.publicArea); if (r < 0) return log_error_errno(r, "Could not get legacy RSA template: %m"); @@ -3379,7 +3834,7 @@ int tpm2_seal(const char *device, /* session= */ NULL, &template, /* sensitive= */ NULL, - &primary_public, + /* ret_public= */ NULL, &primary_handle); if (r < 0) return r; @@ -3420,11 +3875,6 @@ int tpm2_seal(const char *device, return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); - _cleanup_free_ void *hash = NULL; - hash = memdup(policy_digest.buffer, policy_digest.size); - if (!hash) - return log_oom(); - /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both * the raw TPM handle as well as the object name. The object name is used to verify that * the key we use later is the key we expect to establish the session with. @@ -3460,10 +3910,9 @@ int tpm2_seal(const char *device, *ret_secret_size = hmac_sensitive.data.size; *ret_blob = TAKE_PTR(blob); *ret_blob_size = blob_size; - *ret_pcr_hash = TAKE_PTR(hash); - *ret_pcr_hash_size = policy_digest.size; - *ret_pcr_bank = pcr_bank; - *ret_primary_alg = primary_public->publicArea.type; + + if (ret_primary_alg) + *ret_primary_alg = primary_alg; return 0; } @@ -3594,14 +4043,17 @@ int tpm2_unseal(const char *device, if (r < 0) return r; - TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; + TPM2B_PUBLIC pubkey_tpm2b; _cleanup_free_ void *fp = NULL; size_t fp_size = 0; if (pubkey) { - r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); + r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); if (r < 0) - return r; - authorize_key = &pubkey_tpm2; + return log_error_errno(r, "Could not create TPMT_PUBLIC: %m"); + + r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size); + if (r < 0) + return log_error_errno(r, "Could not get key fingerprint: %m"); } /* @@ -3638,7 +4090,7 @@ int tpm2_unseal(const char *device, policy_session, hash_pcr_mask, pcr_bank, - authorize_key, + pubkey ? &pubkey_tpm2b : NULL, fp, fp_size, pubkey_pcr_mask, signature, @@ -3909,45 +4361,6 @@ char *tpm2_pcr_mask_to_string(uint32_t mask) { return TAKE_PTR(s); } -int tpm2_pcr_mask_from_string(const char *arg, uint32_t *ret_mask) { - uint32_t mask = 0; - int r; - - assert(arg); - assert(ret_mask); - - if (isempty(arg)) { - *ret_mask = 0; - return 0; - } - - /* Parses a "," or "+" separated list of PCR indexes. We support "," since this is a list after all, - * and most other tools expect comma separated PCR specifications. We also support "+" since in - * /etc/crypttab the "," is already used to separate options, hence a different separator is nice to - * avoid escaping. */ - - const char *p = arg; - for (;;) { - _cleanup_free_ char *pcr = NULL; - unsigned n; - - r = extract_first_word(&p, &pcr, ",+", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r == 0) - break; - if (r < 0) - return log_error_errno(r, "Failed to parse PCR list: %s", arg); - - r = pcr_index_from_string(pcr); - if (r < 0) - return log_error_errno(r, "Failed to parse specified PCR or specified PCR is out of range: %s", pcr); - n = r; - SET_BIT(mask, n); - } - - *ret_mask = mask; - return 0; -} - int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret) { _cleanup_(json_variant_unrefp) JsonVariant *a = NULL; int r; @@ -4239,6 +4652,18 @@ int tpm2_parse_luks2_json( return 0; } +int tpm2_hash_alg_to_size(uint16_t alg) { + if (alg == TPM2_ALG_SHA1) + return 20; + if (alg == TPM2_ALG_SHA256) + return 32; + if (alg == TPM2_ALG_SHA384) + return 48; + if (alg == TPM2_ALG_SHA512) + return 64; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown hash algorithm id 0x%" PRIx16, alg); +} + const char *tpm2_hash_alg_to_string(uint16_t alg) { if (alg == TPM2_ALG_SHA1) return "sha1"; @@ -4248,6 +4673,7 @@ const char *tpm2_hash_alg_to_string(uint16_t alg) { return "sha384"; if (alg == TPM2_ALG_SHA512) return "sha512"; + log_debug("Unknown hash algorithm id 0x%" PRIx16, alg); return NULL; } @@ -4260,7 +4686,7 @@ int tpm2_hash_alg_from_string(const char *alg) { return TPM2_ALG_SHA384; if (strcaseeq_ptr(alg, "sha512")) return TPM2_ALG_SHA512; - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown hash algorithm name '%s'", alg); } const char *tpm2_asym_alg_to_string(uint16_t alg) { @@ -4268,6 +4694,7 @@ const char *tpm2_asym_alg_to_string(uint16_t alg) { return "ecc"; if (alg == TPM2_ALG_RSA) return "rsa"; + log_debug("Unknown asymmetric algorithm id 0x%" PRIx16, alg); return NULL; } @@ -4276,7 +4703,7 @@ int tpm2_asym_alg_from_string(const char *alg) { return TPM2_ALG_ECC; if (strcaseeq_ptr(alg, "rsa")) return TPM2_ALG_RSA; - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown asymmetric algorithm name '%s'", alg); } Tpm2Support tpm2_support(void) { @@ -4314,29 +4741,147 @@ Tpm2Support tpm2_support(void) { return support; } -int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask) { - uint32_t m; +#if HAVE_TPM2 +static void tpm2_pcr_values_apply_default_hash_alg(Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + TPMI_ALG_HASH default_hash = 0; + for (size_t i = 0; i < n_pcr_values; i++) + if (pcr_values[i].hash != 0) { + default_hash = pcr_values[i].hash; + break; + } + + if (default_hash != 0) + for (size_t i = 0; i < n_pcr_values; i++) + if (pcr_values[i].hash == 0) + pcr_values[i].hash = default_hash; +} +#endif + +/* Parse the PCR selection/value arg(s) and return a corresponding array of Tpm2PCRValue objects. + * + * The format is the same as tpm2_pcr_values_from_string(). The first provided entry with a hash algorithm + * set will be used as the 'default' hash algorithm. All entries with an unset hash algorithm will be updated + * with the 'default' hash algorithm. The resulting array will be sorted and checked for validity. + * + * This will replace *ret_pcr_values with the new array of pcr values; to append to an existing array, use + * tpm2_parse_pcr_argument_append(). */ +int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { +#if HAVE_TPM2 int r; - assert(mask); + assert(arg); + assert(ret_pcr_values); + assert(ret_n_pcr_values); - /* For use in getopt_long() command line parsers: merges masks specified on the command line */ - - if (isempty(arg)) { - *mask = 0; - return 0; - } - - r = tpm2_pcr_mask_from_string(arg, &m); + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values = 0; + r = tpm2_pcr_values_from_string(arg, &pcr_values, &n_pcr_values); if (r < 0) return r; - if (*mask == UINT32_MAX) - *mask = m; - else - *mask |= m; + tpm2_pcr_values_apply_default_hash_alg(pcr_values, n_pcr_values); + + tpm2_sort_pcr_values(pcr_values, n_pcr_values); + + if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); + + *ret_pcr_values = TAKE_PTR(pcr_values); + *ret_n_pcr_values = n_pcr_values; return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support is disabled."); +#endif +} + +/* Same as tpm2_parse_pcr_argument(), but the pcr values array is appended to. If the provided pcr values + * array is not NULL, it must point to an allocated pcr values array and the provided number of pcr values + * must be correct. + * + * Note that 'arg' is parsed into a new array of pcr values independently of any previous pcr values, + * including application of the default hash algorithm. Then the two arrays are combined, the default hash + * algorithm check applied again (in case either the previous or current array had no default hash + * algorithm), and then the resulting array is sorted and rechecked for validity. */ +int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { +#if HAVE_TPM2 + int r; + + assert(arg); + assert(ret_pcr_values); + assert(ret_n_pcr_values); + + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values; + r = tpm2_parse_pcr_argument(arg, &pcr_values, &n_pcr_values); + if (r < 0) + return r; + + /* If we got previous values, append them. */ + if (*ret_pcr_values && !GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, *ret_pcr_values, *ret_n_pcr_values)) + return log_oom(); + + tpm2_pcr_values_apply_default_hash_alg(pcr_values, n_pcr_values); + + tpm2_sort_pcr_values(pcr_values, n_pcr_values); + + if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); + + SWAP_TWO(*ret_pcr_values, pcr_values); + *ret_n_pcr_values = n_pcr_values; + + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support is disabled."); +#endif +} + +/* Same as tpm2_parse_pcr_argument() but converts the pcr values to a pcr mask. If more than one hash + * algorithm is included in the pcr values array this results in error. This retains the previous behavior of + * tpm2_parse_pcr_argument() of clearing the mask if 'arg' is empty, replacing the mask if it is set to + * UINT32_MAX, and or-ing the mask otherwise. */ +int tpm2_parse_pcr_argument_to_mask(const char *arg, uint32_t *ret_mask) { +#if HAVE_TPM2 + _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values; + int r; + + assert(arg); + assert(ret_mask); + + r = tpm2_parse_pcr_argument(arg, &pcr_values, &n_pcr_values); + if (r < 0) + return r; + + if (n_pcr_values == 0) { + /* This retains the previous behavior of clearing the mask if the arg is empty */ + *ret_mask = 0; + return 0; + } + + size_t hash_count; + r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count); + if (r < 0) + return log_error_errno(r, "Could not get hash count from pcr values: %m"); + + if (hash_count > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR hash banks selected."); + + uint32_t new_mask; + r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, pcr_values[0].hash, &new_mask); + if (r < 0) + return log_error_errno(r, "Could not get pcr values mask: %m"); + + if (*ret_mask == UINT32_MAX) + *ret_mask = new_mask; + else + *ret_mask |= new_mask; + + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support is disabled."); +#endif } int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 12f05b43f9..32da247c37 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -7,6 +7,7 @@ #include "io-util.h" #include "json.h" #include "macro.h" +#include "openssl-util.h" #include "sha256.h" typedef enum TPM2Flags { @@ -17,7 +18,7 @@ typedef enum TPM2Flags { * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ #define TPM2_PCRS_MAX 24U #define TPM2_PCRS_MASK ((UINT32_C(1) << TPM2_PCRS_MAX) - 1) -static inline bool TPM2_PCR_VALID(unsigned pcr) { +static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) { return pcr < TPM2_PCRS_MAX; } static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { @@ -34,26 +35,6 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { int dlopen_tpm2(void); -int tpm2_digest_many(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const struct iovec data[], size_t count, bool extend); -static inline int tpm2_digest_buffer(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const void *data, size_t len, bool extend) { - return tpm2_digest_many(alg, digest, &IOVEC_MAKE((void*) data, len), 1, extend); -} -int tpm2_digest_many_digests(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const TPM2B_DIGEST data[], size_t count, bool extend); -static inline int tpm2_digest_rehash(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { - return tpm2_digest_many(alg, digest, NULL, 0, true); -} -static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { - return tpm2_digest_many(alg, digest, NULL, 0, false); -} - -int tpm2_calculate_name(const TPMT_PUBLIC *public, 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_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); - -int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); -int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); - typedef struct { unsigned n_ref; @@ -88,6 +69,26 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); +typedef struct { + unsigned index; + TPMI_ALG_HASH hash; + TPM2B_DIGEST value; +} Tpm2PCRValue; + +#define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) { .index = (i), .hash = (h), .value = ((TPM2B_DIGEST) v), } +bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value); +int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value); +char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value); + +bool TPM2_PCR_VALUES_VALID(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); +void tpm2_sort_pcr_values(Tpm2PCRValue *pcr_values, size_t n_pcr_values); +int tpm2_pcr_values_from_mask(uint32_t mask, TPMI_ALG_HASH hash, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); +int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPMI_ALG_HASH hash, uint32_t *ret_mask); +int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); +char *tpm2_pcr_values_to_string(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); +int tpm2_pcr_values_hash_count(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, size_t *ret_count); +int tpm2_tpml_pcr_selection_from_pcr_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPML_PCR_SELECTION *ret_selection, TPM2B_DIGEST **ret_values, size_t *ret_n_values); + int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); int tpm2_create(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private); int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private, Tpm2Handle **ret_handle); @@ -99,11 +100,15 @@ bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARM int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); +int tpm2_get_best_pcr_bank(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret); int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); -void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret); +uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s); void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); +bool tpm2_tpms_pcr_selection_has_mask(const TPMS_PCR_SELECTION *s, uint32_t mask); +void tpm2_tpms_pcr_selection_add_mask(TPMS_PCR_SELECTION *s, uint32_t mask); +void tpm2_tpms_pcr_selection_sub_mask(TPMS_PCR_SELECTION *s, uint32_t mask); void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b); @@ -111,8 +116,11 @@ char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s); size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s); #define tpm2_tpms_pcr_selection_is_empty(s) (tpm2_tpms_pcr_selection_weight(s) == 0) -int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t *ret); +uint32_t tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash); void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPML_PCR_SELECTION *ret); +bool tpm2_tpml_pcr_selection_has_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask); +void tpm2_tpml_pcr_selection_add_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask); +void tpm2_tpml_pcr_selection_sub_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask); void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b); @@ -121,9 +129,106 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l); size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); #define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0) +int tpm2_digest_many(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const struct iovec data[], size_t count, bool extend); +static inline int tpm2_digest_buffer(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const void *data, size_t len, bool extend) { + return tpm2_digest_many(alg, digest, &IOVEC_MAKE((void*) data, len), 1, extend); +} +int tpm2_digest_many_digests(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const TPM2B_DIGEST data[], size_t count, bool extend); +static inline int tpm2_digest_rehash(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + return tpm2_digest_many(alg, digest, NULL, 0, true); +} +static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + return tpm2_digest_many(alg, digest, NULL, 0, false); +} + +void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg); +void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg); +void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg); +void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg); +void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); + +int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); +int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); + +int tpm2_calculate_name(const TPMT_PUBLIC *public, 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_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); +int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + +int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + +#if HAVE_OPENSSL +int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); +int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret); +#endif + +int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret); +int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size); + +/* The tpm2-tss library has many structs that are simply a combination of an array (or object) and + * size. These macros allow easily initializing or assigning instances of such structs from an existing + * buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the + * provided buffer/object is NULL, the resulting struct's buffer/object will be 0s. If the provided size is + * larger than the struct's buffer/object size, this results in assertion failure; to check the size, use one + * of the TPM2B_*_CHECK_SIZE() macros. */ +#define TPM2B_AUTH_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_AUTH, buffer, size) +#define TPM2B_DATA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_DATA, buffer, size) +#define TPM2B_DIGEST_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_DIGEST, buffer, size) +#define TPM2B_ECC_PARAMETER_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_ECC_PARAMETER, buffer, size) +#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_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) +#define TPM2B_SENSITIVE_DATA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_SENSITIVE_DATA, buffer, size) +#define TPM2B_BUF_SIZE_STRUCT_MAKE(buf, size, struct_type, buffer_field, size_field) \ + _TPM2B_BUF_SIZE_STRUCT_MAKE(buf, size, UNIQ, struct_type, buffer_field, size_field) +#define _TPM2B_BUF_SIZE_STRUCT_MAKE(buf, size, uniq, struct_type, buffer_field, size_field) \ + ({ \ + typeof(buf) UNIQ_T(BUF, uniq) = (buf); \ + typeof(size) UNIQ_T(SIZE, uniq) = (size); \ + struct_type UNIQ_T(STRUCT, uniq) = { .size_field = UNIQ_T(SIZE, uniq), }; \ + assert(sizeof(UNIQ_T(STRUCT, uniq).buffer_field) >= (size_t) UNIQ_T(SIZE, uniq)); \ + if (UNIQ_T(BUF, uniq)) \ + memcpy(UNIQ_T(STRUCT, uniq).buffer_field, UNIQ_T(BUF, uniq), UNIQ_T(SIZE, uniq)); \ + UNIQ_T(STRUCT, uniq); \ + }) + +/* Check if the size will fit in the TPM2B struct buffer. Returns 0 if the size will fit, otherwise this logs + * a debug message and returns < 0. */ +#define TPM2B_AUTH_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_AUTH, buffer) +#define TPM2B_DATA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_DATA, buffer) +#define TPM2B_DIGEST_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_DIGEST, buffer) +#define TPM2B_ECC_PARAMETER_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_ECC_PARAMETER, buffer) +#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_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) +#define TPM2B_SENSITIVE_DATA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_SENSITIVE_DATA, buffer) +#define TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(size, struct_type, buffer_field) \ + _TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(size, UNIQ, struct_type, buffer_field) +#define _TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(size, uniq, struct_type, buffer_field) \ + ({ \ + size_t UNIQ_T(SIZE, uniq) = (size_t) (size); \ + size_t UNIQ_T(BUFSIZE, uniq) = sizeof_field(struct_type, buffer_field); \ + UNIQ_T(BUFSIZE, uniq) < UNIQ_T(SIZE, uniq) ? \ + log_debug_errno(SYNTHETIC_ERRNO(EINVAL), \ + "Size %zu larger than " #struct_type " buffer size %zu.", \ + UNIQ_T(SIZE, uniq), UNIQ_T(BUFSIZE, uniq)) : \ + 0; \ + }) + #else /* HAVE_TPM2 */ typedef struct {} Tpm2Context; typedef struct {} Tpm2Handle; +typedef struct {} Tpm2PCRValue; + +#define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) {} #endif /* HAVE_TPM2 */ int tpm2_list_devices(void); @@ -136,7 +241,8 @@ int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags); /* Default to PCR 7 only */ -#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7) +#define TPM2_PCR_INDEX_DEFAULT (7) +#define TPM2_PCR_MASK_DEFAULT INDEX_TO_MASK(uint32_t, TPM2_PCR_INDEX_DEFAULT) /* We want the helpers below to work also if TPM2 libs are not available, hence define these four defines if * they are missing. */ @@ -164,6 +270,8 @@ int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_p #define TPM2_ALG_RSA 0x1 #endif +int tpm2_hash_alg_to_size(uint16_t alg); + const char *tpm2_hash_alg_to_string(uint16_t alg); int tpm2_hash_alg_from_string(const char *alg); @@ -171,7 +279,6 @@ const char *tpm2_asym_alg_to_string(uint16_t alg); int tpm2_asym_alg_from_string(const char *alg); char *tpm2_pcr_mask_to_string(uint32_t mask); -int tpm2_pcr_mask_from_string(const char *arg, uint32_t *mask); typedef struct { uint32_t search_pcr_mask; @@ -220,7 +327,9 @@ enum { Tpm2Support tpm2_support(void); -int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask); +int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); +int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); +int tpm2_parse_pcr_argument_to_mask(const char *arg, uint32_t *mask); int tpm2_load_pcr_signature(const char *path, JsonVariant **ret); int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size); diff --git a/src/test/meson.build b/src/test/meson.build index 0f7dc8f9d2..1857c9c53b 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -167,7 +167,6 @@ simple_tests += files( 'test-sysctl-util.c', 'test-terminal-util.c', 'test-tmpfile-util.c', - 'test-tpm2.c', 'test-udev-util.c', 'test-uid-alloc-range.c', 'test-uid-range.c', @@ -353,6 +352,11 @@ executables += [ 'dependencies' : libdl, 'conditions' : ['ENABLE_NSS'], }, + test_template + { + 'sources' : files('test-openssl.c'), + 'dependencies' : libopenssl, + 'conditions' : ['HAVE_OPENSSL'], + }, test_template + { 'sources' : files('test-parse-util.c'), 'dependencies' : libm, @@ -415,6 +419,10 @@ executables += [ 'sources' : files('test-time-util.c'), 'timeout' : 120, }, + test_template + { + 'sources' : files('test-tpm2.c'), + 'dependencies' : libopenssl, + }, test_template + { 'sources' : files('test-utmp.c'), 'conditions' : ['ENABLE_UTMP'], diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c new file mode 100644 index 0000000000..7672c8959d --- /dev/null +++ b/src/test/test-openssl.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hexdecoct.h" +#include "openssl-util.h" +#include "tests.h" + +TEST(openssl_pkey_from_pem) { + DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; + assert_se(openssl_pkey_from_pem(key_ecc, key_ecc_len, &pkey_ecc) >= 0); + + _cleanup_free_ void *x = NULL, *y = NULL; + size_t x_len, y_len; + int curve_id; + assert_se(ecc_pkey_to_curve_x_y(pkey_ecc, &curve_id, &x, &x_len, &y, &y_len) >= 0); + assert_se(curve_id == NID_X9_62_prime256v1); + + DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de"); + assert_se(x_len == expected_x_len); + assert_se(memcmp(x, expected_x, x_len) == 0); + + DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5"); + assert_se(y_len == expected_y_len); + assert_se(memcmp(y, expected_y, y_len) == 0); + + DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; + assert_se(openssl_pkey_from_pem(key_rsa, key_rsa_len, &pkey_rsa) >= 0); + + _cleanup_free_ void *n = NULL, *e = NULL; + size_t n_len, e_len; + assert_se(rsa_pkey_to_n_e(pkey_rsa, &n, &n_len, &e, &e_len) >= 0); + + DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b"); + assert_se(n_len == expected_n_len); + assert_se(memcmp(n, expected_n, n_len) == 0); + + DEFINE_HEX_PTR(expected_e, "010001"); + assert_se(e_len == expected_e_len); + assert_se(memcmp(e, expected_e, e_len) == 0); +} + +TEST(rsa_pkey_n_e) { + DEFINE_HEX_PTR(n, "e3975a2124a7c9fe57752d106314ff62f6da731632eac221f1c0255bdcf2a34eeb21e3ab89ba8759ddad3b68be99463c7f03f3d004028a35e6f7c6596aeab2558d490f1e1c38aed2ff796bda8d6d55704eefb6ac55842dd6e606bb707f66acc02f0db2aed0dabab885bd0c850f1bdc8ac4b6bc1f74858db8ca2ab57a3d4217c091e9cd78727a2e36b8126ea629e81fecc69b0bea601000a6c0b749c5be16f53f4fa9f208a581d804234eb6526ba3fee9822d58d1ab9cac2761d7f630eb7ad6054dff0856d41aea219e1adfd87256aa1532202a070f4b1044e718d1f38bbc5a4b1fcb024f04afaafda5edeacfdf0d0bdf35c359acd059e3edb5024e588458f9b5"); + uint32_t e = htobe32(0x10001); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + assert_se(rsa_pkey_from_n_e(n, n_len, &e, sizeof(e), &pkey) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + assert_se(ctx); + assert_se(EVP_PKEY_verify_init(ctx) == 1); + + const char *msg = "this is a secret"; + DEFINE_HEX_PTR(sig, "14b53e0c6ad99a350c3d7811e8160f4ae03ad159815bb91bddb9735b833588df2eac221fbd3fc4ece0dd63bfaeddfdaf4ae67021e759f3638bc194836413414f54e8c4d01c9c37fa4488ea2ef772276b8a33822a53c97b1c35acfb4bc621cfb8fad88f0cf7d5491f05236886afbf9ed47f9469536482f50f74a20defa59d99676bed62a17b5eb98641df5a2f8080fa4b24f2749cc152fa65ba34c14022fcb27f1b36f52021950d7b9b6c3042c50b84cfb7d55a5f9235bfd58e1bf1f604eb93416c5fb5fd90cb68f1270dfa9daf67f52c604f62c2f2beee5e7e672b0e6e9833dd43dba99b77668540c850c9a81a5ea7aaf6297383e6135bd64572362333121fc7"); + assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + + DEFINE_HEX_PTR(invalid_sig, "1234"); + assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + + _cleanup_free_ void *n2 = NULL, *e2 = NULL; + size_t n2_size, e2_size; + assert_se(rsa_pkey_to_n_e(pkey, &n2, &n2_size, &e2, &e2_size) >= 0); + assert_se(memcmp_nn(n, n_len, n2, n2_size) == 0); + assert_se(e2_size <= sizeof(uint32_t)); + assert_se(memcmp(&((uint8_t*) &e)[sizeof(uint32_t) - e2_size], e2, e2_size) == 0); +} + +TEST(ecc_pkey_curve_x_y) { + int curveid = NID_X9_62_prime256v1; + DEFINE_HEX_PTR(x, "2830d2c8f65d3efbef12303b968b91692f8bd04045dcb8a9656374e4ae61d818"); + DEFINE_HEX_PTR(y, "8a80750f76729defdcc2a4bc1a91c22e60109dd6e1ffde634a650a20bab172e9"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + assert_se(ecc_pkey_from_curve_x_y(curveid, x, x_len, y, y_len, &pkey) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + assert_se(ctx); + assert_se(EVP_PKEY_verify_init(ctx) == 1); + + const char *msg = "this is a secret"; + DEFINE_HEX_PTR(sig, "3045022100f6ca10f7ed57a020679899b26dd5ac5a1079265885e2a6477f527b6a3f02b5ca02207b550eb3e7b69360aff977f7f6afac99c3f28266b6c5338ce373f6b59263000a"); + assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + + DEFINE_HEX_PTR(invalid_sig, "1234"); + assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + + _cleanup_free_ void *x2 = NULL, *y2 = NULL; + size_t x2_size, y2_size; + int curveid2; + assert_se(ecc_pkey_to_curve_x_y(pkey, &curveid2, &x2, &x2_size, &y2, &y2_size) >= 0); + assert_se(curveid == curveid2); + assert_se(memcmp_nn(x, x_len, x2, x2_size) == 0); + assert_se(memcmp_nn(y, y_len, y2, y2_size) == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index 9eb9085a59..7e65396b14 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -4,39 +4,6 @@ #include "tpm2-util.h" #include "tests.h" -static void test_tpm2_pcr_mask_from_string_one(const char *s, uint32_t mask, int ret) { - uint32_t m; - - assert_se(tpm2_pcr_mask_from_string(s, &m) == ret); - - if (ret >= 0) - assert_se(m == mask); -} - -TEST(tpm2_mask_from_string) { - test_tpm2_pcr_mask_from_string_one("", 0, 0); - test_tpm2_pcr_mask_from_string_one("0", 1, 0); - test_tpm2_pcr_mask_from_string_one("1", 2, 0); - test_tpm2_pcr_mask_from_string_one("0,1", 3, 0); - test_tpm2_pcr_mask_from_string_one("0+1", 3, 0); - test_tpm2_pcr_mask_from_string_one("0-1", 0, -EINVAL); - test_tpm2_pcr_mask_from_string_one("0,1,2", 7, 0); - test_tpm2_pcr_mask_from_string_one("0+1+2", 7, 0); - test_tpm2_pcr_mask_from_string_one("0+1,2", 7, 0); - test_tpm2_pcr_mask_from_string_one("0,1+2", 7, 0); - test_tpm2_pcr_mask_from_string_one("0,2", 5, 0); - test_tpm2_pcr_mask_from_string_one("0+2", 5, 0); - test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL); - test_tpm2_pcr_mask_from_string_one("7+application-support", 8388736, 0); - test_tpm2_pcr_mask_from_string_one("8+boot-loader-code", 272, 0); - test_tpm2_pcr_mask_from_string_one("6+boot-loader-code,44", 0, -EINVAL); - test_tpm2_pcr_mask_from_string_one("7,shim-policy,4", 16528, 0); - test_tpm2_pcr_mask_from_string_one("sysexts,shim-policy+kernel-boot", 26624, 0); - test_tpm2_pcr_mask_from_string_one("sysexts,shim+kernel-boot", 0, -EINVAL); - test_tpm2_pcr_mask_from_string_one("sysexts+17+23", 8527872, 0); - test_tpm2_pcr_mask_from_string_one("debug+24", 0, -EINVAL); -} - TEST(pcr_index_from_string) { assert_se(pcr_index_from_string("platform-code") == 0); assert_se(pcr_index_from_string("0") == 0); @@ -158,9 +125,7 @@ static void verify_tpms_pcr_selection(TPMS_PCR_SELECTION *s, uint32_t mask, TPMI assert_se(s->pcrSelect[2] == ((mask >> 16) & 0xff)); assert_se(s->pcrSelect[3] == 0); - uint32_t m = POISON_U32; - tpm2_tpms_pcr_selection_to_mask(s, &m); - assert_se(m == mask); + assert_se(tpm2_tpms_pcr_selection_to_mask(s) == mask); } static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION s[], size_t count) { @@ -168,10 +133,8 @@ static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION for (size_t i = 0; i < count; i++) { assert_tpms_pcr_selection_eq(&s[i], &l->pcrSelections[i]); - uint32_t mask = POISON_U32; TPMI_ALG_HASH hash = l->pcrSelections[i].hash; - assert_se(tpm2_tpml_pcr_selection_to_mask(l, hash, &mask) == 0); - verify_tpms_pcr_selection(&l->pcrSelections[i], mask, hash); + verify_tpms_pcr_selection(&l->pcrSelections[i], tpm2_tpml_pcr_selection_to_mask(l, hash), hash); } } @@ -467,21 +430,18 @@ static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { h = hexmem(digest->buffer, digest->size); assert_se(h); - return streq(expect, h); + return strcaseeq(expect, h); } -static void digest_init_sha256(TPM2B_DIGEST *digest, const char *hash) { - _cleanup_free_ void *h = NULL; - size_t s = 0; - - assert_se(strlen(hash) == SHA256_DIGEST_SIZE * 2); +static void digest_init(TPM2B_DIGEST *digest, const char *hash) { assert_se(strlen(hash) <= sizeof(digest->buffer) * 2); - assert_se(unhexmem(hash, strlen(hash), &h, &s) == 0); - assert_se(s == SHA256_DIGEST_SIZE); + DEFINE_HEX_PTR(h, hash); - memcpy_safe(digest->buffer, h, s); - digest->size = s; + /* Make sure the length matches a known hash algorithm */ + assert_se(IN_SET(h_len, TPM2_SHA1_DIGEST_SIZE, TPM2_SHA256_DIGEST_SIZE, TPM2_SHA384_DIGEST_SIZE, TPM2_SHA512_DIGEST_SIZE)); + + *digest = TPM2B_DIGEST_MAKE(h, h_len); assert_se(digest_check(digest, hash)); } @@ -489,11 +449,11 @@ static void digest_init_sha256(TPM2B_DIGEST *digest, const char *hash) { TEST(digest_many) { TPM2B_DIGEST d, d0, d1, d2, d3, d4; - digest_init_sha256(&d0, "0000000000000000000000000000000000000000000000000000000000000000"); - digest_init_sha256(&d1, "17b7703d9d00776310ba032e88c1a8c2a9c630ebdd799db622f6631530789175"); - digest_init_sha256(&d2, "12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251"); - digest_init_sha256(&d3, "c3a65887fedd3fb4f5d0047e906dff830bcbd1293160909eb4b05f485e7387ad"); - digest_init_sha256(&d4, "6491fb4bc08fc0b2ef47fc63db57e249917885e69d8c0d99667df83a59107a33"); + digest_init(&d0, "0000000000000000000000000000000000000000000000000000000000000000"); + digest_init(&d1, "17b7703d9d00776310ba032e88c1a8c2a9c630ebdd799db622f6631530789175"); + digest_init(&d2, "12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251"); + digest_init(&d3, "c3a65887fedd3fb4f5d0047e906dff830bcbd1293160909eb4b05f485e7387ad"); + digest_init(&d4, "6491fb4bc08fc0b2ef47fc63db57e249917885e69d8c0d99667df83a59107a33"); /* tpm2_digest_init, tpm2_digest_rehash */ d = (TPM2B_DIGEST){ .size = 1, .buffer = { 2, }, }; @@ -557,7 +517,192 @@ TEST(digest_many) { assert_se(digest_check(&d, "02ecb0628264235111e0053e271092981c8b15d59cd46617836bee3149a4ecb0")); } -static void tpm2b_public_init(TPM2B_PUBLIC *public) { +static void check_parse_pcr_argument( + const char *arg, + const Tpm2PCRValue *prev_values, + size_t n_prev_values, + const Tpm2PCRValue *expected_values, + size_t n_expected_values) { + + _cleanup_free_ Tpm2PCRValue *values = NULL; + size_t n_values = 0; + + if (n_prev_values > 0) { + assert_se(GREEDY_REALLOC_APPEND(values, n_values, prev_values, n_prev_values)); + assert_se(tpm2_parse_pcr_argument_append(arg, &values, &n_values) == 0); + } else + assert_se(tpm2_parse_pcr_argument(arg, &values, &n_values) == 0); + + assert_se(n_values == n_expected_values); + for (size_t i = 0; i < n_values; i++) { + const Tpm2PCRValue *v = &values[i], *e = &expected_values[i]; + //tpm2_log_debug_pcr_value(e, "Expected value"); + //tpm2_log_debug_pcr_value(v, "Actual value"); + + assert_se(v->index == e->index); + assert_se(v->hash == e->hash); + assert_se(v->value.size == e->value.size); + assert_se(memcmp(v->value.buffer, e->value.buffer, e->value.size) == 0); + } + + size_t hash_count; + assert_se(tpm2_pcr_values_hash_count(expected_values, n_expected_values, &hash_count) == 0); + if (hash_count == 1) { + uint32_t mask = UINT32_MAX, expected_mask = 0; + + if (n_prev_values > 0) + assert_se(tpm2_pcr_values_to_mask(prev_values, n_prev_values, prev_values[0].hash, &mask) == 0); + + assert_se(tpm2_pcr_values_to_mask(expected_values, n_expected_values, expected_values[0].hash, &expected_mask) == 0); + + assert_se(tpm2_parse_pcr_argument_to_mask(arg, &mask) == 0); + assert_se(mask == expected_mask); + } + + size_t old_n_values = n_values; + assert_se(tpm2_parse_pcr_argument_append("", &values, &n_values) == 0); + assert_se(values); + assert_se(n_values == old_n_values); +} + +static void check_parse_pcr_argument_to_mask(const char *arg, int mask) { + uint32_t m = 0; + int r = tpm2_parse_pcr_argument_to_mask(arg, &m); + + if (mask < 0) + assert_se(mask == r); + else + assert_se((uint32_t) mask == m); +} + +TEST(parse_pcr_argument) { + _cleanup_free_ Tpm2PCRValue *t0p = NULL; + size_t n_t0p; + assert_se(tpm2_parse_pcr_argument("", &t0p, &n_t0p) == 0); + assert_se(n_t0p == 0); + assert_se(tpm2_parse_pcr_argument_append("", &t0p, &n_t0p) == 0); + assert_se(n_t0p == 0); + uint32_t m0 = 0xf; + assert_se(tpm2_parse_pcr_argument_to_mask("", &m0) == 0); + assert_se(m0 == 0); + assert_se(tpm2_parse_pcr_argument_to_mask("", &m0) == 0); + assert_se(m0 == 0); + + Tpm2PCRValue t1[] = { + TPM2_PCR_VALUE_MAKE(0, 0, {}), + TPM2_PCR_VALUE_MAKE(4, 0, {}), + TPM2_PCR_VALUE_MAKE(7, 0, {}), + TPM2_PCR_VALUE_MAKE(11, 0, {}), + }; + check_parse_pcr_argument("0,4,7,11", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("11,4,7,0", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("7,4,0,11", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("11,7,4,0", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("0+4+7+11", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("0,4+7,11", NULL, 0, t1, ELEMENTSOF(t1)); + + Tpm2PCRValue t2[] = { + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA1, {}), + }; + check_parse_pcr_argument("0:sha1,4,7,11", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("11,4,7,0:sha1", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("7,4:sha1,0,11", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("0:sha1,4:sha1,7:sha1,11:sha1", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("0:sha1+4:sha1,11:sha1+7:sha1", NULL, 0, t2, ELEMENTSOF(t2)); + + Tpm2PCRValue t3[] = { + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA1, {}), + }; + check_parse_pcr_argument("1,2,3,12", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("12,2,3,1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1,2,3,12:sha1", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1,2,3,12:sha1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2,3,12", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2,3,12", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2:sha1,3:sha1,12:sha1", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2:sha1,3:sha1,12:sha1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + + TPM2B_DIGEST d4; + digest_init(&d4, "FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2"); + Tpm2PCRValue t4[] = { + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA256, d4), + TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, {}), + }; + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("12,2,3,1:sha256=FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("12,2,3,1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12:SHA256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2:sha256,3:sha256,12:sha256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2:sha256,3:sha256,12:sha256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + + TPM2B_DIGEST d5; + digest_init(&d5, "0F21EADB7F27377668E3C8069BE88D116491FBEE"); + Tpm2PCRValue t5[] = { + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA1, d5), + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA256, d4), + TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(5, TPM2_ALG_SHA384, {}), + TPM2_PCR_VALUE_MAKE(6, TPM2_ALG_SHA512, {}), + }; + check_parse_pcr_argument("0,1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,1:sha1=0F21EADB7F27377668E3C8069BE88D116491FBEE,2,3,4,7,11,12,5:sha384,6:sha512", NULL, 0, t5, ELEMENTSOF(t5)); + check_parse_pcr_argument("1:sha1=0F21EADB7F27377668E3C8069BE88D116491FBEE,6:sha512,5:sha384", t4, ELEMENTSOF(t4), t5, ELEMENTSOF(t5)); + + Tpm2PCRValue *v = NULL; + size_t n_v = 0; + assert_se(tpm2_parse_pcr_argument("1,100", &v, &n_v) < 0); + assert_se(tpm2_parse_pcr_argument("1,2=123456abc", &v, &n_v) < 0); + assert_se(tpm2_parse_pcr_argument("1,2:invalid", &v, &n_v) < 0); + assert_se(tpm2_parse_pcr_argument("1:sha1=invalid", &v, &n_v) < 0); + assert_se(v == NULL); + assert_se(n_v == 0); + + check_parse_pcr_argument_to_mask("", 0x0); + check_parse_pcr_argument_to_mask("0", 0x1); + check_parse_pcr_argument_to_mask("1", 0x2); + check_parse_pcr_argument_to_mask("0,1", 0x3); + check_parse_pcr_argument_to_mask("0+1", 0x3); + check_parse_pcr_argument_to_mask("0-1", -EINVAL); + check_parse_pcr_argument_to_mask("foo", -EINVAL); + check_parse_pcr_argument_to_mask("0,1,2", 0x7); + check_parse_pcr_argument_to_mask("0+1+2", 0x7); + check_parse_pcr_argument_to_mask("0+1,2", 0x7); + check_parse_pcr_argument_to_mask("0,1+2", 0x7); + check_parse_pcr_argument_to_mask("0,2", 0x5); + check_parse_pcr_argument_to_mask("0+2", 0x5); + check_parse_pcr_argument_to_mask("7+application-support", 0x800080); + check_parse_pcr_argument_to_mask("8+boot-loader-code", 0x110); + check_parse_pcr_argument_to_mask("7,shim-policy,4", 0x4090); + check_parse_pcr_argument_to_mask("sysexts,shim-policy+kernel-boot", 0x6800); + check_parse_pcr_argument_to_mask("sysexts,shim+kernel-boot", -EINVAL); + check_parse_pcr_argument_to_mask("sysexts+17+23", 0x822000); + check_parse_pcr_argument_to_mask("6+boot-loader-code,44", -EINVAL); + check_parse_pcr_argument_to_mask("debug+24", -EINVAL); +} + +static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { TPMT_PUBLIC tpmt = { .type = TPM2_ALG_RSA, .nameAlg = TPM2_ALG_SHA256, @@ -573,37 +718,155 @@ static void tpm2b_public_init(TPM2B_PUBLIC *public) { }, }; - const char *key = "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"; - _cleanup_free_ void *mem = NULL; - size_t len = 0; - assert_se(unhexmem(key, strlen(key), &mem, &len) == 0); - assert_se(len <= sizeof(tpmt.unique.rsa.buffer)); - memcpy_safe(tpmt.unique.rsa.buffer, mem, len); - tpmt.unique.rsa.size = len; + DEFINE_HEX_PTR(key, rsa_n); + tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(key, key_len); + public->size = sizeof(tpmt); public->publicArea = tpmt; } +static void tpm2b_public_ecc_init(TPM2B_PUBLIC *public, TPMI_ECC_CURVE curve, const char *x, const char *y) { + TPMT_PUBLIC tpmt = { + .type = TPM2_ALG_ECC, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters.eccDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + .curveID = curve, + .kdf.scheme = TPM2_ALG_NULL, + }, + }; + + DEFINE_HEX_PTR(buf_x, x); + tpmt.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(buf_x, buf_x_len); + + DEFINE_HEX_PTR(buf_y, y); + tpmt.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(buf_y, buf_y_len); + + public->size = sizeof(tpmt); + public->publicArea = tpmt; +} + +#if HAVE_OPENSSL +TEST(tpm2b_public_to_openssl_pkey) { + DEFINE_HEX_PTR(msg, "edc64c6523778961fe9ba03ab7d624b27ca1dd5b01e7734cc6c891d50db04269"); + TPM2B_PUBLIC public; + + /* RSA */ + tpm2b_public_rsa_init(&public, "d71cff5bba2173f0434a389171048e7da8cf8409b892c62946481cc383089bc754324620967fea3d00a02a717cdda4bfe1525ad957d294b88434e0a3933e86fb40f234e4935fd2ba27eb1d21da87efa466b74eb4ad18d26059904643441cf402ee933d138a2151f40459c49d87fef59e2cb822768b2d8689a9b58f82bf9a37e70693f2b2d40dfa388d365c1b1f029a14c4fc8dadb68978ef377d20ff2ca24e7078464c705eab42f531557c9c6dc0df66b506d0c26ef604f8110c64867099267453c71871e7ed22505a09daf102afc34355209ca7680eccc0ed368d148f402fa58cbb6c9d52351f535f09e4e24ad805e149f130edaa2f5e7efed3a4d2d03adb85"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; + assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_rsa) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = EVP_PKEY_CTX_new((EVP_PKEY*) pkey_rsa, NULL); + assert_se(ctx_rsa); + assert_se(EVP_PKEY_verify_init(ctx_rsa) == 1); + assert_se(EVP_PKEY_CTX_set_signature_md(ctx_rsa, EVP_sha256()) > 0); + + DEFINE_HEX_PTR(sig_rsa, "9f70a9e68911be3ec464cae91126328307bf355872127e042d6c61e0a80982872c151033bcf727abfae5fc9500c923120011e7ef4aa5fc690a59a034697b6022c141b4b209e2df6f4b282288cd9181073fbe7158ce113c79d87623423c1f3996ff931e59cc91db74f8e8656215b1436fc93ddec0f1f8fa8510826e674b250f047e6cba94c95ff98072a286baca94646b577974a1e00d56c21944e38960d8ee90511a2f938e5cf1ac7b7cc7ff8e3ac001d321254d3e4f988b90e9f6f873c26ecd0a12a626b3474833cdbb9e9f793238f6c97ee5b75a1a89bb7a7858d34ecfa6d34ac58d95085e6c4fbbebd47a4364be2725c2c6b3fa15d916f3c0b62a66fe76ae"); + assert_se(EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); + + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "6fc0ecf3645c673ab7e86d1ec5b315afb950257c5f68ab23296160006711fac2", "8dd2ef7a2c9ecede91493ba98c8fb3f893aff325c6a1e0f752c657b2d6ca1413"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; + assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_ecc) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = EVP_PKEY_CTX_new((EVP_PKEY*) pkey_ecc, NULL); + assert_se(ctx_ecc); + assert_se(EVP_PKEY_verify_init(ctx_ecc) == 1); + + DEFINE_HEX_PTR(sig_ecc, "304602210092447ac0b5b32e90923f79bb4aba864b9c546a9900cf193a83243d35d189a2110221009a8b4df1dfa85e225eff9c606694d4d205a7a3968c9552f50bc2790209a90001"); + assert_se(EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); +} + +static void get_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + TPM2B_PUBLIC p1 = {}, p2 = {}; + + assert(pem); + assert(ret); + + assert_se(openssl_pkey_from_pem(pem, pem_size, &pkey) >= 0); + assert_se(tpm2_tpm2b_public_from_openssl_pkey(pkey, &p1) >= 0); + assert_se(tpm2_tpm2b_public_from_pem(pem, pem_size, &p2) >= 0); + assert_se(memcmp_nn(&p1, sizeof(p1), &p2, sizeof(p2)) == 0); + + *ret = p1; +} + +static void check_tpm2b_public_fingerprint(const TPM2B_PUBLIC *public, const char *hexfp) { + DEFINE_HEX_PTR(expected, hexfp); + _cleanup_free_ void *fp = NULL; + size_t fp_size; + + assert_se(tpm2_tpm2b_public_to_fingerprint(public, &fp, &fp_size) >= 0); + assert_se(memcmp_nn(fp, fp_size, expected, expected_len) == 0); +} + +TEST(tpm2b_public_from_openssl_pkey) { + TPM2B_PUBLIC public; + TPMT_PUBLIC *p = &public.publicArea; + + DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + get_tpm2b_public_from_pem(key_ecc, key_ecc_len, &public); + + assert_se(p->type == TPM2_ALG_ECC); + assert_se(p->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); + + DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de"); + assert_se(memcmp_nn(p->unique.ecc.x.buffer, p->unique.ecc.x.size, expected_x, expected_x_len) == 0); + + DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5"); + assert_se(memcmp_nn(p->unique.ecc.y.buffer, p->unique.ecc.y.size, expected_y, expected_y_len) == 0); + + check_tpm2b_public_fingerprint(&public, "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c"); + + DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + get_tpm2b_public_from_pem(key_rsa, key_rsa_len, &public); + + DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b"); + assert_se(p->unique.rsa.size == expected_n_len); + assert_se(memcmp(p->unique.rsa.buffer, expected_n, expected_n_len) == 0); + + assert_se(p->parameters.rsaDetail.keyBits == expected_n_len * 8); + + assert_se(p->parameters.rsaDetail.exponent == 0); + + check_tpm2b_public_fingerprint(&public, "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9"); +} +#endif + +static void check_name(const TPM2B_NAME *name, const char *expect) { + assert_se(name->size == SHA256_DIGEST_SIZE + 2); + + DEFINE_HEX_PTR(e, expect); + assert_se(name->size == e_len); + assert_se(memcmp(name->name, e, e_len) == 0); +} + TEST(calculate_name) { TPM2B_PUBLIC public; TPM2B_NAME name; - tpm2b_public_init(&public); - assert_se(tpm2_calculate_name(&public.publicArea, &name) == 0); - assert_se(name.size == SHA256_DIGEST_SIZE + 2); + /* RSA */ + tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); + assert_se(tpm2_calculate_name(&public.publicArea, &name) >= 0); + check_name(&name, "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"); - const char *expect = "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"; - _cleanup_free_ char *h = hexmem(name.name, name.size); - assert_se(h); - - assert_se(strlen(expect) == strlen(h)); - assert_se(streq(expect, h)); + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "238e02ee4fd5598add6b502429f1815418515e4b0d6551c8e816b38cb15451d1", "70c2d491769775ec43ccd5a571c429233e9d30cf0f486c2e01acd6cb32ba93b6"); + assert_se(tpm2_calculate_name(&public.publicArea, &name) >= 0); + check_name(&name, "000b302787187ba19c82011c987bd2dcdbb652b3a543ccc5cb0b49c33d4caae604a6"); } TEST(calculate_policy_auth_value) { TPM2B_DIGEST d; - digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); assert_se(tpm2_calculate_policy_auth_value(&d) == 0); assert_se(digest_check(&d, "8fcd2169ab92694e0c633f1ab772842b8241bbc20288981fc7ac1eddc1fddb0e")); assert_se(tpm2_calculate_policy_auth_value(&d) == 0); @@ -614,59 +877,76 @@ TEST(calculate_policy_authorize) { TPM2B_PUBLIC public; TPM2B_DIGEST d; - tpm2b_public_init(&public); - digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + /* RSA */ + tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); + + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "423a89da6f0998f510489ab9682706e762031ef8f9faef2a185eff67065a187e", "996f73291670cef9e303d6cd9fa19ddf2c9c1fb1e283324ca9acca07c405c8d0"); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628")); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628")); } TEST(calculate_policy_pcr) { - TPML_PCR_SELECTION pcr_selection; - TPM2B_DIGEST pcr_values[16]; - TPM2B_DIGEST d; - uint32_t pcr_mask; + TPM2B_DIGEST d, dN[16]; - digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); - pcr_mask = (1<<4) | (1<<7) | (1<<8); - tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); - digest_init_sha256(&pcr_values[0], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); - digest_init_sha256(&pcr_values[1], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); - digest_init_sha256(&pcr_values[2], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); - assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 3, &d) == 0); + digest_init(&dN[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); + digest_init(&dN[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); + digest_init(&dN[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); + digest_init(&dN[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); + digest_init(&dN[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); + digest_init(&dN[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); + digest_init(&dN[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); + digest_init(&dN[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); + digest_init(&dN[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); + digest_init(&dN[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); + digest_init(&dN[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); + digest_init(&dN[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); + digest_init(&dN[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); + digest_init(&dN[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); + digest_init(&dN[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); + digest_init(&dN[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); + + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + Tpm2PCRValue v1[] = { + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, dN[4]), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, dN[7]), + TPM2_PCR_VALUE_MAKE(8, TPM2_ALG_SHA256, dN[8]), + }; + assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); assert_se(digest_check(&d, "76532a0e16f7e6bf6b02918c11f75d99d729fab0cc81d0df2c4284a2c4fe6e05")); - - pcr_mask = (1<<4) | (1<<7) | (1<<8); - tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); - digest_init_sha256(&pcr_values[0], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); - digest_init_sha256(&pcr_values[1], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); - digest_init_sha256(&pcr_values[2], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); - assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 3, &d) == 0); + assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); assert_se(digest_check(&d, "97e64bcabb64c1fa4b726528644926c8029f5b4458b0575c98c04fe225629a0b")); - digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); - pcr_mask = 0xffff; - tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); - digest_init_sha256(&pcr_values[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); - digest_init_sha256(&pcr_values[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); - digest_init_sha256(&pcr_values[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); - digest_init_sha256(&pcr_values[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); - digest_init_sha256(&pcr_values[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); - digest_init_sha256(&pcr_values[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); - digest_init_sha256(&pcr_values[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); - digest_init_sha256(&pcr_values[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); - digest_init_sha256(&pcr_values[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); - digest_init_sha256(&pcr_values[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); - digest_init_sha256(&pcr_values[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); - digest_init_sha256(&pcr_values[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); - digest_init_sha256(&pcr_values[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); - digest_init_sha256(&pcr_values[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); - digest_init_sha256(&pcr_values[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); - digest_init_sha256(&pcr_values[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); - assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 16, &d) == 0); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + Tpm2PCRValue v2[] = { + TPM2_PCR_VALUE_MAKE( 0, TPM2_ALG_SHA256, dN[ 0]), + TPM2_PCR_VALUE_MAKE( 1, TPM2_ALG_SHA256, dN[ 1]), + TPM2_PCR_VALUE_MAKE( 2, TPM2_ALG_SHA256, dN[ 2]), + TPM2_PCR_VALUE_MAKE( 3, TPM2_ALG_SHA256, dN[ 3]), + TPM2_PCR_VALUE_MAKE( 4, TPM2_ALG_SHA256, dN[ 4]), + TPM2_PCR_VALUE_MAKE( 5, TPM2_ALG_SHA256, dN[ 5]), + TPM2_PCR_VALUE_MAKE( 6, TPM2_ALG_SHA256, dN[ 6]), + TPM2_PCR_VALUE_MAKE( 7, TPM2_ALG_SHA256, dN[ 7]), + TPM2_PCR_VALUE_MAKE( 8, TPM2_ALG_SHA256, dN[ 8]), + TPM2_PCR_VALUE_MAKE( 9, TPM2_ALG_SHA256, dN[ 9]), + TPM2_PCR_VALUE_MAKE(10, TPM2_ALG_SHA256, dN[10]), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, dN[11]), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, dN[12]), + TPM2_PCR_VALUE_MAKE(13, TPM2_ALG_SHA256, dN[13]), + TPM2_PCR_VALUE_MAKE(14, TPM2_ALG_SHA256, dN[14]), + TPM2_PCR_VALUE_MAKE(15, TPM2_ALG_SHA256, dN[15]), + }; + assert_se(tpm2_calculate_policy_pcr(v2, ELEMENTSOF(v2), &d) == 0); assert_se(digest_check(&d, "22be4f1674f792d6345cea9427701068f0e8d9f42755dcc0e927e545a68f9c13")); - assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 16, &d) == 0); + assert_se(tpm2_calculate_policy_pcr(v2, ELEMENTSOF(v2), &d) == 0); assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); } diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh index f01642f71a..dd8cdb7efa 100755 --- a/test/units/testsuite-70.sh +++ b/test/units/testsuite-70.sh @@ -93,7 +93,43 @@ PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 "$img # Check with wrong PCR 0 tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 -"$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1 && exit 1 +(! "$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1) + +if tpm_has_pcr sha256 12; then + # Enroll using an explict PCR value (that does match current PCR value) + systemd-cryptenroll --wipe-slot=tpm2 "$img" + EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$img" + "$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1 + "$SD_CRYPTSETUP" detach test-volume + + # Same as above plus more PCRs without the value or alg specified + systemd-cryptenroll --wipe-slot=tpm2 "$img" + EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$img" + "$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1 + "$SD_CRYPTSETUP" detach test-volume + + # Same as above plus more PCRs with hash alg specified but hash value not specified + systemd-cryptenroll --wipe-slot=tpm2 "$img" + EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$img" + "$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1 + "$SD_CRYPTSETUP" detach test-volume + + # Now the interesting part, enrolling using a hash value that doesn't match the current PCR value + systemd-cryptenroll --wipe-slot=tpm2 "$img" + tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 + CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + EXPECTED_PCR_VALUE=$(cat /tmp/pcr.dat /tmp/pcr.dat | openssl dgst -sha256 -r | cut -d ' ' -f 1) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$img" + (! "$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1) + tpm2_pcrextend "12:sha256=$CURRENT_PCR_VALUE" + "$SD_CRYPTSETUP" attach test-volume "$img" - tpm2-device=auto,headless=1 + "$SD_CRYPTSETUP" detach test-volume + + rm -f /tmp/pcr.dat +fi rm -f "${img:?}"