diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 39b6810501..f73724f1c6 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -4567,15 +4567,28 @@ static int make_policy(bool force, bool recovery_pin) { log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); } - TPM2B_NV_PUBLIC nv_public = {}; + /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; + r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); + if (r < 0) + return r; + TPM2B_NV_PUBLIC nv_public = {}; usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); if (!iovec_is_set(&nv_blob)) { - TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - r = tpm2_calculate_policy_auth_value(&recovery_policy_digest); + _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; + r = tpm2_get_name( + tc, + pin_handle, + &pin_name); if (r < 0) - return log_error_errno(r, "Failed to calculate authentication value policy: %m"); + return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); + + TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); + if (r < 0) + return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); log_debug("Allocating NV index to write PCR policy to..."); r = tpm2_define_policy_nv_index( @@ -4594,10 +4607,6 @@ static int make_policy(bool force, bool recovery_pin) { return log_error_errno(r, "Failed to allocate NV index: %m"); } - r = tpm2_set_auth_binary(tc, nv_handle, &auth); - if (r < 0) - return log_error_errno(r, "Failed to set authentication value on NV index: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; r = tpm2_make_policy_session( tc, @@ -4607,9 +4616,11 @@ static int make_policy(bool force, bool recovery_pin) { if (r < 0) return log_error_errno(r, "Failed to allocate policy session: %m"); - r = tpm2_policy_auth_value( + r = tpm2_policy_signed_hmac_sha256( tc, policy_session, + pin_handle, + &IOVEC_MAKE(auth.buffer, auth.size), /* ret_policy_digest= */ NULL); if (r < 0) return log_error_errno(r, "Failed to submit authentication value policy: %m"); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 051ff291dd..dd2148fcda 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -29,6 +29,7 @@ #include "recurse-dir.h" #include "sha256.h" #include "sort-util.h" +#include "sparse-endian.h" #include "stat-util.h" #include "string-table.h" #include "sync-util.h" @@ -3534,6 +3535,150 @@ int tpm2_policy_auth_value( return tpm2_get_policy_digest(c, session, ret_policy_digest); } +/* Extend 'digest' with the PolicySigned calculated hash. */ +int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) { + TPM2_CC command = TPM2_CC_PolicySigned; + TSS2_RC rc; + int r; + + assert(digest); + assert(digest->size == SHA256_DIGEST_SIZE); + assert(name); + + r = dlopen_tpm2(); + if (r < 0) + return log_debug_errno(r, "TPM2 support not installed: %m"); + + uint8_t buf[sizeof(command)]; + size_t offset = 0; + + rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicySigned command: %s", sym_Tss2_RC_Decode(rc)); + + if (offset != sizeof(command)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Offset 0x%zx wrong after marshalling PolicySigned command", offset); + + struct iovec data[] = { + IOVEC_MAKE(buf, offset), + IOVEC_MAKE(name->name, name->size), + }; + + r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true); + if (r < 0) + return r; + + const TPM2B_NONCE policyRef = {}; /* For now, we do not make use of the policyRef stuff */ + + r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, policyRef.buffer, policyRef.size, /* extend= */ true); + if (r < 0) + return r; + + tpm2_log_debug_digest(digest, "PolicySigned calculated digest"); + + return 0; +} + +int tpm2_policy_signed_hmac_sha256( + Tpm2Context *c, + const Tpm2Handle *session, + const Tpm2Handle *hmac_key_handle, + const struct iovec *hmac_key, + TPM2B_DIGEST **ret_policy_digest) { + +#if HAVE_OPENSSL + TSS2_RC rc; + int r; + + assert(c); + assert(session); + assert(hmac_key_handle); + assert(iovec_is_set(hmac_key)); + + /* This sends a TPM2_PolicySigned command to the tpm. As signature key we use an HMAC-SHA256 key + * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and + * referenced in hmac_key_handle. */ + + log_debug("Submitting PolicySigned policy for HMAC-SHA256."); + + /* Acquire the nonce from the TPM that we shall sign */ + _cleanup_(Esys_Freep) TPM2B_NONCE *nonce = NULL; + rc = sym_Esys_TRSess_GetNonceTPM( + c->esys_context, + session->esys_handle, + &nonce); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to determine NoneTPM of auth session: %s", + sym_Tss2_RC_Decode(rc)); + + be32_t expiration = htobe64(0); + const TPM2B_DIGEST cpHashA = {}; /* For now, we do not make use of the cpHashA stuff */ + const TPM2B_NONCE policyRef = {}; /* ditto, we do not bother with policyRef */ + + /* Put together the data to sign, as per TPM2 Spec Part 3, 23.3.1 */ + struct iovec data_to_sign[] = { + IOVEC_MAKE(nonce->buffer, nonce->size), + IOVEC_MAKE(&expiration, sizeof(expiration)), + IOVEC_MAKE(cpHashA.buffer, cpHashA.size), + IOVEC_MAKE(policyRef.buffer, policyRef.size), + }; + + /* Now calculate the digest of the data we put together */ + TPM2B_DIGEST digest_to_sign; + r = tpm2_digest_many(TPM2_ALG_SHA256, &digest_to_sign, data_to_sign, ELEMENTSOF(data_to_sign), /* extend= */ false); + if (r < 0) + return r; + + unsigned char hmac_signature[SHA256_DIGEST_SIZE]; + unsigned hmac_signature_size = sizeof(hmac_signature); + + /* And sign this with our key */ + if (!HMAC(EVP_sha256(), + hmac_key->iov_base, + hmac_key->iov_len, + digest_to_sign.buffer, + digest_to_sign.size, + hmac_signature, + &hmac_signature_size)) + return -ENOTRECOVERABLE; + + /* Now bring the signature into a format that the TPM understands */ + TPMT_SIGNATURE sig = { + .sigAlg = TPM2_ALG_HMAC, + .signature.hmac.hashAlg = TPM2_ALG_SHA256, + }; + assert(hmac_signature_size == sizeof(sig.signature.hmac.digest.sha256)); + memcpy(sig.signature.hmac.digest.sha256, hmac_signature, hmac_signature_size); + + /* And submit the whole shebang to the TPM */ + rc = sym_Esys_PolicySigned( + c->esys_context, + hmac_key_handle->esys_handle, + session->esys_handle, + /* shandle1= */ ESYS_TR_NONE, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + nonce, + &cpHashA, + &policyRef, + expiration, + &sig, + /* timeout= */ NULL, + /* policyTicket= */ NULL); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to add PolicySigned policy to TPM: %s", + sym_Tss2_RC_Decode(rc)); + + return tpm2_get_policy_digest(c, session, ret_policy_digest); +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + int tpm2_calculate_policy_authorize_nv( const TPM2B_NV_PUBLIC *public_info, TPM2B_DIGEST *digest) { @@ -7039,6 +7184,75 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { *ret = device_key_public; return 0; } + +int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret) { + int r; + + assert(c); + assert(pin); + assert(ret); + + log_debug("Converting PIN into TPM2 HMAC-SHA256 object."); + + /* Load the PIN (which we have stored in the "auth" TPM2B_AUTH) into the TPM as an HMAC key so that + * we can use it in a TPM2_PolicySigned() to write to the nvindex. For that we'll prep a pair of + * TPM2B_PUBLIC and TPM2B_SENSITIVE that defines an HMAC-SHA256 keyed hash function, and initialize + * it based on on the provided PIN data. */ + + TPM2B_PUBLIC auth_hmac_public = { + .publicArea = { + .type = TPM2_ALG_KEYEDHASH, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_SIGN_ENCRYPT, + .parameters.keyedHashDetail.scheme = { + .scheme = TPM2_ALG_HMAC, + .details.hmac.hashAlg = TPM2_ALG_SHA256, + }, + .unique.keyedHash.size = SHA256_DIGEST_SIZE, + }, + }; + + TPM2B_SENSITIVE auth_hmac_private = { + .sensitiveArea = { + .sensitiveType = TPM2_ALG_KEYEDHASH, + .sensitive.bits.size = pin->size, + .seedValue.size = SHA256_DIGEST_SIZE, + }, + }; + + /* Copy in the key data */ + memcpy_safe(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, pin->buffer, pin->size); + + /* NB: We initialize the seed of the TPMT_SENSITIVE structure to all zeroes, since we want a stable + * "name" of the PIN object */ + + /* Now calculate the "unique" field for the public area, based on the sensitive data, according to + * the algorithm in the TPM2 spec, part 1, Section 27.5.3.2 */ + struct iovec sensitive_data[] = { + IOVEC_MAKE(auth_hmac_private.sensitiveArea.seedValue.buffer, auth_hmac_private.sensitiveArea.seedValue.size), + IOVEC_MAKE(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, auth_hmac_private.sensitiveArea.sensitive.bits.size), + }; + r = tpm2_digest_many( + auth_hmac_public.publicArea.nameAlg, + &auth_hmac_public.publicArea.unique.keyedHash, + sensitive_data, + ELEMENTSOF(sensitive_data), + /* extend= */ false); + if (r < 0) + return r; + + /* And now load the public/private parts into the TPM and get a handle back */ + r = tpm2_load_external( + c, + session, + &auth_hmac_public, + &auth_hmac_private, + ret); + if (r < 0) + return log_error_errno(r, "Failed to load PIN into TPM2: %m"); + + return 0; +} #endif char *tpm2_pcr_mask_to_string(uint32_t mask) { diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 343c2575aa..adee36f8ed 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -268,6 +268,7 @@ int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tp int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm); +int tpm2_policy_signed_hmac_sha256(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *hmac_key_handle, const struct iovec *hmac_key, TPM2B_DIGEST **ret_policy_digest); int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name); @@ -278,6 +279,7 @@ int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGE int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest); int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy); +int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name); int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size); int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest); int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent); @@ -311,6 +313,8 @@ int tpm2_deserialize(Tpm2Context *c, const void *serialized, size_t serialized_s int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret); +int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret); + /* 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