Re: [PATCH v8 14/22] tpm: Add HMAC session start and end functions

Jarkko Sakkinen
2024-04-29T22:38:35Z

On Mon Apr 29, 2024 at 11:28 PM EEST, James Bottomley wrote:
> Add session based HMAC authentication plus parameter decryption and
> response encryption using AES. The basic design is to segregate all
> the nasty crypto, hash and hmac code into tpm2-sessions.c and export a
> usable API. The API first of all starts off by gaining a session with
> tpm2_start_auth_session() which initiates a session with the TPM and
> allocates an opaque tpm2_auth structure to handle the session
> parameters. The design is that session use will be single threaded
> from start to finish under the ops lock, so the tpm2_auth structure is
> stored in struct tpm2_chip to simpify the externally visible API.
>
> The session can be ended with tpm2_end_auth_session() which is
> designed only to be used in error legs. Ordinarily the further
> session API (future patches) will end or continue the session
> appropriately without having to call this.
>
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> # crypto API parts
>
> ---
>
> v6: split into new patch, update config variable
> v7: add tpm2_ prefix and memzero_explicit
> v8: cosmetic changes, split out KDFe and KDFa
> ---
> drivers/char/tpm/Kconfig | 2 +
> drivers/char/tpm/tpm-buf.c | 1 +
> drivers/char/tpm/tpm-chip.c | 3 +
> drivers/char/tpm/tpm2-sessions.c | 285 +++++++++++++++++++++++++++++++
> include/linux/tpm.h | 34 ++++
> 5 files changed, 325 insertions(+)
>
> diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> index 4873e6eae255..cecf617c9c90 100644
> --- a/drivers/char/tpm/Kconfig
> +++ b/drivers/char/tpm/Kconfig
> @@ -30,6 +30,8 @@ if TCG_TPM
> config TCG_TPM2_HMAC
> bool "Use HMAC and encrypted transactions on the TPM bus"
> default y
> + select CRYPTO_ECDH
> + select CRYPTO_LIB_AESCFB
> select CRYPTO_LIB_SHA256
> help
> Setting this causes us to deploy a scheme which uses request
> diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
> index bb81180495d1..274130398569 100644
> --- a/drivers/char/tpm/tpm-buf.c
> +++ b/drivers/char/tpm/tpm-buf.c
> @@ -44,6 +44,7 @@ void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal)
> head->tag = cpu_to_be16(tag);
> head->length = cpu_to_be32(sizeof(*head));
> head->ordinal = cpu_to_be32(ordinal);
> + buf->handles = 0;
> }
> EXPORT_SYMBOL_GPL(tpm_buf_reset);
>
> diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
> index 42b1062e33cd..d93937326b2e 100644
> --- a/drivers/char/tpm/tpm-chip.c
> +++ b/drivers/char/tpm/tpm-chip.c
> @@ -275,6 +275,9 @@ static void tpm_dev_release(struct device *dev)
> kfree(chip->work_space.context_buf);
> kfree(chip->work_space.session_buf);
> kfree(chip->allocated_banks);
> +#ifdef CONFIG_TCG_TPM2_HMAC
> + kfree(chip->auth);
> +#endif
> kfree(chip);
> }
>
> diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
> index 8429e596f1eb..71b3c0e75760 100644
> --- a/drivers/char/tpm/tpm2-sessions.c
> +++ b/drivers/char/tpm/tpm2-sessions.c
> @@ -3,13 +3,101 @@
> /*
> * Copyright (C) 2018 James.Bottomley@HansenPartnership.com
> *
> + * Cryptographic helper routines for handling TPM2 sessions for
> + * authorization HMAC and request response encryption.
> + *
> + * The idea is to ensure that every TPM command is HMAC protected by a
> + * session, meaning in-flight tampering would be detected and in
> + * addition all sensitive inputs and responses should be encrypted.
> + *
> + * The basic way this works is to use a TPM feature called salted
> + * sessions where a random secret used in session construction is
> + * encrypted to the public part of a known TPM key. The problem is we
> + * have no known keys, so initially a primary Elliptic Curve key is
> + * derived from the NULL seed (we use EC because most TPMs generate
> + * these keys much faster than RSA ones). The curve used is NIST_P256
> + * because that's now mandated to be present in 'TCG TPM v2.0
> + * Provisioning Guidance'
> + *
> + * Threat problems: the initial TPM2_CreatePrimary is not (and cannot
> + * be) session protected, so a clever Man in the Middle could return a
> + * public key they control to this command and from there intercept
> + * and decode all subsequent session based transactions. The kernel
> + * cannot mitigate this threat but, after boot, userspace can get
> + * proof this has not happened by asking the TPM to certify the NULL
> + * key. This certification would chain back to the TPM Endorsement
> + * Certificate and prove the NULL seed primary had not been tampered
> + * with and thus all sessions must have been cryptographically secure.
> + * To assist with this, the initial NULL seed public key name is made
> + * available in a sysfs file.
> + *
> + * Use of these functions:
> + *
> + * The design is all the crypto, hash and hmac gunk is confined in this
> + * file and never needs to be seen even by the kernel internal user. To
> + * the user there's an init function tpm2_sessions_init() that needs to
> + * be called once per TPM which generates the NULL seed primary key.
> + *
> + * These are the usage functions:
> + *
> + * tpm2_start_auth_session() which allocates the opaque auth structure
> + * and gets a session from the TPM. This must be called before
> + * any of the following functions. The session is protected by a
> + * session_key which is derived from a random salt value
> + * encrypted to the NULL seed.
> + * tpm2_end_auth_session() kills the session and frees the resources.
> + * Under normal operation this function is done by
> + * tpm_buf_check_hmac_response(), so this is only to be used on
> + * error legs where the latter is not executed.
> */
>
> #include "tpm.h"
> +#include <linux/random.h>
> +#include <linux/scatterlist.h>
> #include <asm/unaligned.h>
> +#include <crypto/kpp.h>
> +#include <crypto/ecdh.h>
> #include <crypto/hash.h>
> #include <crypto/hmac.h>
>
> +/*
> + * This is the structure that carries all the auth information (like
> + * session handle, nonces, session key and auth) from use to use it is
> + * designed to be opaque to anything outside.
> + */
> +struct tpm2_auth {
> + u32 handle;
> + /*
> + * This has two meanings: before tpm_buf_fill_hmac_session()
> + * it marks the offset in the buffer of the start of the
> + * sessions (i.e. after all the handles). Once the buffer has
> + * been filled it markes the session number of our auth
> + * session so we can find it again in the response buffer.
> + *
> + * The two cases are distinguished because the first offset
> + * must always be greater than TPM_HEADER_SIZE and the second
> + * must be less than or equal to 5.
> + */
> + u32 session;
> + /*
> + * the size here is variable and set by the size of our_nonce
> + * which must be between 16 and the name hash length. we set
> + * the maximum sha256 size for the greatest protection
> + */
> + u8 our_nonce[SHA256_DIGEST_SIZE];
> + u8 tpm_nonce[SHA256_DIGEST_SIZE];
> + /*
> + * the salt is only used across the session command/response
> + * after that it can be used as a scratch area
> + */
> + union {
> + u8 salt[EC_PT_SZ];
> + /* scratch for key + IV */
> + u8 scratch[AES_KEY_BYTES + AES_BLOCK_SIZE];
> + };
> + u8 session_key[SHA256_DIGEST_SIZE];
> +};
> +
> /*
> * It turns out the crypto hmac(sha256) is hard for us to consume
> * because it assumes a fixed key and the TPM seems to change the key
> @@ -113,6 +201,199 @@ static void tpm2_KDFe(u8 z[EC_PT_SZ], const char *str, u8 *pt_u, u8 *pt_v,
> sha256_final(&sctx, out);
> }
>
> +static void tpm_buf_append_salt(struct tpm_buf *buf, struct tpm_chip *chip)
> +{
> + struct crypto_kpp *kpp;
> + struct kpp_request *req;
> + struct scatterlist s[2], d[1];
> + struct ecdh p = {0};
> + u8 encoded_key[EC_PT_SZ], *x, *y;
> + unsigned int buf_len;
> +
> + /* secret is two sized points */
> + tpm_buf_append_u16(buf, (EC_PT_SZ + 2)*2);
> + /*
> + * we cheat here and append uninitialized data to form
> + * the points. All we care about is getting the two
> + * co-ordinate pointers, which will be used to overwrite
> + * the uninitialized data
> + */
> + tpm_buf_append_u16(buf, EC_PT_SZ);
> + x = &buf->data[tpm_buf_length(buf)];
> + tpm_buf_append(buf, encoded_key, EC_PT_SZ);
> + tpm_buf_append_u16(buf, EC_PT_SZ);
> + y = &buf->data[tpm_buf_length(buf)];
> + tpm_buf_append(buf, encoded_key, EC_PT_SZ);
> + sg_init_table(s, 2);
> + sg_set_buf(&s[0], x, EC_PT_SZ);
> + sg_set_buf(&s[1], y, EC_PT_SZ);
> +
> + kpp = crypto_alloc_kpp("ecdh-nist-p256", CRYPTO_ALG_INTERNAL, 0);
> + if (IS_ERR(kpp)) {
> + dev_err(&chip->dev, "crypto ecdh allocation failed\n");
> + return;
> + }
> +
> + buf_len = crypto_ecdh_key_len(&p);
> + if (sizeof(encoded_key) < buf_len) {
> + dev_err(&chip->dev, "salt buffer too small needs %d\n",
> + buf_len);
> + goto out;
> + }
> + crypto_ecdh_encode_key(encoded_key, buf_len, &p);
> + /* this generates a random private key */
> + crypto_kpp_set_secret(kpp, encoded_key, buf_len);
> +
> + /* salt is now the public point of this private key */
> + req = kpp_request_alloc(kpp, GFP_KERNEL);
> + if (!req)
> + goto out;
> + kpp_request_set_input(req, NULL, 0);
> + kpp_request_set_output(req, s, EC_PT_SZ*2);
> + crypto_kpp_generate_public_key(req);
> + /*
> + * we're not done: now we have to compute the shared secret
> + * which is our private key multiplied by the tpm_key public
> + * point, we actually only take the x point and discard the y
> + * point and feed it through KDFe to get the final secret salt
> + */
> + sg_set_buf(&s[0], chip->null_ec_key_x, EC_PT_SZ);
> + sg_set_buf(&s[1], chip->null_ec_key_y, EC_PT_SZ);
> + kpp_request_set_input(req, s, EC_PT_SZ*2);
> + sg_init_one(d, chip->auth->salt, EC_PT_SZ);
> + kpp_request_set_output(req, d, EC_PT_SZ);
> + crypto_kpp_compute_shared_secret(req);
> + kpp_request_free(req);
> +
> + /*
> + * pass the shared secret through KDFe for salt. Note salt
> + * area is used both for input shared secret and output salt.
> + * This works because KDFe fully consumes the secret before it
> + * writes the salt
> + */
> + tpm2_KDFe(chip->auth->salt, "SECRET", x, chip->null_ec_key_x,
> + chip->auth->salt);
> +
> + out:
> + crypto_free_kpp(kpp);
> +}
> +/**
> + * tpm2_end_auth_session() - kill the allocated auth session
> + * @chip: the TPM chip structure
> + *
> + * ends the session started by tpm2_start_auth_session and frees all
> + * the resources. Under normal conditions,
> + * tpm_buf_check_hmac_response() will correctly end the session if
> + * required, so this function is only for use in error legs that will
> + * bypass the normal invocation of tpm_buf_check_hmac_response().
> + */
> +void tpm2_end_auth_session(struct tpm_chip *chip)
> +{
> + tpm2_flush_context(chip, chip->auth->handle);
> + memzero_explicit(chip->auth, sizeof(*chip->auth));
> +}
> +EXPORT_SYMBOL(tpm2_end_auth_session);
> +
> +static int tpm2_parse_start_auth_session(struct tpm2_auth *auth,
> + struct tpm_buf *buf)
> +{
> + struct tpm_header *head = (struct tpm_header *)buf->data;
> + u32 tot_len = be32_to_cpu(head->length);
> + off_t offset = TPM_HEADER_SIZE;
> + u32 val;
> +
> + /* we're starting after the header so adjust the length */
> + tot_len -= TPM_HEADER_SIZE;
> +
> + /* should have handle plus nonce */
> + if (tot_len != 4 + 2 + sizeof(auth->tpm_nonce))
> + return -EINVAL;
> +
> + auth->handle = tpm_buf_read_u32(buf, &offset);
> + val = tpm_buf_read_u16(buf, &offset);
> + if (val != sizeof(auth->tpm_nonce))
> + return -EINVAL;
> + memcpy(auth->tpm_nonce, &buf->data[offset], sizeof(auth->tpm_nonce));
> + /* now compute the session key from the nonces */
> + tpm2_KDFa(auth->salt, sizeof(auth->salt), "ATH", auth->tpm_nonce,
> + auth->our_nonce, sizeof(auth->session_key),
> + auth->session_key);
> +
> + return 0;
> +}
> +
> +/**
> + * tpm2_start_auth_session() - create a HMAC authentication session with the TPM
> + * @chip: the TPM chip structure to create the session with
> + *
> + * This function loads the NULL seed from its saved context and starts
> + * an authentication session on the null seed, fills in the
> + * @chip->auth structure to contain all the session details necessary
> + * for performing the HMAC, encrypt and decrypt operations and
> + * returns. The NULL seed is flushed before this function returns.
> + *
> + * Return: zero on success or actual error encountered.
> + */
> +int tpm2_start_auth_session(struct tpm_chip *chip)
> +{
> + struct tpm_buf buf;
> + struct tpm2_auth *auth = chip->auth;
> + int rc;
> + /* null seed context has no offset, but we must provide one */
> + unsigned int offset = 0;
> + u32 nullkey;
> +
> + rc = tpm2_load_context(chip, chip->null_key_context, &offset,
> + &nullkey);
> + if (rc)
> + goto out;
> +
> + auth->session = TPM_HEADER_SIZE;
> +
> + rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS);
> + if (rc)
> + goto out;
> +
> + /* salt key handle */
> + tpm_buf_append_u32(&buf, nullkey);
> + /* bind key handle */
> + tpm_buf_append_u32(&buf, TPM2_RH_NULL);
> + /* nonce caller */
> + get_random_bytes(auth->our_nonce, sizeof(auth->our_nonce));
> + tpm_buf_append_u16(&buf, sizeof(auth->our_nonce));
> + tpm_buf_append(&buf, auth->our_nonce, sizeof(auth->our_nonce));
> +
> + /* append encrypted salt and squirrel away unencrypted in auth */
> + tpm_buf_append_salt(&buf, chip);
> + /* session type (HMAC, audit or policy) */
> + tpm_buf_append_u8(&buf, TPM2_SE_HMAC);
> +
> + /* symmetric encryption parameters */
> + /* symmetric algorithm */
> + tpm_buf_append_u16(&buf, TPM_ALG_AES);
> + /* bits for symmetric algorithm */
> + tpm_buf_append_u16(&buf, AES_KEY_BITS);
> + /* symmetric algorithm mode (must be CFB) */
> + tpm_buf_append_u16(&buf, TPM_ALG_CFB);
> + /* hash algorithm for session */
> + tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
> +
> + rc = tpm_transmit_cmd(chip, &buf, 0, "start auth session");
> + tpm2_flush_context(chip, nullkey);
> +
> + if (rc == TPM2_RC_SUCCESS)
> + rc = tpm2_parse_start_auth_session(auth, &buf);
> +
> + tpm_buf_destroy(&buf);
> +
> + if (rc)
> + goto out;
> +
> + out:
> + return rc;
> +}
> +EXPORT_SYMBOL(tpm2_start_auth_session);
> +
> /**
> * tpm2_parse_create_primary() - parse the data returned from TPM_CC_CREATE_PRIMARY
> *
> @@ -417,5 +698,9 @@ int tpm2_sessions_init(struct tpm_chip *chip)
> if (rc)
> dev_err(&chip->dev, "TPM: security failed (NULL seed derivation): %d\n", rc);
>
> + chip->auth = kmalloc(sizeof(*chip->auth), GFP_KERNEL);
> + if (!chip->auth)
> + return -ENOMEM;
> +
> return rc;
> }
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index bc8c9a350e23..81b5a70ff80d 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -31,6 +31,14 @@
> struct tpm_chip;
> struct trusted_key_payload;
> struct trusted_key_options;
> +/* opaque structure, holds auth session parameters like the session key */
> +struct tpm2_auth;
> +
> +enum tpm2_session_types {
> + TPM2_SE_HMAC = 0x00,
> + TPM2_SE_POLICY = 0x01,
> + TPM2_SE_TRIAL = 0x02,
> +};
>
> /* if you add a new hash to this, increment TPM_MAX_HASHES below */
> enum tpm_algorithms {
> @@ -203,6 +211,7 @@ struct tpm_chip {
> u8 null_key_name[TPM2_NAME_SIZE];
> u8 null_ec_key_x[EC_PT_SZ];
> u8 null_ec_key_y[EC_PT_SZ];
> + struct tpm2_auth *auth;
> #endif
> };
>
> @@ -266,6 +275,7 @@ enum tpm2_command_codes {
> TPM2_CC_CONTEXT_LOAD = 0x0161,
> TPM2_CC_CONTEXT_SAVE = 0x0162,
> TPM2_CC_FLUSH_CONTEXT = 0x0165,
> + TPM2_CC_START_AUTH_SESS = 0x0176,
> TPM2_CC_VERIFY_SIGNATURE = 0x0177,
> TPM2_CC_GET_CAPABILITY = 0x017A,
> TPM2_CC_GET_RANDOM = 0x017B,
> @@ -349,16 +359,21 @@ struct tpm_buf {
> u32 flags;
> u32 length;
> u8 *data;
> + u8 handles;
> };
>
> enum tpm2_object_attributes {
> TPM2_OA_FIXED_TPM = BIT(1),
> + TPM2_OA_ST_CLEAR = BIT(2),
> TPM2_OA_FIXED_PARENT = BIT(4),
> TPM2_OA_SENSITIVE_DATA_ORIGIN = BIT(5),
> TPM2_OA_USER_WITH_AUTH = BIT(6),
> + TPM2_OA_ADMIN_WITH_POLICY = BIT(7),
> TPM2_OA_NO_DA = BIT(10),
> + TPM2_OA_ENCRYPTED_DUPLICATION = BIT(11),
> TPM2_OA_RESTRICTED = BIT(16),
> TPM2_OA_DECRYPT = BIT(17),
> + TPM2_OA_SIGN = BIT(18),
> };
>
> /*
> @@ -378,6 +393,11 @@ enum tpm2_object_attributes {
>
> enum tpm2_session_attributes {
> TPM2_SA_CONTINUE_SESSION = BIT(0),
> + TPM2_SA_AUDIT_EXCLUSIVE = BIT(1),
> + TPM2_SA_AUDIT_RESET = BIT(3),
> + TPM2_SA_DECRYPT = BIT(5),
> + TPM2_SA_ENCRYPT = BIT(6),
> + TPM2_SA_AUDIT = BIT(7),
> };
>
> struct tpm2_hash {
> @@ -469,4 +489,18 @@ static inline void tpm_buf_append_empty_auth(struct tpm_buf *buf, u32 handle)
> {
> }
> #endif
> +#ifdef CONFIG_TCG_TPM2_HMAC
> +
> +int tpm2_start_auth_session(struct tpm_chip *chip);
> +void tpm2_end_auth_session(struct tpm_chip *chip);
> +#else
> +static inline int tpm2_start_auth_session(struct tpm_chip *chip)
> +{
> + return 0;
> +}
> +static inline void tpm2_end_auth_session(struct tpm_chip *chip)
> +{
> +}
> +#endif /* CONFIG_TCG_TPM2_HMAC */
> +
> #endif

This is IMHO now also good enough.

Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>

BR, Jarkko