From 7ff4ae3dac16a9717bfd74df6f12e336115aa491 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Wed, 28 Jun 2023 11:46:31 -0400 Subject: [PATCH] tpm2: add tpm2_calculate_seal() and helper functions Add functions to calculate a sealed secret object. (cherry picked from commit 0a7874ad55c9cd9114292186da74ba0fd91b8436) Related: RHEL-16182 --- src/shared/tpm2-util.c | 818 ++++++++++++++++++++++++++++++++++++++++- src/shared/tpm2-util.h | 7 + 2 files changed, 816 insertions(+), 9 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 2e2a3f5fb0..e5fc8a72a7 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -41,6 +41,7 @@ static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flush static void (*sym_Esys_Free)(void *ptr) = NULL; static TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData) = NULL; static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; +static TSS2_RC (*sym_Esys_Import)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DATA *encryptionKey, const TPM2B_PUBLIC *objectPublic, const TPM2B_PRIVATE *duplicate, const TPM2B_ENCRYPTED_SECRET *inSymSeed, const TPMT_SYM_DEF_OBJECT *symmetricAlg, TPM2B_PRIVATE **outPrivate) = NULL; static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL; @@ -72,16 +73,24 @@ static TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, static TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPM2_HANDLE_Marshal)(TPM2_HANDLE src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPM2B_DIGEST_Marshal)(TPM2B_DIGEST const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal)(TPM2B_ENCRYPTED_SECRET const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_ENCRYPTED_SECRET *dest) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL; +static TSS2_RC (*sym_Tss2_MU_TPMS_ECC_POINT_Marshal)(TPMS_ECC_POINT const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +static TSS2_RC (*sym_Tss2_MU_UINT32_Marshal)(UINT32 src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; @@ -99,6 +108,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_Free), DLSYM_ARG(Esys_GetCapability), DLSYM_ARG(Esys_GetRandom), + DLSYM_ARG(Esys_Import), DLSYM_ARG(Esys_Initialize), DLSYM_ARG(Esys_Load), DLSYM_ARG(Esys_LoadExternal), @@ -145,16 +155,24 @@ int dlopen_tpm2(void) { return dlopen_many_sym_or_warn( &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2_HANDLE_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_DIGEST_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal), + DLSYM_ARG(Tss2_MU_TPM2B_NAME_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal), DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal), + DLSYM_ARG(Tss2_MU_TPM2B_SENSITIVE_Marshal), DLSYM_ARG(Tss2_MU_TPML_PCR_SELECTION_Marshal), DLSYM_ARG(Tss2_MU_TPMS_NV_PUBLIC_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal), + DLSYM_ARG(Tss2_MU_TPMS_ECC_POINT_Marshal), DLSYM_ARG(Tss2_MU_TPMT_HA_Marshal), - DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); + DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_UINT32_Marshal)); } void Esys_Freep(void *p) { @@ -2336,6 +2354,48 @@ int tpm2_create_loaded( return 0; } +static int tpm2_import( + Tpm2Context *c, + const Tpm2Handle *parent, + const Tpm2Handle *session, + const TPM2B_PUBLIC *public, + const TPM2B_PRIVATE *private, + const TPM2B_ENCRYPTED_SECRET *seed, + const TPM2B_DATA *encryption_key, + const TPMT_SYM_DEF_OBJECT *symmetric, + TPM2B_PRIVATE **ret_private) { + + TSS2_RC rc; + + assert(c); + assert(parent); + assert(!!encryption_key == !!symmetric); + assert(public); + assert(private); + assert(seed); + assert(ret_private); + + log_debug("Importing key into TPM."); + + rc = sym_Esys_Import( + c->esys_context, + parent->esys_handle, + session ? session->esys_handle : ESYS_TR_PASSWORD, + ESYS_TR_NONE, + ESYS_TR_NONE, + encryption_key, + public, + private, + seed, + symmetric ?: &(TPMT_SYM_DEF_OBJECT){ .algorithm = TPM2_ALG_NULL, }, + ret_private); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to import key into TPM: %s", sym_Tss2_RC_Decode(rc)); + + return 0; +} + /* 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 @@ -3157,7 +3217,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) /* Get the "name" of a key from the TPM. * - * The "name" of a key is explained above in tpm2_calculate_name(). + * The "name" of a key is explained above in tpm2_calculate_pubkey_name(). * * The handle must reference a key already present in the TPM. It may be either a public key only, or a * public/private keypair. */ @@ -3825,12 +3885,14 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r #endif } -/* Marshal the public and private objects into a single nonstandard 'blob'. This is not a (publicly) standard - * format, this is specific to how we currently store the sealed object. This 'blob' can be unmarshalled by +/* Marshal the public, private, and seed objects into a single nonstandard 'blob'. The public and private + * objects are required, while the seed is optional. This is not a (publicly) standard format, this is + * specific to how we currently store the sealed object. This 'blob' can be unmarshalled by * tpm2_unmarshal_blob(). */ static int tpm2_marshal_blob( const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, + const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size) { @@ -3842,6 +3904,8 @@ static int tpm2_marshal_blob( assert(ret_blob_size); size_t max_size = sizeof(*private) + sizeof(*public); + if (seed) + max_size += sizeof(*seed); _cleanup_free_ void *blob = malloc(max_size); if (!blob) @@ -3858,26 +3922,36 @@ static int tpm2_marshal_blob( return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + if (seed) { + rc = sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal(seed, blob, max_size, &blob_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal encrypted seed: %s", sym_Tss2_RC_Decode(rc)); + } + *ret_blob = TAKE_PTR(blob); *ret_blob_size = blob_size; return 0; } -/* Unmarshal the 'blob' into public and private objects. This is not a (publicly) standard format, this is - * specific to how we currently store the sealed object. This expects the 'blob' to have been created by +/* Unmarshal the 'blob' into public, private, and seed objects. The public and private objects are required + * in the 'blob', while the seed is optional. This is not a (publicly) standard format, this is specific to + * how we currently store the sealed object. This expects the 'blob' to have been created by * tpm2_marshal_blob(). */ static int tpm2_unmarshal_blob( const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, - TPM2B_PRIVATE *ret_private) { + TPM2B_PRIVATE *ret_private, + TPM2B_ENCRYPTED_SECRET *ret_seed) { TSS2_RC rc; assert(blob); assert(ret_public); assert(ret_private); + assert(ret_seed); TPM2B_PRIVATE private = {}; size_t offset = 0; @@ -3892,8 +3966,67 @@ static int tpm2_unmarshal_blob( return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); + TPM2B_ENCRYPTED_SECRET seed = {}; + if (blob_size > offset) { + rc = sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal(blob, blob_size, &offset, &seed); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unmarshal encrypted seed: %s", sym_Tss2_RC_Decode(rc)); + } + *ret_public = public; *ret_private = private; + *ret_seed = seed; + + return 0; +} + +/* Calculate a serialized handle. Once the upstream tpm2-tss library provides an api to do this, we can + * remove this function. The addition of this functionality in tpm2-tss may be tracked here: + * https://github.com/tpm2-software/tpm2-tss/issues/2575 */ +int tpm2_calculate_serialize( + TPM2_HANDLE handle, + const TPM2B_NAME *name, + const TPM2B_PUBLIC *public, + void **ret_serialized, + size_t *ret_serialized_size) { + + TSS2_RC rc; + + assert(name); + assert(public); + assert(ret_serialized); + assert(ret_serialized_size); + + size_t max_size = sizeof(TPM2_HANDLE) + sizeof(TPM2B_NAME) + sizeof(uint32_t) + sizeof(TPM2B_PUBLIC); + _cleanup_free_ void *serialized = malloc(max_size); + if (!serialized) + return log_oom_debug(); + + size_t serialized_size = 0; + rc = sym_Tss2_MU_TPM2_HANDLE_Marshal(handle, serialized, max_size, &serialized_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal tpm handle: %s", sym_Tss2_RC_Decode(rc)); + + rc = sym_Tss2_MU_TPM2B_NAME_Marshal(name, serialized, max_size, &serialized_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal name: %s", sym_Tss2_RC_Decode(rc)); + + /* This is defined (non-publicly) in the tpm2-tss source as IESYSC_KEY_RSRC, to a value of "1". */ + rc = sym_Tss2_MU_UINT32_Marshal(UINT32_C(1), serialized, max_size, &serialized_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal esys resource id: %s", sym_Tss2_RC_Decode(rc)); + + rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, serialized, max_size, &serialized_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public: %s", sym_Tss2_RC_Decode(rc)); + + *ret_serialized = TAKE_PTR(serialized); + *ret_serialized_size = serialized_size; return 0; } @@ -3957,6 +4090,654 @@ static int tpm2_deserialize( return 0; } +#if HAVE_OPENSSL + +/* KDFa() as defined by the TPM spec. */ +static int tpm2_kdfa( + TPMI_ALG_HASH hash_alg, + const void *key, + size_t key_len, + const char *label, + const void *context, + size_t context_len, + size_t bits, + void **ret_key, + size_t *ret_key_len) { + + int r; + + assert(key); + assert(label); + assert(context || context_len == 0); + assert(bits > 0); + assert(bits <= SIZE_MAX - 7); + assert(ret_key); + assert(ret_key_len); + + log_debug("Calculating KDFa()."); + + size_t len = DIV_ROUND_UP(bits, 8); + + const char *hash_alg_name = tpm2_hash_alg_to_string(hash_alg); + if (!hash_alg_name) + return -EOPNOTSUPP; + + _cleanup_free_ void *buf = NULL; + r = kdf_kb_hmac_derive( + "COUNTER", + hash_alg_name, + key, + key_len, + label, + strlen(label), + context, + context_len, + /* seed= */ NULL, + /* seed_len= */ 0, + len, + &buf); + if (r < 0) + return r; + + /* If the number of bits results in a partial byte, the TPM spec requires we zero the unrequested + * bits in the MSB (i.e. at index 0). From the spec Part 1 ("Architecture") section on Key + * Derivation Function, specifically KDFa(): + * + * "The implied return from this function is a sequence of octets with a length equal to (bits + 7) / + * 8. If bits is not an even multiple of 8, then the returned value occupies the least significant + * bits of the returned octet array, and the additional, high-order bits in the 0th octet are + * CLEAR. The unused bits of the most significant octet (MSO) are masked off and not shifted." */ + size_t partial = bits % 8; + if (partial > 0) + ((uint8_t*) buf)[0] &= 0xffu >> (8 - partial); + + *ret_key = TAKE_PTR(buf); + *ret_key_len = len; + + return 0; +} + +/* KDFe() as defined by the TPM spec. */ +static int tpm2_kdfe( + TPMI_ALG_HASH hash_alg, + const void *shared_secret, + size_t shared_secret_len, + const char *label, + const void *context_u, + size_t context_u_size, + const void *context_v, + size_t context_v_size, + size_t bits, + void **ret_key, + size_t *ret_key_len) { + + int r; + + assert(shared_secret); + assert(label); + assert(context_u); + assert(context_v); + assert(bits > 0); + assert(bits <= SIZE_MAX - 7); + assert(ret_key); + assert(ret_key_len); + + log_debug("Calculating KDFe()."); + + size_t len = DIV_ROUND_UP(bits, 8); + + const char *hash_alg_name = tpm2_hash_alg_to_string(hash_alg); + if (!hash_alg_name) + return -EOPNOTSUPP; + + size_t info_len = strlen(label) + 1 + context_u_size + context_v_size; + _cleanup_free_ void *info = malloc(info_len); + if (!info) + return log_oom_debug(); + + void *end = mempcpy(mempcpy(stpcpy(info, label) + 1, context_u, context_u_size), context_v, context_v_size); + /* assert we copied exactly the right amount that we allocated */ + assert(end > info && (uintptr_t) end - (uintptr_t) info == info_len); + + _cleanup_free_ void *buf = NULL; + r = kdf_ss_derive( + hash_alg_name, + shared_secret, + shared_secret_len, + /* salt= */ NULL, + /* salt_size= */ 0, + info, + info_len, + len, + &buf); + if (r < 0) + return r; + + *ret_key = TAKE_PTR(buf); + *ret_key_len = len; + + return 0; +} + +static int tpm2_calculate_seal_public( + const TPM2B_PUBLIC *parent, + const TPMA_OBJECT *attributes, + const TPM2B_DIGEST *policy, + const TPM2B_DIGEST *seed, + const void *secret, + size_t secret_size, + TPM2B_PUBLIC *ret) { + + int r; + + assert(parent); + assert(seed); + assert(secret); + assert(ret); + + log_debug("Calculating public part of sealed object."); + + struct iovec data[] = { + IOVEC_MAKE((void*) seed->buffer, seed->size), + IOVEC_MAKE((void*) secret, secret_size), + }; + TPM2B_DIGEST unique; + r = tpm2_digest_many( + parent->publicArea.nameAlg, + &unique, + data, + ELEMENTSOF(data), + /* extend= */ false); + if (r < 0) + return r; + + *ret = (TPM2B_PUBLIC) { + .size = sizeof(TPMT_PUBLIC), + .publicArea = { + .type = TPM2_ALG_KEYEDHASH, + .nameAlg = parent->publicArea.nameAlg, + .objectAttributes = attributes ? *attributes : 0, + .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, unique.size), + .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, + .unique.keyedHash = unique, + }, + }; + + return 0; +} + +static int tpm2_calculate_seal_private( + const TPM2B_PUBLIC *parent, + const TPM2B_NAME *name, + const char *pin, + const TPM2B_DIGEST *seed, + const void *secret, + size_t secret_size, + TPM2B_PRIVATE *ret) { + + TSS2_RC rc; + int r; + + assert(parent); + assert(name); + assert(seed); + assert(secret); + assert(ret); + + log_debug("Calculating private part of sealed object."); + + _cleanup_free_ void *storage_key = NULL; + size_t storage_key_size; + r = tpm2_kdfa(parent->publicArea.nameAlg, + seed->buffer, + seed->size, + "STORAGE", + name->name, + name->size, + (size_t) parent->publicArea.parameters.asymDetail.symmetric.keyBits.sym, + &storage_key, + &storage_key_size); + if (r < 0) + return log_debug_errno(r, "Could not calculate storage key KDFa: %m"); + + r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg); + if (r < 0) + return -EOPNOTSUPP; + + size_t bits = (size_t) r * 8; + + _cleanup_free_ void *integrity_key = NULL; + size_t integrity_key_size; + r = tpm2_kdfa(parent->publicArea.nameAlg, + seed->buffer, + seed->size, + "INTEGRITY", + /* context= */ NULL, + /* n_context= */ 0, + bits, + &integrity_key, + &integrity_key_size); + if (r < 0) + return log_debug_errno(r, "Could not calculate integrity key KDFa: %m"); + + TPM2B_AUTH auth = {}; + if (pin) { + r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth); + if (r < 0) + return r; + } + + TPM2B_SENSITIVE sensitive = { + .size = sizeof(TPMT_SENSITIVE), + .sensitiveArea = { + .sensitiveType = TPM2_ALG_KEYEDHASH, + .authValue = auth, + .seedValue = *seed, + .sensitive.bits = TPM2B_SENSITIVE_DATA_MAKE(secret, secret_size), + }, + }; + + _cleanup_free_ void *marshalled_sensitive = malloc(sizeof(sensitive)); + if (!marshalled_sensitive) + return log_oom_debug(); + + size_t marshalled_sensitive_size = 0; + rc = sym_Tss2_MU_TPM2B_SENSITIVE_Marshal( + &sensitive, + marshalled_sensitive, + sizeof(sensitive), + &marshalled_sensitive_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal sensitive: %s", sym_Tss2_RC_Decode(rc)); + + const char *sym_alg = tpm2_sym_alg_to_string(parent->publicArea.parameters.asymDetail.symmetric.algorithm); + if (!sym_alg) + return -EOPNOTSUPP; + + const char *sym_mode = tpm2_sym_mode_to_string(parent->publicArea.parameters.asymDetail.symmetric.mode.sym); + if (!sym_mode) + return -EOPNOTSUPP; + + _cleanup_free_ void *encrypted_sensitive = NULL; + size_t encrypted_sensitive_size; + r = openssl_cipher( + sym_alg, + parent->publicArea.parameters.asymDetail.symmetric.keyBits.sym, + sym_mode, + storage_key, storage_key_size, + /* iv= */ NULL, /* n_iv= */ 0, + marshalled_sensitive, marshalled_sensitive_size, + &encrypted_sensitive, &encrypted_sensitive_size); + if (r < 0) + return r; + + const char *hash_alg_name = tpm2_hash_alg_to_string(parent->publicArea.nameAlg); + if (!hash_alg_name) + return -EOPNOTSUPP; + + _cleanup_free_ void *hmac_buffer = NULL; + size_t hmac_size = 0; + struct iovec hmac_data[] = { + IOVEC_MAKE((void*) encrypted_sensitive, encrypted_sensitive_size), + IOVEC_MAKE((void*) name->name, name->size), + }; + r = openssl_hmac_many( + hash_alg_name, + integrity_key, + integrity_key_size, + hmac_data, + ELEMENTSOF(hmac_data), + &hmac_buffer, + &hmac_size); + if (r < 0) + return r; + + TPM2B_DIGEST outer_hmac = TPM2B_DIGEST_MAKE(hmac_buffer, hmac_size); + + TPM2B_PRIVATE private = {}; + size_t private_size = 0; + rc = sym_Tss2_MU_TPM2B_DIGEST_Marshal( + &outer_hmac, + private.buffer, + sizeof(private.buffer), + &private_size); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal digest: %s", sym_Tss2_RC_Decode(rc)); + private.size = private_size; + + assert(sizeof(private.buffer) - private.size >= encrypted_sensitive_size); + memcpy_safe(&private.buffer[private.size], encrypted_sensitive, encrypted_sensitive_size); + private.size += encrypted_sensitive_size; + + *ret = private; + + return 0; +} + +static int tpm2_calculate_seal_rsa_seed( + const TPM2B_PUBLIC *parent, + void **ret_seed, + size_t *ret_seed_size, + void **ret_encrypted_seed, + size_t *ret_encrypted_seed_size) { + + int r; + + assert(parent); + assert(ret_seed); + assert(ret_seed_size); + assert(ret_encrypted_seed); + assert(ret_encrypted_seed_size); + + log_debug("Calculating encrypted seed for RSA sealed object."); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *parent_pkey = NULL; + r = tpm2_tpm2b_public_to_openssl_pkey(parent, &parent_pkey); + if (r < 0) + return log_debug_errno(r, "Could not convert TPM2B_PUBLIC to Openssl PKEY: %m"); + + r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg); + if (r < 0) + return -EOPNOTSUPP; + + size_t seed_size = (size_t) r; + + _cleanup_free_ void *seed = malloc(seed_size); + if (!seed) + return log_oom_debug(); + + r = crypto_random_bytes(seed, seed_size); + if (r < 0) + return log_debug_errno(r, "Failed to generate random seed: %m"); + + const char *hash_alg_name = tpm2_hash_alg_to_string(parent->publicArea.nameAlg); + if (!hash_alg_name) + return -EOPNOTSUPP; + + _cleanup_free_ void *encrypted_seed = NULL; + size_t encrypted_seed_size; + r = rsa_oaep_encrypt_bytes( + parent_pkey, + hash_alg_name, + "DUPLICATE", + seed, + seed_size, + &encrypted_seed, + &encrypted_seed_size); + if (r < 0) + return log_debug_errno(r, "Could not RSA-OAEP encrypt random seed: %m"); + + *ret_seed = TAKE_PTR(seed); + *ret_seed_size = seed_size; + *ret_encrypted_seed = TAKE_PTR(encrypted_seed); + *ret_encrypted_seed_size = encrypted_seed_size; + + return 0; +} + +static int tpm2_calculate_seal_ecc_seed( + const TPM2B_PUBLIC *parent, + void **ret_seed, + size_t *ret_seed_size, + void **ret_encrypted_seed, + size_t *ret_encrypted_seed_size) { + + TSS2_RC rc; + int r; + + assert(parent); + assert(ret_seed); + assert(ret_seed_size); + assert(ret_encrypted_seed); + assert(ret_encrypted_seed_size); + + log_debug("Calculating encrypted seed for ECC sealed object."); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *parent_pkey = NULL; + r = tpm2_tpm2b_public_to_openssl_pkey(parent, &parent_pkey); + if (r < 0) + return log_debug_errno(r, "Could not convert TPM2B_PUBLIC to Openssl PKEY: %m"); + + int curve_id; + r = ecc_pkey_to_curve_x_y( + parent_pkey, + &curve_id, + /* ret_x= */ NULL, /* ret_x_size= */ 0, + /* ret_y= */ NULL, /* ret_y_size= */ 0); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + r = ecc_pkey_new(curve_id, &pkey); + if (r < 0) + return r; + + _cleanup_free_ void *shared_secret = NULL; + size_t shared_secret_size; + r = ecc_ecdh(pkey, parent_pkey, &shared_secret, &shared_secret_size); + if (r < 0) + return log_debug_errno(r, "Could not generate ECC shared secret: %m"); + + _cleanup_free_ void *x = NULL, *y = NULL; + size_t x_size, y_size; + r = ecc_pkey_to_curve_x_y(pkey, /* curve_id= */ NULL, &x, &x_size, &y, &y_size); + if (r < 0) + return log_debug_errno(r, "Could not get ECC get x/y: %m"); + + r = TPM2B_ECC_PARAMETER_CHECK_SIZE(x_size); + if (r < 0) + return log_debug_errno(r, "ECC point x size %zu is too large: %m", x_size); + + r = TPM2B_ECC_PARAMETER_CHECK_SIZE(y_size); + if (r < 0) + return log_debug_errno(r, "ECC point y size %zu is too large: %m", y_size); + + TPMS_ECC_POINT point = { + .x = TPM2B_ECC_PARAMETER_MAKE(x, x_size), + .y = TPM2B_ECC_PARAMETER_MAKE(y, y_size), + }; + + _cleanup_free_ void *encrypted_seed = malloc(sizeof(point)); + if (!encrypted_seed) + return log_oom_debug(); + + size_t encrypted_seed_size = 0; + rc = sym_Tss2_MU_TPMS_ECC_POINT_Marshal(&point, encrypted_seed, sizeof(point), &encrypted_seed_size); + if (rc != TPM2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal ECC point: %s", sym_Tss2_RC_Decode(rc)); + + r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg); + if (r < 0) + return -EOPNOTSUPP; + + size_t bits = (size_t) r * 8; + + _cleanup_free_ void *seed = NULL; + size_t seed_size; + r = tpm2_kdfe(parent->publicArea.nameAlg, + shared_secret, + shared_secret_size, + "DUPLICATE", + x, + x_size, + parent->publicArea.unique.ecc.x.buffer, + parent->publicArea.unique.ecc.x.size, + bits, + &seed, + &seed_size); + if (r < 0) + return log_debug_errno(r, "Could not calculate KDFe: %m"); + + *ret_seed = TAKE_PTR(seed); + *ret_seed_size = seed_size; + *ret_encrypted_seed = TAKE_PTR(encrypted_seed); + *ret_encrypted_seed_size = encrypted_seed_size; + + return 0; +} + +static int tpm2_calculate_seal_seed( + const TPM2B_PUBLIC *parent, + TPM2B_DIGEST *ret_seed, + TPM2B_ENCRYPTED_SECRET *ret_encrypted_seed) { + + int r; + + assert(parent); + assert(ret_seed); + assert(ret_encrypted_seed); + + log_debug("Calculating encrypted seed for sealed object."); + + _cleanup_free_ void *seed = NULL, *encrypted_seed = NULL; + size_t seed_size, encrypted_seed_size; + if (parent->publicArea.type == TPM2_ALG_RSA) + r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); + else if (parent->publicArea.type == TPM2_ALG_ECC) + r = tpm2_calculate_seal_ecc_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); + else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unsupported parent key type 0x%" PRIx16, parent->publicArea.type); + if (r < 0) + return log_debug_errno(r, "Could not calculate encrypted seed: %m"); + + *ret_seed = TPM2B_DIGEST_MAKE(seed, seed_size); + *ret_encrypted_seed = TPM2B_ENCRYPTED_SECRET_MAKE(encrypted_seed, encrypted_seed_size); + + return 0; +} + +#endif /* HAVE_OPENSSL */ + +int tpm2_calculate_seal( + TPM2_HANDLE parent_handle, + const TPM2B_PUBLIC *parent_public, + const TPMA_OBJECT *attributes, + const void *secret, + size_t secret_size, + 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_serialized_parent, + size_t *ret_serialized_parent_size) { + +#if HAVE_OPENSSL + int r; + + assert(parent_public); + assert(secret || secret_size == 0); + assert(secret || ret_secret); + assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */ + assert(ret_blob); + assert(ret_blob_size); + assert(ret_serialized_parent); + assert(ret_serialized_parent_size); + + log_debug("Calculating sealed object."); + + /* Default to the SRK. */ + if (parent_handle == 0) + parent_handle = TPM2_SRK_HANDLE; + + switch (TPM2_HANDLE_TYPE(parent_handle)) { + case TPM2_HT_PERSISTENT: + case TPM2_HT_NV_INDEX: + break; + case TPM2_HT_TRANSIENT: + log_warning("Handle is transient, sealed secret may not be recoverable."); + break; + default: + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Handle 0x%" PRIx32 " not persistent, transient, or NV.", + parent_handle); + } + + _cleanup_(erase_and_freep) void *generated_secret = NULL; + if (!secret) { + /* No secret provided, generate a random secret. We use SHA256 digest length, though it can + * be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash + * size. */ + secret_size = TPM2_SHA256_DIGEST_SIZE; + generated_secret = malloc(secret_size); + if (!generated_secret) + return log_oom_debug(); + + r = crypto_random_bytes(generated_secret, secret_size); + if (r < 0) + return log_debug_errno(r, "Failed to generate secret key: %m"); + + secret = generated_secret; + } + + if (secret_size > TPM2_MAX_SEALED_DATA) + return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW), + "Secret size %zu too large, limit is %d bytes.", + secret_size, TPM2_MAX_SEALED_DATA); + + TPM2B_DIGEST random_seed; + TPM2B_ENCRYPTED_SECRET seed; + r = tpm2_calculate_seal_seed(parent_public, &random_seed, &seed); + if (r < 0) + return r; + + TPM2B_PUBLIC public; + r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public); + if (r < 0) + return r; + + TPM2B_NAME name; + r = tpm2_calculate_pubkey_name(&public.publicArea, &name); + if (r < 0) + return r; + + TPM2B_PRIVATE private; + r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private); + if (r < 0) + return r; + + _cleanup_free_ void *blob = NULL; + size_t blob_size; + r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size); + if (r < 0) + return log_debug_errno(r, "Could not create sealed blob: %m"); + + TPM2B_NAME parent_name; + r = tpm2_calculate_pubkey_name(&parent_public->publicArea, &parent_name); + if (r < 0) + return r; + + _cleanup_free_ void *serialized_parent = NULL; + size_t serialized_parent_size; + r = tpm2_calculate_serialize( + parent_handle, + &parent_name, + parent_public, + &serialized_parent, + &serialized_parent_size); + if (r < 0) + return r; + + if (ret_secret) + *ret_secret = TAKE_PTR(generated_secret); + if (ret_secret_size) + *ret_secret_size = secret_size; + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; + *ret_serialized_parent = TAKE_PTR(serialized_parent); + *ret_serialized_parent_size = serialized_parent_size; + + return 0; +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, @@ -4119,7 +4900,7 @@ int tpm2_seal(Tpm2Context *c, _cleanup_free_ void *blob = NULL; size_t blob_size = 0; - r = tpm2_marshal_blob(public, private, &blob, &blob_size); + r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size); if (r < 0) return log_debug_errno(r, "Could not create sealed blob: %m"); @@ -4203,7 +4984,8 @@ int tpm2_unseal(Tpm2Context *c, TPM2B_PUBLIC public; TPM2B_PRIVATE private; - r = tpm2_unmarshal_blob(blob, blob_size, &public, &private); + TPM2B_ENCRYPTED_SECRET seed = {}; + r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed); if (r < 0) return log_debug_errno(r, "Could not extract parts from blob: %m"); @@ -4239,6 +5021,24 @@ int tpm2_unseal(Tpm2Context *c, return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "No SRK or primary alg provided."); + if (seed.size > 0) { + /* This is a calculated (or duplicated) sealed object, and must be imported. */ + _cleanup_free_ TPM2B_PRIVATE *imported_private = NULL; + r = tpm2_import(c, + primary_handle, + /* session= */ NULL, + &public, + &private, + &seed, + /* encryption_key= */ NULL, + /* symmetric= */ NULL, + &imported_private); + if (r < 0) + return r; + + private = *imported_private; + } + log_debug("Loading HMAC key into TPM."); /* diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 59c6a6fa92..1b84783660 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -27,6 +27,11 @@ typedef enum TPM2Flags { * the Provisioning Guidance document for more details. */ #define TPM2_SRK_HANDLE UINT32_C(0x81000001) +/* The TPM specification limits sealed data to MAX_SYM_DATA. Unfortunately, tpm2-tss incorrectly + * defines this value as 256; the TPM specification Part 2 ("Structures") section + * "TPMU_SENSITIVE_CREATE" states "For interoperability, MAX_SYM_DATA should be 128." */ +#define TPM2_MAX_SEALED_DATA UINT16_C(128) + static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) { return pcr < TPM2_PCRS_MAX; } @@ -179,7 +184,9 @@ int tpm2_calculate_pubkey_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_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size); int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); +int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, 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_serialized_parent, size_t *ret_serialized_parent_size); int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template); int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);