[RFC v2 2/5] agent: add tpm specific functions

James Bottomley James.Bottomley at HansenPartnership.com
Mon Mar 5 20:14:34 CET 2018


This commit adds code to handle the three specific functions needed to
make the agent TPM aware, namely the ability to load a key from shadow
information, the ability to sign a digest with that key, the ability
to decrypt with the key and the ability to import a key to the TPM.

The TPM2 is a bit of an esoteric beast, so all TPM specific callouts
are confined inside this code.  Additionaly, it requires the tss2
library to function, so the code is designed such that if the library
isn't present then all TPM functions simply fail.  This allows the
code to be compiled with TPM support, but not require that the support
library be present on the system.

Signed-off-by: James Bottomley <James.Bottomley at HansenPartnership.com>

---

v2: use gcrypt for AES functions and tidy up some logging
---
 agent/Makefile.am |   1 +
 agent/tpm2.c      | 783 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 agent/tpm2.h      |  22 ++
 3 files changed, 806 insertions(+)
 create mode 100644 agent/tpm2.c
 create mode 100644 agent/tpm2.h

diff --git a/agent/Makefile.am b/agent/Makefile.am
index ce29462b2..bc3d43a0f 100644
--- a/agent/Makefile.am
+++ b/agent/Makefile.am
@@ -51,6 +51,7 @@ gpg_agent_SOURCES = \
 	protect.c \
 	trustlist.c \
 	divert-scd.c \
+	tpm2.c \
 	cvt-openpgp.c cvt-openpgp.h \
 	call-scd.c \
 	learncard.c
diff --git a/agent/tpm2.c b/agent/tpm2.c
new file mode 100644
index 000000000..f7fb1e043
--- /dev/null
+++ b/agent/tpm2.c
@@ -0,0 +1,783 @@
+#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 <agent.h>
+#include <tpm2.h>
+
+#include "../common/i18n.h"
+#include "../common/sexp-parse.h"
+
+#include <tss2/tssutils.h>
+#include <tss2/tssresponsecode.h>
+#include <tss2/tssmarshal.h>
+#include <tss2/Unmarshal_fp.h>
+#include <tss2/tsscryptoh.h>
+
+/* List of tss2 functions we use.  This is macro jiggery-pokery:
+ * the F argument gives us the ability to run an arbitrary macro over
+ * the function list as for each function do macro F */
+#define _TSS2_LIST(F)                   \
+  F(TSS_Create);                        \
+  F(TSS_SetProperty);                   \
+  F(TSS_Execute);                       \
+  F(TSS_ResponseCode_toString);         \
+  F(TPM2B_PUBLIC_Unmarshal);            \
+  F(TPM2B_PRIVATE_Unmarshal);           \
+  F(TSS_TPM2B_PUBLIC_Marshal);          \
+  F(TSS_TPMT_PUBLIC_Marshal);           \
+  F(TSS_TPM2B_PRIVATE_Marshal);         \
+  F(TSS_UINT16_Marshal);                \
+  F(TSS_TPMT_SENSITIVE_Marshal);        \
+  F(TSS_SetProperty);                   \
+  F(TSS_GetDigestSize);                 \
+  F(TSS_Hash_Generate);                 \
+  F(TSS_Delete);
+
+/* create static declarations for the function pointers */
+#define _DL_DECLARE(func) \
+  static typeof(func) *p##func
+_TSS2_LIST(_DL_DECLARE);
+
+static const char *tpm2_dir;
+
+/* The TPM builds a small database of active files representing key
+ * parameters used for authentication and session encryption.  Make sure
+ * they're contained in a separate directory to avoid stepping on any
+ * other application uses of the TPM */
+static const char *
+tpm2_set_unique_tssdir(void)
+{
+  char *prefix = getenv("XDG_RUNTIME_DIR"), *template,
+    *dir;
+  int len = 0;
+
+  if (!prefix)
+    prefix = "/tmp";
+
+  len = snprintf(NULL, 0, "%s/tss2.XXXXXX", prefix);
+  if (len <= 0)
+    return NULL;
+  template = xtrymalloc(len + 1);
+  if (!template)
+    return NULL;
+
+  len++;
+  len = snprintf(template, len, "%s/tss2.XXXXXX", prefix);
+
+  dir = mkdtemp(template);
+
+  return dir;
+}
+
+/* now dynamically load the tss library (if it exists) and resolve the
+ * above symbols.  This allows us simply to return 0 for tpm2_init on
+ * systems where there is no TPM library */
+static int
+tpm2_init(void)
+{
+  static int inited = 0;
+  const char *sym;
+  void *dl;
+
+  if (inited)
+    return 0;
+
+  dl = dlopen(TSS2_LIB, RTLD_LAZY);
+
+  if (!dl)
+    {
+      log_error("opening of tss2 library failed %s\n", strerror(errno));
+      return GPG_ERR_CARD_NOT_PRESENT;
+    }
+
+  /* load each symbol pointer and check for existence */
+# define _DL_SYM(func)                          \
+    sym = #func;                                \
+    p##func = dlsym(dl, #func);                 \
+      if (p##func == NULL)                      \
+        goto out_symfail
+
+  _TSS2_LIST(_DL_SYM);
+
+  tpm2_dir = tpm2_set_unique_tssdir();
+  if (!tpm2_dir)
+    /* make this non fatal */
+    log_error("Failed to set unique TPM directory\n");
+  inited = 1;
+  return 0;
+
+ out_symfail:
+  log_error("Failed to find symbol %s in tss2 library\n", sym);
+  return GPG_ERR_CARD_NOT_PRESENT;
+}
+
+static void
+tpm2_error(TPM_RC rc, char *prefix)
+{
+  const char *msg, *submsg, *num;
+
+  pTSS_ResponseCode_toString(&msg, &submsg, &num, rc);
+  log_error("%s gave TPM2 Error: %s%s%s", prefix, msg, submsg, num);
+}
+
+#define _TSS_CHECK(f)           \
+  rc = f;                       \
+  if (rc != TPM_RC_SUCCESS)     \
+    {                           \
+      tpm2_error(rc, #f);       \
+      return GPG_ERR_CARD;      \
+    }
+
+int
+tpm2_start(TSS_CONTEXT **tssc)
+{
+  TPM_RC rc;
+  int ret;
+
+  ret = tpm2_init();
+  if (ret)
+    return ret;
+
+  _TSS_CHECK(pTSS_Create(tssc));
+  _TSS_CHECK(pTSS_SetProperty(*tssc, TPM_DATA_DIR, tpm2_dir));
+  return 0;
+}
+
+void
+tpm2_end(TSS_CONTEXT *tssc)
+{
+  pTSS_Delete(tssc);
+}
+
+void
+tpm2_flush_handle(TSS_CONTEXT *tssc, TPM_HANDLE h)
+{
+        FlushContext_In in;
+
+        if (!h)
+                return;
+
+        in.flushHandle = h;
+        pTSS_Execute(tssc, NULL,
+                     (COMMAND_PARAMETERS *)&in,
+                     NULL,
+                     TPM_CC_FlushContext,
+                     TPM_RH_NULL, NULL, 0);
+}
+
+static int
+tpm2_get_hmac_handle(TSS_CONTEXT *tssc, TPM_HANDLE *handle,
+                     TPM_HANDLE salt_key)
+{
+  TPM_RC rc;
+  StartAuthSession_In in;
+  StartAuthSession_Out out;
+  StartAuthSession_Extra extra;
+
+  memset(&in, 0, sizeof(in));
+  memset(&extra, 0 , sizeof(extra));
+  in.bind = TPM_RH_NULL;
+  in.sessionType = TPM_SE_HMAC;
+  in.authHash = TPM_ALG_SHA256;
+  in.tpmKey = TPM_RH_NULL;
+  in.symmetric.algorithm = TPM_ALG_AES;
+  in.symmetric.keyBits.aes = 128;
+  in.symmetric.mode.aes = TPM_ALG_CFB;
+  if (salt_key) {
+    ReadPublic_In rin;
+    ReadPublic_Out rout;
+
+    rin.objectHandle = salt_key;
+    rc = pTSS_Execute (tssc,
+                       (RESPONSE_PARAMETERS *)&rout,
+                       (COMMAND_PARAMETERS *)&rin,
+                       NULL,
+                       TPM_CC_ReadPublic,
+                       TPM_RH_NULL, NULL, 0);
+    if (rc) {
+      tpm2_error(rc, "TPM2_ReadPublic");
+      return GPG_ERR_CARD;
+    }
+
+    /* don't care what rout returns, the purpose of the operation was
+     * to get the public key parameters into the tss so it can
+     * construct the salt */
+    in.tpmKey = salt_key;
+  }
+  rc = pTSS_Execute(tssc,
+                    (RESPONSE_PARAMETERS *)&out,
+                    (COMMAND_PARAMETERS *)&in,
+                    (EXTRA_PARAMETERS *)&extra,
+                    TPM_CC_StartAuthSession,
+                    TPM_RH_NULL, NULL, 0);
+  if (rc) {
+    tpm2_error(rc, "TPM2_StartAuthSession");
+    return GPG_ERR_CARD;
+  }
+
+  *handle = out.sessionHandle;
+
+  return 0;
+}
+
+static int
+tpm2_exec_with_auth(ctrl_t ctrl, TSS_CONTEXT *tssc, int cmd, char *cmd_str,
+                    void *out, void *in)
+{
+  TPM_HANDLE ah;
+  struct pin_entry_info_s *pi;
+  TPM_RC rc;
+
+  pi = gcry_xmalloc_secure(sizeof(*pi) + MAX_PASSPHRASE_LEN + 10);
+  pi->max_length = MAX_PASSPHRASE_LEN;
+  pi->min_digits = 0;           /* want a real passphrase */
+  pi->max_digits = 16;
+  pi->max_tries = 3;
+  rc = agent_askpin(ctrl, NULL, "TPM Key Passphrase", NULL, pi, NULL, 0);
+  if (rc) {
+    gcry_free (pi);
+    return rc;
+  }
+
+  rc = tpm2_get_hmac_handle(tssc, &ah, 0);
+  if (rc)
+    return rc;
+
+  rc = pTSS_Execute(tssc, out, in, NULL,
+                    cmd,
+                    ah, pi->pin, 0,
+                    TPM_RH_NULL, NULL, 0);
+  gcry_free (pi);
+  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 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)
+{
+  uint32_t parent;
+  Load_In in;
+  Load_Out out;
+  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;
+
+  in.parentHandle = parent;
+
+  buf = (BYTE *)priv;
+  size = priv_len;
+  pTPM2B_PRIVATE_Unmarshal(&in.inPrivate, &buf, &size);
+
+  buf = (BYTE *)pub;
+  size = pub_len;
+  pTPM2B_PUBLIC_Unmarshal(&in.inPublic, &buf, &size, FALSE);
+
+  rc = pTSS_Execute(tssc,
+                    (RESPONSE_PARAMETERS *)&out,
+                    (COMMAND_PARAMETERS *)&in,
+                    NULL,
+                    TPM_CC_Load,
+                    TPM_RS_PW, NULL, 0,
+                    TPM_RH_NULL, NULL, 0);
+  if (rc != TPM_RC_SUCCESS) {
+    tpm2_error(rc, "TPM2_Load");
+    return GPG_ERR_CARD;
+  }
+
+  *key = out.objectHandle;
+
+  return 0;
+}
+
+int
+tpm2_sign(ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
+          const unsigned char *digest, size_t digestlen,
+          unsigned char **r_sig, size_t *r_siglen)
+{
+  Sign_In in;
+  Sign_Out out;
+  int ret;
+
+  /* The TPM insists on knowing the digest type, so
+   * calculate that from the size */
+  in.inScheme.scheme = TPM_ALG_RSASSA;
+  switch (digestlen) {
+  case 20:
+    in.inScheme.details.rsassa.hashAlg = TPM_ALG_SHA1;
+    break;
+  case 32:
+    in.inScheme.details.rsassa.hashAlg = TPM_ALG_SHA256;
+    break;
+  case 48:
+    in.inScheme.details.rsassa.hashAlg = TPM_ALG_SHA384;
+    break;
+#ifdef TPM_ALG_SHA512
+  case 64:
+    in.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;
+  }
+  in.digest.t.size = digestlen;
+  memcpy(in.digest.t.buffer, digest, digestlen);
+  in.keyHandle = key;
+  in.validation.tag = TPM_ST_HASHCHECK;
+  in.validation.hierarchy = TPM_RH_NULL;
+  in.validation.digest.t.size = 0;
+
+  ret = tpm2_exec_with_auth(ctrl, tssc, TPM_CC_Sign, "TPM2_Sign", &out, &in);
+  if (ret)
+    return ret;
+
+  *r_siglen = out.signature.signature.rsassa.sig.t.size;
+  *r_sig = xtrymalloc(*r_siglen);
+  if (!r_sig)
+    return GPG_ERR_ENOMEM;
+
+  memcpy(*r_sig, out.signature.signature.rsassa.sig.t.buffer, *r_siglen);
+
+  return 0;
+}
+
+static int
+sexp_to_tpm2_sensitive(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;
+  s->seedValue.b.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(s->sensitive.rsa.t.buffer);
+  rc = gcry_mpi_print (GCRYMPI_FMT_USG, s->sensitive.rsa.t.buffer, len, &len, p);
+  s->sensitive.rsa.t.size = len;
+  gcry_mpi_release (p);
+
+  return rc;
+}
+
+static int
+sexp_to_tpm2_public(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;
+  /* note: all our keys are decrypt only.  This is because
+   * we use the TPM2_RSA_Decrypt operation for both signing
+   * and decryption (see e_tpm2.c for details) */
+  p->objectAttributes.val = TPMA_OBJECT_NODA |
+    TPMA_OBJECT_DECRYPT |
+    TPMA_OBJECT_SIGN |
+    TPMA_OBJECT_USERWITHAUTH;
+  p->authPolicy.t.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(p->unique.rsa.t.buffer);
+  p->parameters.rsaDetail.keyBits = gcry_mpi_get_nbits (n);
+  rc = gcry_mpi_print (GCRYMPI_FMT_USG, p->unique.rsa.t.buffer, len, &len, n);
+  p->unique.rsa.t.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)
+    goto out;
+
+  rc = sexp_to_tpm2_public(p, l2);
+  if (!rc)
+    rc = sexp_to_tpm2_sensitive(s, l2);
+
+  gcry_sexp_release(l2);
+
+ out:
+  gcry_sexp_release(l1);
+  return rc;
+}
+
+/* copied from TPM implementation code */
+static TPM_RC
+tpm2_ObjectPublic_GetName(TPM2B_NAME *name,
+                          TPMT_PUBLIC *tpmtPublic)
+{
+  TPM_RC rc = 0;
+  uint16_t written = 0;
+  TPMT_HA digest;
+  uint32_t sizeInBytes;
+  uint8_t buffer[MAX_RESPONSE_SIZE];
+
+  /* marshal the TPMT_PUBLIC */
+  if (rc == 0) {
+    INT32 size = MAX_RESPONSE_SIZE;
+    uint8_t *buffer1 = buffer;
+    rc = pTSS_TPMT_PUBLIC_Marshal(tpmtPublic, &written, &buffer1, &size);
+  }
+  /* hash the public area */
+  if (rc == 0) {
+    sizeInBytes = pTSS_GetDigestSize(tpmtPublic->nameAlg);
+    digest.hashAlg = tpmtPublic->nameAlg;       /* Name digest algorithm */
+    /* generate the TPMT_HA */
+    rc = pTSS_Hash_Generate(&digest,
+                            written, buffer,
+                            0, NULL);
+  }
+  if (rc == 0) {
+    /* copy the digest */
+    memcpy(name->t.name + sizeof(TPMI_ALG_HASH), (uint8_t *)&digest.digest, sizeInBytes);
+    /* copy the hash algorithm */
+    TPMI_ALG_HASH nameAlgNbo = htons(tpmtPublic->nameAlg);
+    memcpy(name->t.name, (uint8_t *)&nameAlgNbo, sizeof(TPMI_ALG_HASH));
+    /* set the size */
+    name->t.size = sizeInBytes + sizeof(TPMI_ALG_HASH);
+  }
+  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,
+                                 TPM2B_NAME *name,
+                                 TPM_ALG_ID nalg,
+                                 TPMT_SYM_DEF_OBJECT *symdef,
+                                 TPM2B_DATA *innerkey,
+                                 TPM2B_PRIVATE *p)
+{
+  BYTE *buf = p->t.buffer;
+
+  p->t.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;
+    const int hlen = pTSS_GetDigestSize(nalg);
+    TPM2B *digest = (TPM2B *)buf;
+    TPM2B *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));
+
+    /* reserve space for hash before the encrypted sensitive */
+    bsize = sizeof(digest->size) + hlen;
+    buf += bsize;
+    p->t.size += bsize;
+    s2b = (TPM2B *)buf;
+
+    /* marshal the digest size */
+    buf = (BYTE *)&digest->size;
+    bsize = hlen;
+    size = 2;
+    pTSS_UINT16_Marshal(&bsize, &written, &buf, &size);
+
+    /* marshal the unencrypted sensitive in place */
+    size = sizeof(*s);
+    bsize = 0;
+    buf = s2b->buffer;
+    pTSS_TPMT_SENSITIVE_Marshal(s, &bsize, &buf, &size);
+    buf = (BYTE *)&s2b->size;
+    size = 2;
+    pTSS_UINT16_Marshal(&bsize, &written, &buf, &size);
+
+    bsize = bsize + sizeof(s2b->size);
+    p->t.size += bsize;
+
+    /* compute hash of unencrypted marshalled sensitive and
+     * write to the digest buffer */
+    hash.hashAlg = nalg;
+    pTSS_Hash_Generate(&hash, bsize, s2b,
+                       name->t.size, name->t.name,
+                       0, NULL);
+    memcpy(digest->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->b.buffer, innerkey->b.size);
+    /* encrypt the hash and sensitive in-place */
+    gcry_cipher_encrypt(hd, p->t.buffer, p->t.size, NULL, 0);
+    gcry_cipher_close(hd);
+
+  } else if (symdef->algorithm == TPM_ALG_NULL) {
+    TPM2B *s2b = (TPM2B *)buf;
+    int32_t size = sizeof(*s);
+    UINT16 bsize = 0, written = 0;
+
+    buf = s2b->buffer;
+
+    /* marshal the unencrypted sensitive in place */
+    pTSS_TPMT_SENSITIVE_Marshal(s, &bsize, &buf, &size);
+    buf = (BYTE *)&s2b->size;
+    size = 2;
+    pTSS_UINT16_Marshal(&bsize, &written, &buf, &size);
+
+    p->b.size += bsize + sizeof(s2b->size);
+  } else {
+    log_error ("Unknown symmetric algorithm\n");
+    return TPM_RC_SYMMETRIC;
+  }
+
+  return TPM_RC_SUCCESS;
+}
+
+static void
+tpm2_encrypt_duplicate(Import_In *iin, TPMT_SENSITIVE *s)
+{
+  TPM2B_NAME name;
+  TPMT_PUBLIC *p = &iin->objectPublic.publicArea;
+  const int aes_key_bits = 128;
+  const int aes_key_bytes = aes_key_bits/8;
+
+  tpm2_ObjectPublic_GetName(&name, p);
+  gcry_randomize(iin->encryptionKey.t.buffer,
+                 aes_key_bytes, GCRY_STRONG_RANDOM);
+  iin->encryptionKey.t.size = aes_key_bytes;
+
+  /* set random iin.symSeed */
+  iin->inSymSeed.t.size = 0;
+  iin->symmetricAlg.algorithm = TPM_ALG_AES;
+  iin->symmetricAlg.keyBits.aes = aes_key_bits;
+  iin->symmetricAlg.mode.aes = TPM_ALG_CFB;
+
+  tpm2_SensitiveToDuplicate(s, &name, p->nameAlg, &iin->symmetricAlg,
+                            &iin->encryptionKey, &iin->duplicate);
+}
+
+int
+tpm2_import_key(ctrl_t ctrl, TSS_CONTEXT *tssc, char *pub, int *pub_len,
+                char *priv, int *priv_len, gcry_sexp_t s_skey)
+{
+  Import_In iin;
+  Import_Out iout;
+  TPMT_SENSITIVE s;
+  TPM_HANDLE ah;
+  TPM_RC rc;
+
+  uint32_t size;
+  uint16_t len;
+  BYTE *buffer;
+  int ret;
+  char *passphrase;
+
+  iin.parentHandle = TPM2_PARENT;
+  ret = sexp_to_tpm2(&iin.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 = agent_ask_new_passphrase (ctrl,  _("Please enter the TPM Authorization passphrase for the key."), &passphrase);
+  if (ret)
+    return ret;
+  s.authValue.b.size = strlen(passphrase);
+  memcpy(s.authValue.b.buffer, passphrase, s.authValue.b.size);
+
+  /* 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 */
+  tpm2_encrypt_duplicate(&iin, &s);
+
+  /* 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, TPM2_PARENT);
+  if (rc)
+    return GPG_ERR_CARD;
+
+  rc = pTSS_Execute(tssc,
+                    (RESPONSE_PARAMETERS *)&iout,
+                    (COMMAND_PARAMETERS *)&iin,
+                    NULL,
+                    TPM_CC_Import,
+                    ah, NULL, TPMA_SESSION_DECRYPT,
+                    TPM_RH_NULL, NULL, 0);
+  if (rc) {
+    tpm2_error(rc, "TPM2_Import");
+    /* failure means auth handle is not flushed */
+    tpm2_flush_handle(tssc, ah);
+    return GPG_ERR_CARD;
+  }
+
+  size = sizeof(TPM2B_PUBLIC);
+  buffer = pub;
+  len = 0;
+  pTSS_TPM2B_PUBLIC_Marshal(&iin.objectPublic,
+                            &len, &buffer, &size);
+  *pub_len = len;
+
+  size = sizeof(TPM2B_PRIVATE);
+  buffer = priv;
+  len = 0;
+  pTSS_TPM2B_PRIVATE_Marshal(&iout.outPrivate,
+                             &len, &buffer, &size);
+  *priv_len = len;
+
+  return 0;
+}
+
+int
+tpm2_decrypt(ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
+             const char *ciphertext, int ciphertext_len,
+             char **decrypt, size_t *decrypt_len)
+{
+  RSA_Decrypt_In in;
+  RSA_Decrypt_Out out;
+  int ret;
+
+  in.keyHandle = key;
+  in.inScheme.scheme = TPM_ALG_RSAES;
+  in.cipherText.t.size = ciphertext_len;
+  memcpy (in.cipherText.t.buffer, ciphertext, ciphertext_len);
+  in.label.t.size = 0;
+
+  ret = tpm2_exec_with_auth(ctrl, tssc, TPM_CC_RSA_Decrypt, "TPM2_RSA_Decrypt",
+                            &out, &in);
+  if (ret)
+    return ret;
+
+  *decrypt_len = out.message.t.size;
+  *decrypt = xtrymalloc(out.message.t.size);
+  memcpy (*decrypt, out.message.t.buffer, out.message.t.size);
+
+  return 0;
+}
diff --git a/agent/tpm2.h b/agent/tpm2.h
new file mode 100644
index 000000000..2e168032f
--- /dev/null
+++ b/agent/tpm2.h
@@ -0,0 +1,22 @@
+#ifndef _TPM2_H
+#define _TPM2_H
+
+#include <tss2/tss.h>
+
+#define TSS2_LIB "libtss.so.0"
+#define TPM2_PARENT 0x81000001
+
+int tpm2_start(TSS_CONTEXT **tssc);
+void tpm2_end(TSS_CONTEXT *tssc);
+void tpm2_flush_handle(TSS_CONTEXT *tssc, TPM_HANDLE h);
+int tpm2_load_key(TSS_CONTEXT *tssc, const unsigned char *shadow_info,
+                  TPM_HANDLE *key);
+int tpm2_sign(ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
+              const unsigned char *digest, size_t digestlen,
+              unsigned char **r_sig, size_t *r_siglen);
+int tpm2_import_key(ctrl_t ctrl, TSS_CONTEXT *tssc, char *pub, int *pub_len,
+                    char *priv, int *priv_len, gcry_sexp_t s_skey);
+int tpm2_decrypt(ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
+                 const char *ciphertext, int ciphertext_len,
+                 char **decrypt, size_t *decrypt_len);
+#endif
-- 
2.12.3



More information about the Gnupg-devel mailing list