/* tpm2.c - Supporting TPM routines for the IBM TSS
 * Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
 *
 * This file is part of GnuPG.
 *
 * GnuPG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * GnuPG is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <arpa/inet.h>

#include "tpm2.h"

#include "../common/i18n.h"
#include "../common/sexp-parse.h"

int
tpm2_start (TSS_CONTEXT **tssc)
{
  return TSS_start(tssc);
}

void
tpm2_end (TSS_CONTEXT *tssc)
{
  TSS_Delete (tssc);
}

static TPM_HANDLE
tpm2_get_parent (TSS_CONTEXT *tssc, TPM_HANDLE p)
{
  TPM_RC rc;
  TPM2B_SENSITIVE_CREATE inSensitive;
  TPM2B_PUBLIC inPublic;
  TPM_HANDLE objectHandle;

  p = tpm2_handle_int(tssc, p);
  if (tpm2_handle_mso(tssc, p, TPM_HT_PERSISTENT))
    return p;			/* should only be permanent */

  /*  assume no hierarchy auth */
  VAL_2B (inSensitive.sensitive.userAuth, size) = 0;
  /* no sensitive date for storage keys */
  VAL_2B (inSensitive.sensitive.data, size) = 0;

  /* public parameters for a P-256 EC key  */
  inPublic.publicArea.type = TPM_ALG_ECC;
  inPublic.publicArea.nameAlg = TPM_ALG_SHA256;
  VAL (inPublic.publicArea.objectAttributes) =
    TPMA_OBJECT_NODA |
    TPMA_OBJECT_SENSITIVEDATAORIGIN |
    TPMA_OBJECT_USERWITHAUTH |
    TPMA_OBJECT_DECRYPT |
    TPMA_OBJECT_RESTRICTED |
    TPMA_OBJECT_FIXEDPARENT |
    TPMA_OBJECT_FIXEDTPM;

  inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES;
  inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128;
  inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB;
  inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
  inPublic.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256;
  inPublic.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL;

  VAL_2B (inPublic.publicArea.unique.ecc.x, size) = 0;
  VAL_2B (inPublic.publicArea.unique.ecc.y, size) = 0;
  VAL_2B (inPublic.publicArea.authPolicy, size) = 0;

  rc = tpm2_CreatePrimary (tssc, p, &inSensitive, &inPublic, &objectHandle);
  if (rc)
    {
      tpm2_error (rc, "TSS_CreatePrimary");
      return 0;
    }
  return objectHandle;
}

void
tpm2_flush_handle (TSS_CONTEXT *tssc, TPM_HANDLE h)
{
  /* only flush volatile handles */
  if (tpm2_handle_mso(tssc, h, TPM_HT_PERSISTENT))
    return;

  tpm2_FlushContext(tssc, h);
}

static int
tpm2_get_hmac_handle (TSS_CONTEXT *tssc, TPM_HANDLE *handle,
		      TPM_HANDLE salt_key)
{
  TPM_RC rc;
  TPMT_SYM_DEF symmetric;

  symmetric.algorithm = TPM_ALG_AES;
  symmetric.keyBits.aes = 128;
  symmetric.mode.aes = TPM_ALG_CFB;

  rc = tpm2_StartAuthSession(tssc, salt_key, TPM_RH_NULL, TPM_SE_HMAC,
			     &symmetric, TPM_ALG_SHA256, handle, NULL);
  if (rc)
    {
      tpm2_error (rc, "TPM2_StartAuthSession");
      return GPG_ERR_CARD;
    }

  return 0;
}

static int
tpm2_pre_auth (ctrl_t ctrl, TSS_CONTEXT *tssc,
	       gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
				     char **retstr),
	       TPM_HANDLE *ah, char **auth)
{
  TPM_RC rc;
  int len;

  rc = pin_cb (ctrl, _("TPM Key Passphrase"), auth);
  if (rc)
    return rc;

  len = strlen(*auth);
  /*
   * TPMs can't accept a longer passphrase than the name algorithm.
   * We hard code the name algorithm to SHA256 so the max passphrase
   * length is 32
   */
  if (len > 32)
    {
      log_error ("Truncating Passphrase to TPM allowed 32\n");
      (*auth)[32] = '\0';
    }

  rc = tpm2_get_hmac_handle (tssc, ah, TPM_RH_NULL);

  return rc;
}

static int
tpm2_post_auth (TSS_CONTEXT *tssc, TPM_RC rc, TPM_HANDLE ah,
		char **auth, const char *cmd_str)
{
  gcry_free (*auth);
  *auth = NULL;
  if (rc)
    {
      tpm2_error (rc, cmd_str);
      tpm2_flush_handle (tssc, ah);
      switch (rc & 0xFF)
	{
	case TPM_RC_BAD_AUTH:
	case TPM_RC_AUTH_FAIL:
	  return GPG_ERR_BAD_PASSPHRASE;
	default:
	  return GPG_ERR_CARD;
	}
    }
  return 0;
}

static unsigned char *
make_tpm2_shadow_info (uint32_t parent, const char *pub, int pub_len,
                       const char *priv, int priv_len, size_t *len)
{
  gcry_sexp_t s_exp;
  char *info;

  gcry_sexp_build (&s_exp, NULL, "(%u%b%b)", parent, pub_len, pub,
		   priv_len, priv);

  *len = gcry_sexp_sprint (s_exp, GCRYSEXP_FMT_CANON, NULL, 0);
  info = xtrymalloc (*len);
  if (!info)
	  goto out;
  gcry_sexp_sprint (s_exp, GCRYSEXP_FMT_CANON, info, *len);

 out:
  gcry_sexp_release (s_exp);
  return (unsigned char *)info;
}

static gpg_error_t
parse_tpm2_shadow_info (const unsigned char *shadow_info,
                        uint32_t *parent,
                        const char **pub, int *pub_len,
                        const char **priv, int *priv_len)
{
  const unsigned char *s;
  size_t n;
  int i;

  s = shadow_info;
  if (*s != '(')
    return gpg_error (GPG_ERR_INV_SEXP);
  s++;
  n = snext (&s);
  if (!n)
    return gpg_error (GPG_ERR_INV_SEXP);
  *parent = 0;
  for (i = 0; i < n; i++)
    {
      *parent *= 10;
      *parent += atoi_1(s+i);
    }

  s += n;
  n = snext (&s);
  if (!n)
    return gpg_error (GPG_ERR_INV_SEXP);

  *pub_len = n;
  *pub = s;

  s += n;
  n = snext (&s);
  if (!n)
    return gpg_error (GPG_ERR_INV_SEXP);

  *priv_len = n;
  *priv = s;

  return 0;
}

int
tpm2_load_key (TSS_CONTEXT *tssc, const unsigned char *shadow_info,
	       TPM_HANDLE *key, TPMI_ALG_PUBLIC *type)
{
  uint32_t parent;
  TPM_HANDLE parentHandle;
  PRIVATE_2B inPrivate;
  TPM2B_PUBLIC inPublic;
  const char *pub, *priv;
  int ret, pub_len, priv_len;
  TPM_RC rc;
  BYTE *buf;
  uint32_t size;

  ret = parse_tpm2_shadow_info (shadow_info, &parent, &pub, &pub_len,
                                &priv, &priv_len);
  if (ret)
    return ret;

  parentHandle = tpm2_get_parent (tssc, parent);

  buf = (BYTE *)priv;
  size = priv_len;
  TPM2B_PRIVATE_Unmarshal ((TPM2B_PRIVATE *)&inPrivate, &buf, &size);

  buf = (BYTE *)pub;
  size = pub_len;
  TPM2B_PUBLIC_Unmarshal (&inPublic, &buf, &size, FALSE);

  *type = inPublic.publicArea.type;

  rc = tpm2_Load (tssc, parentHandle, &inPrivate, &inPublic, key,
		  TPM_RS_PW, NULL);

  tpm2_flush_handle (tssc, parentHandle);

  if (rc != TPM_RC_SUCCESS)
    {
      tpm2_error (rc, "TPM2_Load");
      return GPG_ERR_CARD;
    }

  return 0;
}

int
tpm2_sign (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
	   gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
				 char **retstr),
	   TPMI_ALG_PUBLIC type,
	   const unsigned char *digest, size_t digestlen,
	   unsigned char **r_sig, size_t *r_siglen)
{
  int ret;
  DIGEST_2B digest2b;
  TPMT_SIG_SCHEME inScheme;
  TPMT_SIGNATURE signature;
  TPM_HANDLE ah;
  char *auth;

  /* The TPM insists on knowing the digest type, so
   * calculate that from the size */
  switch (digestlen)
    {
    case 20:
      inScheme.details.rsassa.hashAlg = TPM_ALG_SHA1;
      break;
    case 32:
      inScheme.details.rsassa.hashAlg = TPM_ALG_SHA256;
      break;
    case 48:
      inScheme.details.rsassa.hashAlg = TPM_ALG_SHA384;
      break;
#ifdef TPM_ALG_SHA512
    case 64:
      inScheme.details.rsassa.hashAlg = TPM_ALG_SHA512;
      break;
#endif
    default:
      log_error ("Unknown signature digest length, cannot deduce hash type for TPM\n");
      return GPG_ERR_NO_SIGNATURE_SCHEME;
    }
  digest2b.size = digestlen;
  memcpy (digest2b.buffer, digest, digestlen);

  if (type == TPM_ALG_RSA)
    inScheme.scheme = TPM_ALG_RSASSA;
  else if (type == TPM_ALG_ECC)
    inScheme.scheme = TPM_ALG_ECDSA;
  else
    return GPG_ERR_PUBKEY_ALGO;

  ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth);
  if (ret)
    return ret;
  ret = tpm2_Sign (tssc, key, &digest2b, &inScheme, &signature, ah, auth);
  ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_Sign");
  if (ret)
    return ret;

  if (type == TPM_ALG_RSA)
    *r_siglen = VAL_2B (signature.signature.rsassa.sig, size);
  else if (type == TPM_ALG_ECC)
    *r_siglen = VAL_2B (signature.signature.ecdsa.signatureR, size)
      + VAL_2B (signature.signature.ecdsa.signatureS, size);

  *r_sig = xtrymalloc (*r_siglen);
  if (!r_sig)
    return GPG_ERR_ENOMEM;

  if (type == TPM_ALG_RSA)
    {
      memcpy (*r_sig, VAL_2B (signature.signature.rsassa.sig, buffer),
	      *r_siglen);
    }
  else if (type == TPM_ALG_ECC)
    {
      memcpy (*r_sig, VAL_2B (signature.signature.ecdsa.signatureR, buffer),
	      VAL_2B (signature.signature.ecdsa.signatureR, size));
      memcpy (*r_sig + VAL_2B (signature.signature.ecdsa.signatureR, size),
	      VAL_2B (signature.signature.ecdsa.signatureS, buffer),
	      VAL_2B (signature.signature.ecdsa.signatureS, size));
    }

  return 0;
}

static int
sexp_to_tpm2_sensitive_ecc (TPMT_SENSITIVE *s, gcry_sexp_t key)
{
  gcry_mpi_t d;
  gcry_sexp_t l;
  int rc = -1;
  size_t len;

  s->sensitiveType = TPM_ALG_ECC;
  VAL_2B (s->seedValue, size) = 0;

  l = gcry_sexp_find_token (key, "d", 0);
  if (!l)
    return rc;
  d = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
  gcry_sexp_release (l);
  len = sizeof (VAL_2B (s->sensitive.ecc, buffer));
  rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (s->sensitive.ecc, buffer),
		       len, &len, d);
  VAL_2B (s->sensitive.ecc, size) = len;
  gcry_mpi_release (d);

  return rc;
}

/* try to match the libgcrypt curve names to known TPM parameters.
 *
 * As of 2018 the TCG defined curves are only NIST
 * (192,224,256,384,521) Barreto-Naehring (256,638) and the Chinese
 * SM2 (256), which means only the NIST ones overlap with libgcrypt */
static struct {
  const char *name;
  TPMI_ECC_CURVE c;
} tpm2_curves[] = {
  { "NIST P-192", TPM_ECC_NIST_P192 },
  { "prime192v1", TPM_ECC_NIST_P192 },
  { "secp192r1", TPM_ECC_NIST_P192 },
  { "nistp192", TPM_ECC_NIST_P192 },
  { "NIST P-224", TPM_ECC_NIST_P224 },
  { "secp224r1", TPM_ECC_NIST_P224 },
  { "nistp224", TPM_ECC_NIST_P224 },
  { "NIST P-256", TPM_ECC_NIST_P256 },
  { "prime256v1", TPM_ECC_NIST_P256 },
  { "secp256r1", TPM_ECC_NIST_P256 },
  { "nistp256", TPM_ECC_NIST_P256 },
  { "NIST P-384", TPM_ECC_NIST_P384 },
  { "secp384r1", TPM_ECC_NIST_P384 },
  { "nistp384", TPM_ECC_NIST_P384 },
  { "NIST P-521", TPM_ECC_NIST_P521 },
  { "secp521r1", TPM_ECC_NIST_P521 },
  { "nistp521", TPM_ECC_NIST_P521 },
};

static int
tpm2_ecc_curve (const char *curve_name, TPMI_ECC_CURVE *c)
{
  int i;

  for (i = 0; i < DIM (tpm2_curves); i++)
    if (strcmp (tpm2_curves[i].name, curve_name) == 0)
      break;
  if (i == DIM (tpm2_curves))
    {
      log_error ("curve %s does not match any available TPM curves\n", curve_name);
      return GPG_ERR_UNKNOWN_CURVE;
    }

  *c = tpm2_curves[i].c;

  return 0;
}

static int
sexp_to_tpm2_public_ecc (TPMT_PUBLIC *p, gcry_sexp_t key)
{
  const char *q;
  gcry_sexp_t l = NULL;
  int rc;
  size_t len;
  TPMI_ECC_CURVE curve;
  char *curve_name = NULL;

  l = gcry_sexp_find_token (key, "curve", 0);
  if (!l)
    {
      rc = GPG_ERR_NO_PUBKEY;
      goto leave;
    }
  curve_name = gcry_sexp_nth_string (l, 1);
  if (!curve_name)
    {
      rc = GPG_ERR_INV_CURVE;
      goto leave;
    }
  rc = tpm2_ecc_curve (curve_name, &curve);
  if (rc)
    goto leave;

  gcry_sexp_release (l);
  l = gcry_sexp_find_token (key, "q", 0);
  if (!l)
    {
      rc = GPG_ERR_NO_PUBKEY;
      goto leave;
    }
  q = gcry_sexp_nth_data (l, 1, &len);
  /* This is a point representation, the first byte tells you what
   * type.  The only format we understand is uncompressed (0x04)
   * which has layout 0x04 | x | y */
  if (!q || len < 2 || q[0] != 0x04)
    {
      log_error ("tss: point format for q is not uncompressed\n");
      rc = GPG_ERR_BAD_PUBKEY;
      goto leave;
    }
  q++;
  len--;
  /* now should have to equal sized big endian point numbers */
  if ((len & 0x01) == 1)
    {
      log_error ("tss: point format for q has incorrect length\n");
      rc = GPG_ERR_BAD_PUBKEY;
      goto leave;
    }
  len >>= 1; /* Compute length of one coordinate.  */

  p->type = TPM_ALG_ECC;
  p->nameAlg = TPM_ALG_SHA256;
  VAL (p->objectAttributes) = TPMA_OBJECT_NODA |
    TPMA_OBJECT_SIGN |
    TPMA_OBJECT_DECRYPT |
    TPMA_OBJECT_USERWITHAUTH;
  VAL_2B (p->authPolicy, size) = 0;
  p->parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL;
  p->parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
  p->parameters.eccDetail.curveID = curve;
  p->parameters.eccDetail.kdf.scheme = TPM_ALG_NULL;
  memcpy (VAL_2B (p->unique.ecc.x, buffer), q, len);
  VAL_2B (p->unique.ecc.x, size) = len;
  memcpy (VAL_2B (p->unique.ecc.y, buffer), q + len, len);
  VAL_2B (p->unique.ecc.y, size) = len;

 leave:
  gcry_free (curve_name);
  gcry_sexp_release (l);
  return rc;
}

static int
sexp_to_tpm2_sensitive_rsa (TPMT_SENSITIVE *s, gcry_sexp_t key)
{
  gcry_mpi_t p;
  gcry_sexp_t l;
  int rc = -1;
  size_t len;

  s->sensitiveType = TPM_ALG_RSA;
  VAL_2B (s->seedValue, size) = 0;

  l = gcry_sexp_find_token (key, "p", 0);
  if (!l)
    return rc;
  p = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
  gcry_sexp_release (l);
  len = sizeof (VAL_2B (s->sensitive.rsa, buffer));
  rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (s->sensitive.rsa, buffer),
		       len, &len, p);
  VAL_2B (s->sensitive.rsa, size) = len;
  gcry_mpi_release (p);

  return rc;
}

static int
sexp_to_tpm2_public_rsa (TPMT_PUBLIC *p, gcry_sexp_t key)
{
  gcry_mpi_t n, e;
  gcry_sexp_t l;
  int rc = -1, i;
  size_t len;
  /* longer than an int */
  unsigned char ebuf[5];
  uint32_t exp = 0;

  p->type = TPM_ALG_RSA;
  p->nameAlg = TPM_ALG_SHA256;
  VAL (p->objectAttributes) = TPMA_OBJECT_NODA |
    TPMA_OBJECT_DECRYPT |
    TPMA_OBJECT_SIGN |
    TPMA_OBJECT_USERWITHAUTH;
  VAL_2B (p->authPolicy, size) = 0;
  p->parameters.rsaDetail.symmetric.algorithm = TPM_ALG_NULL;
  p->parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL;

  l = gcry_sexp_find_token (key, "n", 0);
  if (!l)
    return rc;
  n = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
  gcry_sexp_release (l);
  len = sizeof (VAL_2B (p->unique.rsa, buffer));
  p->parameters.rsaDetail.keyBits = gcry_mpi_get_nbits (n);
  rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (p->unique.rsa, buffer),
		       len, &len, n);
  VAL_2B (p->unique.rsa, size) = len;
  gcry_mpi_release (n);
  if (rc)
    return rc;
  rc = -1;
  l = gcry_sexp_find_token (key, "e", 0);
  if (!l)
    return rc;
  e = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
  gcry_sexp_release (l);
  len = sizeof (ebuf);
  rc = gcry_mpi_print (GCRYMPI_FMT_USG, ebuf, len, &len, e);
  gcry_mpi_release (e);
  if (rc)
    return rc;
  if (len > 4)
    return -1;

  /* MPI are simply big endian integers, so convert to uint32 */
  for (i = 0; i < len; i++)
    {
      exp <<= 8;
      exp += ebuf[i];
    }
  if (exp == 0x10001)
    p->parameters.rsaDetail.exponent = 0;
  else
    p->parameters.rsaDetail.exponent = exp;
  return 0;
}

static int
sexp_to_tpm2(TPMT_PUBLIC *p, TPMT_SENSITIVE *s, gcry_sexp_t s_skey)
{
  gcry_sexp_t l1, l2;
  int rc = -1;

  /* find the value of (private-key */
  l1 = gcry_sexp_nth (s_skey, 1);
  if (!l1)
    return rc;

  l2 = gcry_sexp_find_token (l1, "rsa", 0);
  if (l2)
    {
      rc = sexp_to_tpm2_public_rsa (p, l2);
      if (!rc)
	rc = sexp_to_tpm2_sensitive_rsa (s, l2);
    }
  else
    {
      l2 = gcry_sexp_find_token (l1, "ecc", 0);
      if (!l2)
	goto out;
      rc = sexp_to_tpm2_public_ecc (p, l2);
      if (!rc)
	rc = sexp_to_tpm2_sensitive_ecc (s, l2);
    }

  gcry_sexp_release (l2);

 out:
  gcry_sexp_release (l1);
  return rc;
}

/* copied from TPM implementation code */
static TPM_RC
tpm2_ObjectPublic_GetName (NAME_2B *name,
			   TPMT_PUBLIC *tpmtPublic)
{
  TPM_RC rc = 0;
  uint16_t written = 0;
  TPMT_HA digest;
  uint32_t sizeInBytes;
  INT32 size = MAX_RESPONSE_SIZE;
  uint8_t buffer[MAX_RESPONSE_SIZE];
  uint8_t *buffer1 = buffer;
  TPMI_ALG_HASH nameAlgNbo;
  int length;

  /* marshal the TPMT_PUBLIC */
  rc = TSS_TPMT_PUBLIC_Marshal (tpmtPublic, &written, &buffer1, &size);
  if (rc)
    goto leave;

  /* hash the public area */
  length = TSS_GetDigestSize (tpmtPublic->nameAlg);
  if (length < 0)
    {
      rc = TPM_RC_VALUE;
      goto leave;
    }
  sizeInBytes = length;
  digest.hashAlg = tpmtPublic->nameAlg;       /* Name digest algorithm */

  /* generate the TPMT_HA */
  rc = TSS_Hash_Generate (&digest, written, buffer, 0, NULL);
  if (rc)
    goto leave;

  /* copy the digest */
  memcpy (name->name + sizeof (TPMI_ALG_HASH),
          (uint8_t *)&digest.digest, sizeInBytes);
  /* copy the hash algorithm */
  nameAlgNbo = htons (tpmtPublic->nameAlg);
  memcpy (name->name, (uint8_t *)&nameAlgNbo, sizeof (TPMI_ALG_HASH));
  /* set the size */
  name->size = sizeInBytes + sizeof (TPMI_ALG_HASH);

 leave:
  return rc;
}

/*
 * Cut down version of Part 4 Supporting Routines 7.6.3.10
 *
 * Hard coded to symmetrically encrypt with aes128 as the inner
 * wrapper and no outer wrapper but with a prototype that allows
 * drop in replacement with a tss equivalent
 */
TPM_RC tpm2_SensitiveToDuplicate (TPMT_SENSITIVE *s,
				  NAME_2B *name,
				  TPM_ALG_ID nalg,
				  TPMT_SYM_DEF_OBJECT *symdef,
				  DATA_2B *innerkey,
				  PRIVATE_2B *p)
{
  BYTE *buf = p->buffer;

  p->size = 0;
  memset (p, 0, sizeof (*p));

  /* hard code AES CFB */
  if (symdef->algorithm == TPM_ALG_AES
      && symdef->mode.aes == TPM_ALG_CFB)
    {
      TPMT_HA hash;
      int hlen;
      BYTE *digest;
      BYTE *s2b;
      int32_t size;
      unsigned char null_iv[AES_128_BLOCK_SIZE_BYTES];
      UINT16 bsize, written = 0;
      gcry_cipher_hd_t hd;

      /* WARNING: don't use the static null_iv trick here:
       * the AES routines alter the passed in iv */
      memset (null_iv, 0, sizeof (null_iv));

      hlen = TSS_GetDigestSize (nalg);
      if (hlen < 0)
        {
          log_error ("%s: unknown symmetric algo id %d\n",
                     "TSS_GetDigestSize", (int)nalg);
          return TPM_RC_SYMMETRIC;
        }

      /* reserve space for hash before the encrypted sensitive */
      digest = buf;
      bsize = sizeof (uint16_t /* TPM2B.size */) + hlen;
      p->size += bsize;
      s2b = digest + bsize;

      /* marshal the digest size */
      bsize = hlen;
      size = 2;
      TSS_UINT16_Marshal (&bsize, &written, &buf, &size);

      /* marshal the unencrypted sensitive in place */
      size = sizeof (*s);
      bsize = 0;
      buf = s2b + offsetof (TPM2B, buffer);
      TSS_TPMT_SENSITIVE_Marshal (s, &bsize, &buf, &size);
      buf = s2b;
      size = 2;
      TSS_UINT16_Marshal (&bsize, &written, &buf, &size);

      bsize = bsize + sizeof (uint16_t /* TPM2B.size */);
      p->size += bsize;

      /* compute hash of unencrypted marshalled sensitive and
       * write to the digest buffer */
      hash.hashAlg = nalg;
      TSS_Hash_Generate (&hash, bsize, s2b,
			 name->size, name->name,
			 0, NULL);
      memcpy (digest + offsetof (TPM2B, buffer), &hash.digest, hlen);
      gcry_cipher_open (&hd, GCRY_CIPHER_AES128,
			GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
      gcry_cipher_setiv (hd, null_iv, sizeof (null_iv));
      gcry_cipher_setkey (hd, innerkey->buffer, innerkey->size);
      /* encrypt the hash and sensitive in-place */
      gcry_cipher_encrypt (hd, p->buffer, p->size, NULL, 0);
      gcry_cipher_close (hd);

    }
  else if (symdef->algorithm == TPM_ALG_NULL)
    {
      /* Code is for debugging only, should never be used in production */
      BYTE *s2b = buf;
      int32_t size = sizeof (*s);
      UINT16 bsize = 0, written = 0;

      log_error ("Secret key sent to TPM unencrypted\n");
      buf = s2b + offsetof (TPM2B, buffer);

      /* marshal the unencrypted sensitive in place */
      TSS_TPMT_SENSITIVE_Marshal (s, &bsize, &buf, &size);
      buf = s2b;
      size = 2;
      TSS_UINT16_Marshal (&bsize, &written, &buf, &size);

      p->size += bsize + sizeof (uint16_t /* TPM2B.size */);
    }
  else
    {
      log_error ("tss: Unknown symmetric algorithm\n");
      return TPM_RC_SYMMETRIC;
    }

  return TPM_RC_SUCCESS;
}

int
tpm2_import_key (ctrl_t ctrl, TSS_CONTEXT *tssc,
		 gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
				       char **retstr),
		 unsigned char **shadow_info, size_t *shadow_len,
		 gcry_sexp_t s_skey, unsigned long parent)
{
  TPM_HANDLE parentHandle;
  DATA_2B encryptionKey;
  TPM2B_PUBLIC objectPublic;
  PRIVATE_2B duplicate;
  ENCRYPTED_SECRET_2B inSymSeed;
  TPMT_SYM_DEF_OBJECT symmetricAlg;
  PRIVATE_2B outPrivate;
  NAME_2B name;
  const int aes_key_bits = 128;
  const int aes_key_bytes = aes_key_bits/8;

  TPMT_SENSITIVE s;
  TPM_HANDLE ah;
  TPM_RC rc;

  uint32_t size;
  uint16_t u16len;
  size_t len;
  int dlen;
  BYTE *buffer;
  int ret;
  char *passphrase;

  char pub[sizeof (TPM2B_PUBLIC)];
  int pub_len;
  char priv[sizeof (TPM2B_PRIVATE)];
  int priv_len;

  if (parent == 0)
    parent = EXT_TPM_RH_OWNER;

  ret = sexp_to_tpm2 (&objectPublic.publicArea, &s, s_skey);
  if (ret)
    {
      log_error ("Failed to parse Key s-expression: key corrupt?\n");
      return ret;
    }

  /* add an authorization password to the key which the TPM will check */

  ret = pin_cb (ctrl,
           _("Please enter the TPM Authorization passphrase for the key."),
                &passphrase);
  if (ret)
    return ret;
  len = strlen(passphrase);
  dlen = TSS_GetDigestSize(objectPublic.publicArea.nameAlg);
  if (dlen < 0)
    {
      log_error ("%s: error getting digest size\n", "TSS_GetDigestSize");
      return GPG_ERR_DIGEST_ALGO;
    }
  if (len > dlen)
    {
      len = dlen;
      log_info ("tss: truncating Passphrase to TPM allowed size of %zu\n", len);
    }
  VAL_2B (s.authValue, size) = len;
  memcpy (VAL_2B (s.authValue, buffer), passphrase, len);

  /* We're responsible for securing the data in transmission to the
   * TPM here.  The TPM provides parameter encryption via a session,
   * but only for the first parameter.  For TPM2_Import, the first
   * parameter is a symmetric key used to encrypt the sensitive data,
   * so we must populate this key with random value and encrypt the
   * sensitive data with it */
  parentHandle = tpm2_get_parent (tssc, parent);
  tpm2_ObjectPublic_GetName (&name, &objectPublic.publicArea);
  gcry_randomize (encryptionKey.buffer,
                 aes_key_bytes, GCRY_STRONG_RANDOM);
  encryptionKey.size = aes_key_bytes;

  /* set random symSeed */
  inSymSeed.size = 0;
  symmetricAlg.algorithm = TPM_ALG_AES;
  symmetricAlg.keyBits.aes = aes_key_bits;
  symmetricAlg.mode.aes = TPM_ALG_CFB;

  tpm2_SensitiveToDuplicate (&s, &name, objectPublic.publicArea.nameAlg,
			     &symmetricAlg, &encryptionKey, &duplicate);

  /* use salted parameter encryption to hide the key.  First we read
   * the public parameters of the parent key and use them to agree an
   * encryption for the first parameter */
  rc = tpm2_get_hmac_handle (tssc, &ah, parentHandle);
  if (rc)
    {
      tpm2_flush_handle (tssc, parentHandle);
      return GPG_ERR_CARD;
    }

  rc = tpm2_Import (tssc, parentHandle, &encryptionKey, &objectPublic,
		    &duplicate, &inSymSeed, &symmetricAlg, &outPrivate,
		    ah, NULL);
  tpm2_flush_handle (tssc, parentHandle);
  if (rc)
    {
      tpm2_error (rc, "TPM2_Import");
      /* failure means auth handle is not flushed */
      tpm2_flush_handle (tssc, ah);

      if ((rc & 0xbf) == TPM_RC_VALUE)
	{
	  log_error ("TPM cannot import RSA key: wrong size");
	  return GPG_ERR_UNSUPPORTED_ALGORITHM;
	}
      else if ((rc & 0xbf) == TPM_RC_CURVE)
	{
	  log_error ("TPM cannot import requested curve");
	  return GPG_ERR_UNKNOWN_CURVE;
	}
      return GPG_ERR_CARD;
    }

  size = sizeof (pub);
  buffer = pub;
  u16len = 0;
  TSS_TPM2B_PUBLIC_Marshal (&objectPublic,
                            &u16len, &buffer, &size);
  pub_len = u16len;

  size = sizeof (priv);
  buffer = priv;
  u16len = 0;
  TSS_TPM2B_PRIVATE_Marshal ((TPM2B_PRIVATE *)&outPrivate,
			     &u16len, &buffer, &size);
  priv_len = u16len;

  *shadow_info = make_tpm2_shadow_info (parent, pub, pub_len,
					priv, priv_len, shadow_len);
  return rc;
}

int
tpm2_ecc_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
		  gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
					char **retstr),
		  const char *ciphertext, int ciphertext_len,
		  char **decrypt, size_t *decrypt_len)
{
  TPM2B_ECC_POINT inPoint;
  TPM2B_ECC_POINT outPoint;
  TPM_HANDLE ah;
  char *auth;
  size_t len;
  int ret;

#if defined(TPM2_MAX_ECC_KEY_BYTES) /* Intel stack */
  if (ciphertext_len > 2*TPM2_MAX_ECC_KEY_BYTES + 1)
    return GPG_ERR_TOO_LARGE;
#elif defined(MAX_ECC_KEY_BYTES)    /* IBM stack */
  if (ciphertext_len > 2*MAX_ECC_KEY_BYTES + 1)
    return GPG_ERR_TOO_LARGE;
#else
# error TMP2 header are not correctly installed
#endif

  /* This isn't really a decryption per se.  The ciphertext actually
   * contains an EC Point which we must multiply by the private key number.
   *
   * The reason is to generate a diffie-hellman agreement on a shared
   * point.  This shared point is then used to generate the per
   * session encryption key.
   */
  if (ciphertext[0] != 0x04)
    {
      log_error ("Decryption Shared Point format is not uncompressed\n");
      return GPG_ERR_ENCODING_PROBLEM;
    }
  if ((ciphertext_len & 0x01) != 1)
    {
      log_error ("Decryption Shared Point has incorrect length\n");
      return GPG_ERR_ENCODING_PROBLEM;
    }
  len = ciphertext_len >> 1;

  memcpy (VAL_2B (inPoint.point.x, buffer), ciphertext + 1, len);
  VAL_2B (inPoint.point.x, size) = len;
  memcpy (VAL_2B (inPoint.point.y, buffer), ciphertext + 1 + len, len);
  VAL_2B (inPoint.point.y, size) = len;

  ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth);
  if (ret)
    return ret;
  ret = tpm2_ECDH_ZGen (tssc, key, &inPoint, &outPoint, ah, auth);
  ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_ECDH_ZGen");
  if (ret)
    return ret;

  *decrypt_len = VAL_2B (outPoint.point.x, size) +
	  VAL_2B (outPoint.point.y, size) + 1;
  *decrypt = xtrymalloc (*decrypt_len);
  (*decrypt)[0] = 0x04;
  memcpy (*decrypt + 1, VAL_2B (outPoint.point.x, buffer),
	  VAL_2B (outPoint.point.x, size));
  memcpy (*decrypt + 1 + VAL_2B (outPoint.point.x, size),
	  VAL_2B (outPoint.point.y, buffer),
	  VAL_2B (outPoint.point.y, size));

  return 0;
}

int
tpm2_rsa_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
		  gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
					char **retstr),
		  const char *ciphertext, int ciphertext_len,
		  char **decrypt, size_t *decrypt_len)
{
  int ret;
  PUBLIC_KEY_RSA_2B cipherText;
  TPMT_RSA_DECRYPT inScheme;
  PUBLIC_KEY_RSA_2B message;
  TPM_HANDLE ah;
  char *auth;

#if defined(TPM2_MAX_RSA_KEY_BYTES)  /* Intel stack */
  if (ciphertext_len > TPM2_MAX_RSA_KEY_BYTES)
    return GPG_ERR_TOO_LARGE;
#elif defined(MAX_RSA_KEY_BYTES)     /* IBM stack */
  if (ciphertext_len > MAX_RSA_KEY_BYTES)
    return GPG_ERR_TOO_LARGE;
#else
# error TMP2 header are not correctly installed
#endif

  inScheme.scheme = TPM_ALG_RSAES;
  /*
   * apparent gcrypt error: occasionally rsa ciphertext will
   * be one byte too long and have a leading zero
   */
  if ((ciphertext_len & 1) == 1 && ciphertext[0] == 0)
    {
      log_info ("Fixing Wrong Ciphertext size %d\n", ciphertext_len);
      ciphertext_len--;
      ciphertext++;
    }
  cipherText.size = ciphertext_len;
  memcpy (cipherText.buffer, ciphertext, ciphertext_len);

  ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth);
  if (ret)
    return ret;
  ret = tpm2_RSA_Decrypt (tssc, key, &cipherText, &inScheme, &message,
			  ah, auth, TPMA_SESSION_ENCRYPT);
  ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_RSA_Decrypt");
  if (ret)
    return ret;

  *decrypt_len = message.size;
  *decrypt = xtrymalloc (message.size);
  memcpy (*decrypt, message.buffer, message.size);

  return 0;
}
