diff mbox series

[v6,12/20] tpm: Add NULL primary creation

Message ID 20240102170408.21969-13-James.Bottomley@HansenPartnership.com (mailing list archive)
State New
Headers show
Series add integrity and security to TPM2 transactions | expand

Commit Message

James Bottomley Jan. 2, 2024, 5:04 p.m. UTC
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 <James.Bottomley@HansenPartnership.com>

---
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

Comments

Jarkko Sakkinen Jan. 3, 2024, 3:11 p.m. UTC | #1
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 <James.Bottomley@HansenPartnership.com>
>
> ---
> 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 James.Bottomley@HansenPartnership.com
> + *
> + */
> +
> +#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
James Bottomley Jan. 3, 2024, 3:25 p.m. UTC | #2
On Wed, 2024-01-03 at 17:11 +0200, Jarkko Sakkinen wrote:
> > +       u8 ec_point_x[EC_PT_SZ];
> > +       u8 ec_point_y[EC_PT_SZ];
> 
> And also prefix these similarly with hmac.

This isn't in itself an HMAC; it's the public point of the P-256 key
derived from the null seed (effectively it's a public key).  It's used
to exchange secret information with the TPM, like a salt for the HMAC
sessions but it's a thing in its own right (and is bound to the null
name).  I suppose null_key_x/y is better.

James
Jarkko Sakkinen Jan. 4, 2024, 5:56 p.m. UTC | #3
On Wed Jan 3, 2024 at 5:25 PM EET, James Bottomley wrote:
> On Wed, 2024-01-03 at 17:11 +0200, Jarkko Sakkinen wrote:
> > > +       u8 ec_point_x[EC_PT_SZ];
> > > +       u8 ec_point_y[EC_PT_SZ];
> > 
> > And also prefix these similarly with hmac.
>
> This isn't in itself an HMAC; it's the public point of the P-256 key
> derived from the null seed (effectively it's a public key).  It's used
> to exchange secret information with the TPM, like a salt for the HMAC
> sessions but it's a thing in its own right (and is bound to the null
> name).  I suppose null_key_x/y is better.
>
> James

Right, yea would add clarity (or perhaps null_ec_key_{x,y} for extra
clarity).

BR, Jarkko
diff mbox series

Patch

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 James.Bottomley@HansenPartnership.com
+ *
+ */
+
+#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)
+{
+	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 */
+	u8 ec_point_x[EC_PT_SZ];
+	u8 ec_point_y[EC_PT_SZ];
+#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