From: William Roberts Date: Wed, 7 Sep 2022 12:52:16 +0000 (-0500) Subject: tpm2: add bind key X-Git-Tag: v252-rc1~233 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0e15c14f639c15a0279388e2bfe6907ed03e788a;p=thirdparty%2Fsystemd.git tpm2: add bind key Currently, the tpm2 support will use encrypted sessions by creating a primary key that is used to encrypt traffic. This creates a problem as the key created for encrypting the traffic could be faked by an active interposer on the bus. In cases when a pin is used, we can introduce the bind key. The pin is used as the auth value for the seal key, aka the disk encryption key, and that auth value can be used in the session establishment. An attacker would need the pin value to create the secure session and thus an active interposer without the pin could not interpose on TPM traffic. Related-to: #22637 Signed-off-by: William Roberts --- diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 38ca8621234..fdb305ca48f 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -616,9 +616,25 @@ static int tpm2_get_best_pcr_bank( return 0; } +static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + struct sha256_ctx hash; + + assert(auth); + assert(pin); + auth->size = SHA256_DIGEST_SIZE; + + sha256_init_ctx(&hash); + sha256_process_bytes(pin, len, &hash); + sha256_finish_ctx(&hash, auth->buffer); + + explicit_bzero_safe(&hash, sizeof(hash)); +} + static int tpm2_make_encryption_session( ESYS_CONTEXT *c, ESYS_TR primary, + ESYS_TR bind_key, + const char *pin, ESYS_TR *ret_session) { static const TPMT_SYM_DEF symmetric = { @@ -633,6 +649,28 @@ static int tpm2_make_encryption_session( assert(c); + /* + * if a pin is set for the seal object, use it to bind the session + * key to that object. This prevents active bus interposers from + * faking a TPM and seeing the unsealed value. An active interposer + * could fake a TPM, satisfying the encrypted session, and just + * forward everything to the *real* TPM. + */ + if (pin) { + TPM2B_AUTH auth = {}; + + hash_pin(pin, strlen(pin), &auth); + + rc = sym_Esys_TR_SetAuth(c, bind_key, &auth); + /* ESAPI knows about it, so clear it from our memory */ + explicit_bzero_safe(&auth, sizeof(auth)); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load PIN in TPM: %s", + sym_Tss2_RC_Decode(rc)); + } + log_debug("Starting HMAC encryption session."); /* Start a salted, unbound HMAC session with a well-known key (e.g. primary key) as tpmKey, which @@ -641,7 +679,7 @@ static int tpm2_make_encryption_session( rc = sym_Esys_StartAuthSession( c, primary, - ESYS_TR_NONE, + bind_key, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, @@ -815,18 +853,6 @@ finish: return r; } -static void hash_pin(const char *pin, size_t len, uint8_t ret_digest[static SHA256_DIGEST_SIZE]) { - struct sha256_ctx hash; - - assert(pin); - - sha256_init_ctx(&hash); - sha256_process_bytes(pin, len, &hash); - sha256_finish_ctx(&hash, ret_digest); - - explicit_bzero_safe(&hash, sizeof(hash)); -} - int tpm2_seal( const char *device, uint32_t pcr_mask, @@ -893,7 +919,8 @@ int tpm2_seal( if (r < 0) return r; - r = tpm2_make_encryption_session(c.esys_context, primary, &session); + /* we cannot use the bind key before its created */ + r = tpm2_make_encryption_session(c.esys_context, primary, ESYS_TR_NONE, NULL, &session); if (r < 0) goto finish; @@ -930,10 +957,9 @@ int tpm2_seal( .size = sizeof(hmac_sensitive.sensitive), .sensitive.data.size = 32, }; - if (pin) { - hash_pin(pin, strlen(pin), hmac_sensitive.sensitive.userAuth.buffer); - hmac_sensitive.sensitive.userAuth.size = SHA256_DIGEST_SIZE; - } + if (pin) + hash_pin(pin, strlen(pin), &hmac_sensitive.sensitive.userAuth); + assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); (void) tpm2_credit_random(c.esys_context); @@ -1107,37 +1133,18 @@ int tpm2_unseal( if (r < 0) return r; - r = tpm2_make_encryption_session(c.esys_context, primary, &hmac_session); - if (r < 0) - goto finish; - - r = tpm2_make_pcr_session( - c.esys_context, - primary, - hmac_session, - TPM2_SE_POLICY, - pcr_mask, - pcr_bank, - !!pin, - &session, - &policy_digest, - /* ret_pcr_bank= */ NULL); - if (r < 0) - goto finish; - - /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not - * wait until the TPM2 tells us to go away. */ - if (known_policy_hash_size > 0 && - memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "Current policy digest does not match stored policy digest, cancelling TPM2 authentication attempt."); - log_debug("Loading HMAC key into TPM."); + /* + * Nothing sensitive on the bus, no need for encryption. Even if an attacker + * gives you back a different key, the session initiation will fail if a pin + * is provided. If an attacker gives back a bad key, we already lost since + * primary key is not verified and they could attack there as well. + */ rc = sym_Esys_Load( c.esys_context, primary, - hmac_session, /* use HMAC session to enable parameter encryption */ + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, &private, @@ -1158,23 +1165,31 @@ int tpm2_unseal( goto finish; } - if (pin) { - TPM2B_AUTH auth = { - .size = SHA256_DIGEST_SIZE - }; + r = tpm2_make_encryption_session(c.esys_context, primary, hmac_key, pin, &hmac_session); + if (r < 0) + goto finish; - hash_pin(pin, strlen(pin), auth.buffer); + r = tpm2_make_pcr_session( + c.esys_context, + primary, + hmac_session, + TPM2_SE_POLICY, + pcr_mask, + pcr_bank, + !!pin, + &session, + &policy_digest, + /* ret_pcr_bank= */ NULL); + if (r < 0) + goto finish; - rc = sym_Esys_TR_SetAuth(c.esys_context, hmac_key, &auth); - explicit_bzero_safe(&auth, sizeof(auth)); - if (rc != TSS2_RC_SUCCESS) { - r = log_error_errno( - SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to load PIN in TPM: %s", - sym_Tss2_RC_Decode(rc)); - goto finish; - } - } + /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not + * wait until the TPM2 tells us to go away. */ + if (known_policy_hash_size > 0 && + memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Current policy digest does not match stored policy digest, cancelling " + "TPM2 authentication attempt."); log_debug("Unsealing HMAC key.");