On Tue Jan 2, 2024 at 7:04 PM EET, James Bottomley wrote:
> The session handling code uses a "salted" session, meaning a session
> whose salt is encrypted to the public part of another TPM key so an
> observer cannot obtain it (and thus deduce the session keys).  This
> patch creates and context saves in the tpm_chip area the primary key
> of the NULL hierarchy for this purpose.
>
> Signed-off-by: James Bottomley <[email protected]>
>
> ---
> v6: split out of original HMAC patch update config name
> ---
>  drivers/char/tpm/Kconfig         |  11 ++
>  drivers/char/tpm/Makefile        |   1 +
>  drivers/char/tpm/tpm.h           |  10 ++
>  drivers/char/tpm/tpm2-cmd.c      |   5 +
>  drivers/char/tpm/tpm2-sessions.c | 276 +++++++++++++++++++++++++++++++
>  include/linux/tpm.h              |  49 ++++++
>  6 files changed, 352 insertions(+)
>  create mode 100644 drivers/char/tpm/tpm2-sessions.c
>
> diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> index 927088b2c3d3..e3c39a83171b 100644
> --- a/drivers/char/tpm/Kconfig
> +++ b/drivers/char/tpm/Kconfig
> @@ -27,6 +27,17 @@ menuconfig TCG_TPM
>  
>  if TCG_TPM
>  
> +config TCG_TPM2_HMAC
> +     bool "Use encrypted and HMACd transactions on the TPM bus"
> +     default y
> +     help
> +          Setting this causes us to deploy a scheme which uses request
> +       and response HMACs in addition to encryption for
> +       communicating with the TPM to prevent or detect bus snooping
> +       and interposer attacks (see tpm-security.rst).  Saying Y
> +       here adds some encryption overhead to all kernel to TPM
> +       transactions.
> +
>  config HW_RANDOM_TPM
>       bool "TPM HW Random Number Generator support"
>       depends on TCG_TPM && HW_RANDOM && !(TCG_TPM=y && HW_RANDOM=m)
> diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
> index ad3594e383e1..4c695b0388f3 100644
> --- a/drivers/char/tpm/Makefile
> +++ b/drivers/char/tpm/Makefile
> @@ -17,6 +17,7 @@ tpm-y += eventlog/tpm1.o
>  tpm-y += eventlog/tpm2.o
>  tpm-y += tpm-buf.o
>  
> +tpm-$(CONFIG_TCG_TPM2_HMAC) += tpm2-sessions.o
>  tpm-$(CONFIG_ACPI) += tpm_ppi.o eventlog/acpi.o
>  tpm-$(CONFIG_EFI) += eventlog/efi.o
>  tpm-$(CONFIG_OF) += eventlog/of.o
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index cbc9d1e2974d..6b8b9956ba69 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -321,4 +321,14 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
>  void tpm_bios_log_teardown(struct tpm_chip *chip);
>  int tpm_dev_common_init(void);
>  void tpm_dev_common_exit(void);
> +
> +#ifdef CONFIG_TCG_TPM2_HMAC
> +int tpm2_sessions_init(struct tpm_chip *chip);
> +#else
> +static inline int tpm2_sessions_init(struct tpm_chip *chip)
> +{
> +     return 0;
> +}
> +#endif
> +
>  #endif
> diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
> index 93545be190a5..b0e72fb563d9 100644
> --- a/drivers/char/tpm/tpm2-cmd.c
> +++ b/drivers/char/tpm/tpm2-cmd.c
> @@ -759,6 +759,11 @@ int tpm2_auto_startup(struct tpm_chip *chip)
>               rc = 0;
>       }
>  
> +     if (rc)
> +             goto out;
> +
> +     rc = tpm2_sessions_init(chip);
> +
>  out:
>       /*
>        * Infineon TPM in field upgrade mode will return no data for the number
> diff --git a/drivers/char/tpm/tpm2-sessions.c 
> b/drivers/char/tpm/tpm2-sessions.c
> new file mode 100644
> index 000000000000..ef66c28bb332
> --- /dev/null
> +++ b/drivers/char/tpm/tpm2-sessions.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/*
> + * Copyright (C) 2018 [email protected]
> + *
> + */
> +
> +#include "tpm.h"
> +
> +#include <asm/unaligned.h>
> +
> +#include <crypto/aes.h>
> +
> +/* if you change to AES256, you only need change this */
> +#define AES_KEYBYTES AES_KEYSIZE_128
> +
> +#define AES_KEYBITS  (AES_KEYBYTES*8)
> +
> +static int parse_create_primary(struct tpm_chip *chip, struct tpm_buf *buf,
> +                             u32 *nullkey)

nit: i'd add tpm2_ in from as other functions.

> +{
> +     struct tpm_header *head = (struct tpm_header *)buf->data;
> +     off_t offset_r = TPM_HEADER_SIZE, offset_t;
> +     u16 len = TPM_HEADER_SIZE;
> +     u32 tot_len = be32_to_cpu(head->length);
> +     u32 val, parm_len;
> +
> +     *nullkey = tpm_buf_read_u32(buf, &offset_r);
> +     parm_len = tpm_buf_read_u32(buf, &offset_r);
> +     /*
> +      * parm_len doesn't include the header, but all the other
> +      * lengths and offsets do, so add it to parm len to make
> +      * the comparisons easier
> +      */
> +     parm_len += TPM_HEADER_SIZE;
> +
> +     if (parm_len + 8 > tot_len)
> +             return -EINVAL;
> +     len = tpm_buf_read_u16(buf, &offset_r);
> +     offset_t = offset_r;
> +     /* now we have the public area, compute the name of the object */
> +     put_unaligned_be16(TPM_ALG_SHA256, chip->tpmkeyname);
> +     sha256(&buf->data[offset_r], len, chip->tpmkeyname + 2);
> +
> +     /* validate the public key */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     /* key type (must be what we asked for) */
> +     if (val != TPM_ALG_ECC)
> +             return -EINVAL;
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     /* name algorithm */
> +     if (val != TPM_ALG_SHA256)
> +             return -EINVAL;
> +     val = tpm_buf_read_u32(buf, &offset_t);
> +     /* object properties */
> +     if (val != (TPM2_OA_NO_DA |
> +                 TPM2_OA_FIXED_TPM |
> +                 TPM2_OA_FIXED_PARENT |
> +                 TPM2_OA_SENSITIVE_DATA_ORIGIN |
> +                 TPM2_OA_USER_WITH_AUTH |
> +                 TPM2_OA_DECRYPT |
> +                 TPM2_OA_RESTRICTED))
> +             return -EINVAL;
> +     /* auth policy (empty) */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != 0)
> +             return -EINVAL;
> +     /* symmetric key parameters */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != TPM_ALG_AES)
> +             return -EINVAL;
> +     /* symmetric key length */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != AES_KEYBITS)
> +             return -EINVAL;
> +     /* symmetric encryption scheme */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != TPM_ALG_CFB)
> +             return -EINVAL;
> +     /* signing scheme */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != TPM_ALG_NULL)
> +             return -EINVAL;
> +     /* ECC Curve */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != TPM2_ECC_NIST_P256)
> +             return -EINVAL;
> +     /* KDF Scheme */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != TPM_ALG_NULL)
> +             return -EINVAL;
> +     /* x point */
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != EC_PT_SZ)
> +             return -EINVAL;
> +     memcpy(chip->ec_point_x, &buf->data[offset_t], val);
> +     offset_t += val;
> +     val = tpm_buf_read_u16(buf, &offset_t);
> +     if (val != EC_PT_SZ)
> +             return -EINVAL;
> +     memcpy(chip->ec_point_y, &buf->data[offset_t], val);
> +     offset_t += val;
> +
> +     /* original length of the whole TPM2B */
> +     offset_r += len;
> +
> +     /* should have exactly consumed the TPM2B public structure */
> +     if (offset_t != offset_r)
> +             return -EINVAL;
> +     if (offset_r > parm_len)
> +             return -EINVAL;
> +     /* creation data (skip) */
> +     len = tpm_buf_read_u16(buf, &offset_r);
> +     offset_r += len;
> +     if (offset_r > parm_len)
> +             return -EINVAL;
> +     /* creation digest (must be sha256) */
> +     len = tpm_buf_read_u16(buf, &offset_r);
> +     offset_r += len;
> +     if (len != SHA256_DIGEST_SIZE || offset_r > parm_len)
> +             return -EINVAL;
> +     /* TPMT_TK_CREATION follows */
> +     /* tag, must be TPM_ST_CREATION (0x8021) */
> +     val = tpm_buf_read_u16(buf, &offset_r);
> +     if (val != TPM2_ST_CREATION || offset_r > parm_len)
> +             return -EINVAL;
> +     /* hierarchy (must be NULL) */
> +     val = tpm_buf_read_u32(buf, &offset_r);
> +     if (val != TPM2_RH_NULL || offset_r > parm_len)
> +             return -EINVAL;
> +     /* the ticket digest HMAC (might not be sha256) */
> +     len = tpm_buf_read_u16(buf, &offset_r);
> +     offset_r += len;
> +     if (offset_r > parm_len)
> +             return -EINVAL;
> +     /*
> +      * finally we have the name, which is a sha256 digest plus a 2
> +      * byte algorithm type
> +      */
> +     len = tpm_buf_read_u16(buf, &offset_r);
> +     if (offset_r + len != parm_len + 8)
> +             return -EINVAL;
> +     if (len != SHA256_DIGEST_SIZE + 2)
> +             return -EINVAL;
> +
> +     if (memcmp(chip->tpmkeyname, &buf->data[offset_r],
> +                SHA256_DIGEST_SIZE + 2) != 0) {
> +             dev_err(&chip->dev, "NULL Seed name comparison failed\n");
> +             return -EINVAL;
> +     }
> +
> +     return 0;
> +}
> +
> +static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy, u32 
> *handle)
> +{
> +     int rc;
> +     struct tpm_buf buf;
> +     struct tpm_buf template;
> +
> +     rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY);
> +     if (rc)
> +             return rc;
> +
> +     rc = tpm_buf_init_sized(&template);
> +     if (rc) {
> +             tpm_buf_destroy(&buf);
> +             return rc;
> +     }
> +
> +     /*
> +      * create the template.  Note: in order for userspace to
> +      * verify the security of the system, it will have to create
> +      * and certify this NULL primary, meaning all the template
> +      * parameters will have to be identical, so conform exactly to
> +      * the TCG TPM v2.0 Provisioning Guidance for the SRK ECC
> +      * key
> +      */
> +
> +     /* key type */
> +     tpm_buf_append_u16(&template, TPM_ALG_ECC);
> +     /* name algorithm */
> +     tpm_buf_append_u16(&template, TPM_ALG_SHA256);
> +     /* object properties */
> +     tpm_buf_append_u32(&template, TPM2_OA_NO_DA |
> +                        TPM2_OA_FIXED_TPM |
> +                        TPM2_OA_FIXED_PARENT |
> +                        TPM2_OA_SENSITIVE_DATA_ORIGIN |
> +                        TPM2_OA_USER_WITH_AUTH |
> +                        TPM2_OA_DECRYPT |
> +                        TPM2_OA_RESTRICTED);
> +     /* sauth policy (empty) */
> +     tpm_buf_append_u16(&template, 0);
> +
> +     /* BEGIN parameters: key specific; for ECC*/
> +     /* symmetric algorithm */
> +     tpm_buf_append_u16(&template, TPM_ALG_AES);
> +     /* bits for symmetric algorithm */
> +     tpm_buf_append_u16(&template, 128);
> +     /* algorithm mode (must be CFB) */
> +     tpm_buf_append_u16(&template, TPM_ALG_CFB);
> +     /* scheme (NULL means any scheme) */
> +     tpm_buf_append_u16(&template, TPM_ALG_NULL);
> +     /* ECC Curve ID */
> +     tpm_buf_append_u16(&template, TPM2_ECC_NIST_P256);
> +     /* KDF Scheme */
> +     tpm_buf_append_u16(&template, TPM_ALG_NULL);
> +     /* unique: key specific; for ECC it is two points */
> +     tpm_buf_append_u16(&template, 0);
> +     tpm_buf_append_u16(&template, 0);
> +     /* END parameters */
> +
> +     /* primary handle */
> +     tpm_buf_append_u32(&buf, hierarchy);
> +     tpm_buf_append_empty_auth(&buf, TPM2_RS_PW);
> +     /* sensitive create size is 4 for two empty buffers */
> +     tpm_buf_append_u16(&buf, 4);
> +     /* sensitive create auth data (empty) */
> +     tpm_buf_append_u16(&buf, 0);
> +     /* sensitive create sensitive data (empty) */
> +     tpm_buf_append_u16(&buf, 0);
> +     /* the public template */
> +     tpm_buf_append(&buf, template.data, template.length);
> +     tpm_buf_destroy(&template);
> +     /* outside info (empty) */
> +     tpm_buf_append_u16(&buf, 0);
> +     /* creation PCR (none) */
> +     tpm_buf_append_u32(&buf, 0);
> +
> +     rc = tpm_transmit_cmd(chip, &buf, 0,
> +                           "attempting to create NULL primary");
> +
> +     if (rc == TPM2_RC_SUCCESS)
> +             rc = parse_create_primary(chip, &buf, handle);
> +
> +     tpm_buf_destroy(&buf);
> +
> +     return rc;
> +}
> +
> +static int tpm2_create_null_primary(struct tpm_chip *chip)
> +{
> +     u32 nullkey;
> +     int rc;
> +
> +     rc = tpm2_create_primary(chip, TPM2_RH_NULL, &nullkey);
> +
> +     if (rc == TPM2_RC_SUCCESS) {
> +             unsigned int offset = 0; /* dummy offset for tpmkeycontext */
> +
> +             rc = tpm2_save_context(chip, nullkey, chip->tpmkeycontext,
> +                                    sizeof(chip->tpmkeycontext), &offset);
> +             tpm2_flush_context(chip, nullkey);
> +     }
> +
> +     return rc;
> +}
> +
> +/**
> + * tpm2_sessions_init() - start of day initialization for the sessions code
> + * @chip: TPM chip
> + *
> + * Derive and context save the null primary and allocate memory in the
> + * struct tpm_chip for the authorizations.
> + */
> +int tpm2_sessions_init(struct tpm_chip *chip)
> +{
> +     int rc;
> +
> +     rc = tpm2_create_null_primary(chip);
> +     if (rc)
> +             dev_err(&chip->dev, "TPM: security failed (NULL seed 
> derivation): %d\n", rc);
> +
> +     return rc;
> +}
> +EXPORT_SYMBOL(tpm2_sessions_init);
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index 6be263509e81..bdb03c093914 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -35,12 +35,15 @@ struct trusted_key_options;
>  enum tpm_algorithms {
>       TPM_ALG_ERROR           = 0x0000,
>       TPM_ALG_SHA1            = 0x0004,
> +     TPM_ALG_AES             = 0x0006,
>       TPM_ALG_KEYEDHASH       = 0x0008,
>       TPM_ALG_SHA256          = 0x000B,
>       TPM_ALG_SHA384          = 0x000C,
>       TPM_ALG_SHA512          = 0x000D,
>       TPM_ALG_NULL            = 0x0010,
>       TPM_ALG_SM3_256         = 0x0012,
> +     TPM_ALG_ECC             = 0x0023,
> +     TPM_ALG_CFB             = 0x0043,
>  };
>  
>  /*
> @@ -49,6 +52,11 @@ enum tpm_algorithms {
>   */
>  #define TPM_MAX_HASHES       5
>  
> +enum tpm2_curves {
> +     TPM2_ECC_NONE           = 0x0000,
> +     TPM2_ECC_NIST_P256      = 0x0003,
> +};
> +
>  struct tpm_digest {
>       u16 alg_id;
>       u8 digest[TPM_MAX_DIGEST_SIZE];
> @@ -116,6 +124,20 @@ struct tpm_chip_seqops {
>       const struct seq_operations *seqops;
>  };
>  
> +/* fixed define for the curve we use which is NIST_P256 */
> +#define EC_PT_SZ     32
> +
> +/*
> + * fixed define for the size of a name.  This is actually HASHALG size
> + * plus 2, so 32 for SHA256
> + */
> +#define TPM2_NAME_SIZE       34
> +
> +/*
> + * The maximum size for an object context
> + */
> +#define TPM2_MAX_CONTEXT_SIZE 4096
> +
>  struct tpm_chip {
>       struct device dev;
>       struct device devs;
> @@ -170,6 +192,14 @@ struct tpm_chip {
>  
>       /* active locality */
>       int locality;
> +
> +#ifdef CONFIG_TCG_TPM2_HMAC
> +     /* details for communication security via sessions */
> +     u8 tpmkeycontext[TPM2_MAX_CONTEXT_SIZE]; /* context for NULL seed */
> +     u8 tpmkeyname[TPM2_NAME_SIZE];           /* name of NULL seed */

these are bit convoluted imho.

Maybe simply name them as hmac_context and hmac_key_name? Then they
are somewhat self-documenting.


> +     u8 ec_point_x[EC_PT_SZ];
> +     u8 ec_point_y[EC_PT_SZ];

And also prefix these similarly with hmac.

I'm actually thinking that since there's 4 field already maybe we should
really have these all bundled to let's say "struct tpm_hmac_session" and
the parse function would take this as parameter?

Anyway at least the naming changes would be nice.

> +#endif
>  };
>  
>  #define TPM_HEADER_SIZE              10
> @@ -194,6 +224,7 @@ enum tpm2_timeouts {
>  enum tpm2_structures {
>       TPM2_ST_NO_SESSIONS     = 0x8001,
>       TPM2_ST_SESSIONS        = 0x8002,
> +     TPM2_ST_CREATION        = 0x8021,
>  };
>  
>  /* Indicates from what layer of the software stack the error comes from */
> @@ -243,6 +274,7 @@ enum tpm2_command_codes {
>  };
>  
>  enum tpm2_permanent_handles {
> +     TPM2_RH_NULL            = 0x40000007,
>       TPM2_RS_PW              = 0x40000009,
>  };
>  
> @@ -318,7 +350,11 @@ struct tpm_buf {
>  enum tpm2_object_attributes {
>       TPM2_OA_FIXED_TPM               = BIT(1),
>       TPM2_OA_FIXED_PARENT            = BIT(4),
> +     TPM2_OA_SENSITIVE_DATA_ORIGIN   = BIT(5),
>       TPM2_OA_USER_WITH_AUTH          = BIT(6),
> +     TPM2_OA_NO_DA                   = BIT(10),
> +     TPM2_OA_RESTRICTED              = BIT(16),
> +     TPM2_OA_DECRYPT                 = BIT(17),
>  };
>  
>  enum tpm2_session_attributes {
> @@ -373,6 +409,15 @@ extern int tpm_pcr_extend(struct tpm_chip *chip, u32 
> pcr_idx,
>  extern int tpm_get_random(struct tpm_chip *chip, u8 *data, size_t max);
>  extern struct tpm_chip *tpm_default_chip(void);
>  void tpm2_flush_context(struct tpm_chip *chip, u32 handle);
> +static inline void tpm_buf_append_empty_auth(struct tpm_buf *buf, u32 handle)
> +{
> +     /* simple authorization for empty auth */
> +     tpm_buf_append_u32(buf, 9);             /* total length of auth */
> +     tpm_buf_append_u32(buf, handle);
> +     tpm_buf_append_u16(buf, 0);             /* nonce len */
> +     tpm_buf_append_u8(buf, 0);              /* attributes */
> +     tpm_buf_append_u16(buf, 0);             /* hmac len */
> +}
>  #else
>  static inline int tpm_is_tpm2(struct tpm_chip *chip)
>  {
> @@ -399,5 +444,9 @@ static inline struct tpm_chip *tpm_default_chip(void)
>  {
>       return NULL;
>  }
> +
> +static inline void tpm_buf_append_empty_auth(struct tpm_buf *buf, u32 handle)
> +{
> +}
>  #endif
>  #endif

Otherwise looking pretty good.

BR, Jarkko

Reply via email to