]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tpm2: add bind key
authorWilliam Roberts <william.c.roberts@intel.com>
Wed, 7 Sep 2022 12:52:16 +0000 (07:52 -0500)
committerLennart Poettering <lennart@poettering.net>
Thu, 8 Sep 2022 09:16:28 +0000 (11:16 +0200)
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 <william.c.roberts@intel.com>
src/shared/tpm2-util.c

index 38ca862123427b7c66f6ed7103e808ec90f883fe..fdb305ca48fb582c6f368a8967f71152f53b2963 100644 (file)
@@ -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.");