[RFC 0/4] TPM support for gpg

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

[RFC 0/4] TPM support for gpg

James Bottomley
This patch series adds TPM 2.0 support to gnupg. TPMs are useful as an
alternative to key cards: they provide the same security against key
theft and the same cryptographic protections.  The main difference is
that TPMs are universally present in every laptop, so they provide a
simple and ubiquitous solution to key security.  The only real down
side is that unlike key cards, TPM protected keys cannot be transferred
between laptops, you must instead keep an offline backup copy of the
key can then be transferred to the TPM of any new laptop.

The way TPM protection works is slightly different from key cards.
 Instead of moving the key inside the card, the TPM converts any given
key to a TPM specific representation (meaning it's encrypted by a
special key that only the TPM possesses).  The TPM represenation must
be stored offline somewhere and if it is lost, so is the protected key.
 The way I implemented this is to use the TPM to convert the key to
protected representation and then store it in the shadow_info of a
shadowed-private-key using a shadow type of tpm2-v1.  The TPM can
handle an arbitrary number of keys, but the price is the shadow_info
stores the keys and must be preserved.

This implementation is an RFC, because ordinarily TPM support would be
integrated into the cryptosystem rather than the application but since
gnupg already integrates key cards via a diversion mechanism, it was
also easy to follow this route for TPM support.  The main difference
between the TPM and the card is that there's no need for a separate
daemon to run the card, so I integrated diversion support directly into
the agent.

The way to use this is simple: I added a new command to keyedit.c:
keytotpm which converts an existing encrypted private key to TPM
representation. This conversion is done immediately the command
completes and cannot be undone.  Once converted, all the usual gpg
operations go transparently through the TPM, so it should be largely
invisible to a user.

James

---

James Bottomley (4):
  agent: expose shadow key type
  agent: add tpm specific functions
  agent: plumb in TPM handling
  g10: add ability to transfer a private key to the tpm

 agent/Makefile.am   |   2 +
 agent/agent.h       |  23 +-
 agent/command.c     |  78 +++++-
 agent/divert-tpm2.c | 187 +++++++++++++
 agent/findkey.c     |   5 +-
 agent/pkdecrypt.c   |   8 +-
 agent/pksign.c      |  14 +-
 agent/protect.c     |  72 ++++-
 agent/tpm2.c        | 784 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 agent/tpm2.h        |  22 ++
 g10/call-agent.c    |  22 ++
 g10/call-agent.h    |   3 +
 g10/keyedit.c       |  45 ++-
 13 files changed, 1240 insertions(+), 25 deletions(-)
 create mode 100644 agent/divert-tpm2.c
 create mode 100644 agent/tpm2.c
 create mode 100644 agent/tpm2.h

--
2.12.3

_______________________________________________
Gnupg-devel mailing list
[hidden email]
http://lists.gnupg.org/mailman/listinfo/gnupg-devel
Reply | Threaded
Open this post in threaded view
|

[RFC 1/4] agent: expose shadow key type

James Bottomley
For TPM support it is necessary to indroduce another type of shadow
key, so allow other agent functions to extract the type so they can
make the right decisions based on it.

Signed-off-by: James Bottomley <[hidden email]>
---
 agent/agent.h   | 10 +++++++-
 agent/command.c | 21 +++++++++++++----
 agent/findkey.c |  5 ++--
 agent/protect.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 4 files changed, 90 insertions(+), 18 deletions(-)

diff --git a/agent/agent.h b/agent/agent.h
index 687635dc7..2df2fde53 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -420,7 +420,8 @@ int agent_is_eddsa_key (gcry_sexp_t s_key);
 int agent_key_available (const unsigned char *grip);
 gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
                                       int *r_keytype,
-                                      unsigned char **r_shadow_info);
+                                      unsigned char **r_shadow_info,
+                                      unsigned char **r_shadow_info_type);
 gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
                               const unsigned char *grip,
                               int force, int only_stubs);
@@ -502,8 +503,15 @@ unsigned char *make_shadow_info (const char *serialno, const char *idstring);
 int agent_shadow_key (const unsigned char *pubkey,
                       const unsigned char *shadow_info,
                       unsigned char **result);
+int agent_shadow_key_type (const unsigned char *pubkey,
+                           const unsigned char *shadow_info,
+                           const unsigned char *type,
+                           unsigned char **result);
 gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey,
                                    unsigned char const **shadow_info);
+gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey,
+                                        unsigned char const **shadow_info,
+                                        unsigned char **shadow_type);
 gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
                                char **r_hexsn, char **r_idstr, int *r_pinlen);
 gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
diff --git a/agent/command.c b/agent/command.c
index 7c7e8a4bc..57e9de75b 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1134,7 +1134,7 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
   char hexgrip[40+1];
   char *fpr = NULL;
   int keytype;
-  unsigned char *shadow_info = NULL;
+  unsigned char *shadow_info = NULL, *shadow_info_type = NULL;
   char *serialno = NULL;
   char *idstr = NULL;
   const char *keytypestr;
@@ -1145,7 +1145,8 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
   char ttlbuf[20];
   char flagsbuf[5];
 
-  err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
+  err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info,
+                                  &shadow_info_type);
   if (err)
     {
       if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
@@ -1215,9 +1216,18 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
 
   if (shadow_info)
     {
-      err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
-      if (err)
-        goto leave;
+      if (strcmp (shadow_info_type, "t1-v1") == 0)
+        {
+          err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
+          if (err)
+            goto leave;
+        }
+      else
+        {
+          log_error ("Unrecognised shadow key type %s\n", shadow_info_type);
+          err = GPG_ERR_BAD_KEY;
+          goto leave;
+        }
     }
 
   if (!data)
@@ -1252,6 +1262,7 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
 
  leave:
   xfree (fpr);
+  xfree (shadow_info_type);
   xfree (shadow_info);
   xfree (serialno);
   xfree (idstr);
diff --git a/agent/findkey.c b/agent/findkey.c
index e3e9a123f..d6c600e71 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1359,7 +1359,8 @@ agent_key_available (const unsigned char *grip)
    S-expression.  */
 gpg_error_t
 agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
-                          int *r_keytype, unsigned char **r_shadow_info)
+                          int *r_keytype, unsigned char **r_shadow_info,
+                          unsigned char **r_shadow_info_type)
 {
   gpg_error_t err;
   unsigned char *buf;
@@ -1406,7 +1407,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
           const unsigned char *s;
           size_t n;
 
-          err = agent_get_shadow_info (buf, &s);
+          err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type);
           if (!err)
             {
               n = gcry_sexp_canon_len (s, 0, NULL, NULL);
diff --git a/agent/protect.c b/agent/protect.c
index 16ae715e1..0920667d1 100644
--- a/agent/protect.c
+++ b/agent/protect.c
@@ -1499,9 +1499,10 @@ make_shadow_info (const char *serialno, const char *idstring)
   to. The input parameters are expected to be valid canonicalized
   S-expressions */
 int
-agent_shadow_key (const unsigned char *pubkey,
-                  const unsigned char *shadow_info,
-                  unsigned char **result)
+agent_shadow_key_type (const unsigned char *pubkey,
+                       const unsigned char *shadow_info,
+                       const unsigned char *type,
+                       unsigned char **result)
 {
   const unsigned char *s;
   const unsigned char *point;
@@ -1557,7 +1558,7 @@ agent_shadow_key (const unsigned char *pubkey,
   assert (depth == 1);
 
   /* Calculate required length by taking in account: the "shadowed-"
-     prefix, the "shadowed", "t1-v1" as well as some parenthesis */
+     prefix, the "shadowed", shadow type as well as some parenthesis */
   n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1;
   *result = xtrymalloc (n);
   p = (char*)*result;
@@ -1567,7 +1568,7 @@ agent_shadow_key (const unsigned char *pubkey,
   /* (10:public-key ...)*/
   memcpy (p, pubkey+14, point - (pubkey+14));
   p += point - (pubkey+14);
-  p = stpcpy (p, "(8:shadowed5:t1-v1");
+  p += sprintf (p, "(8:shadowed%d:%s", (int)strlen(type), type);
   memcpy (p, shadow_info, shadow_info_len);
   p += shadow_info_len;
   *p++ = ')';
@@ -1577,11 +1578,20 @@ agent_shadow_key (const unsigned char *pubkey,
   return 0;
 }
 
+int
+agent_shadow_key (const unsigned char *pubkey,
+                  const unsigned char *shadow_info,
+                  unsigned char **result)
+{
+  return agent_shadow_key_type (pubkey, shadow_info, "t1-v1", result);
+}
+
 /* Parse a canonical encoded shadowed key and return a pointer to the
-   inner list with the shadow_info */
+   inner list with the shadow_info and the shadow type */
 gpg_error_t
-agent_get_shadow_info (const unsigned char *shadowkey,
-                       unsigned char const **shadow_info)
+agent_get_shadow_info_type (const unsigned char *shadowkey,
+                            unsigned char const **shadow_info,
+                            unsigned char **shadow_type)
 {
   const unsigned char *s;
   size_t n;
@@ -1633,17 +1643,59 @@ agent_get_shadow_info (const unsigned char *shadowkey,
   n = snext (&s);
   if (!n)
     return gpg_error (GPG_ERR_INV_SEXP);
-  if (smatch (&s, n, "t1-v1"))
+  if (shadow_type) {
+    char *buf = xtrymalloc(n+1);
+    memcpy(buf, s, n);
+    buf[n] = '\0';
+    *shadow_type = buf;
+  }
+
+  if (smatch (&s, n, "t1-v1") || smatch(&s, n, "tpm2-v1"))
     {
       if (*s != '(')
         return gpg_error (GPG_ERR_INV_SEXP);
-      *shadow_info = s;
+      if (shadow_info)
+        *shadow_info = s;
     }
   else
     return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
   return 0;
 }
 
+gpg_error_t
+agent_get_shadow_info(const unsigned char *shadowkey,
+                      unsigned char const **shadow_info)
+{
+  return agent_get_shadow_info_type(shadowkey, shadow_info, NULL);
+}
+
+int
+agent_is_tpm2_key(gcry_sexp_t s_skey)
+{
+  unsigned char *buf;
+  unsigned char *type;
+  size_t len;
+  gpg_error_t err;
+
+  err = make_canon_sexp(s_skey, &buf, &len);
+  if (err)
+    return 0;
+
+  err = agent_get_shadow_info_type(buf, NULL, &type);
+  if (err)
+    return 0;
+
+  err = strcmp(type, "tpm2-v1") == 0;
+  xfree(type);
+  return err;
+}
+
+gpg_error_t
+agent_get_shadow_type(const unsigned char *shadowkey,
+                      unsigned char **shadow_type)
+{
+  return agent_get_shadow_info_type(shadowkey, NULL, shadow_type);
+}
 
 /* Parse the canonical encoded SHADOW_INFO S-expression.  On success
    the hex encoded serial number is returned as a malloced strings at
--
2.12.3

_______________________________________________
Gnupg-devel mailing list
[hidden email]
http://lists.gnupg.org/mailman/listinfo/gnupg-devel
Reply | Threaded
Open this post in threaded view
|

[RFC 2/4] agent: add tpm specific functions

James Bottomley
In reply to this post by James Bottomley
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 <[hidden email]>
---
 agent/Makefile.am |   1 +
 agent/tpm2.c      | 784 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 agent/tpm2.h      |  22 ++
 3 files changed, 807 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..1df22e2a8
--- /dev/null
+++ b/agent/tpm2.c
@@ -0,0 +1,784 @@
+#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>
+#include <tss2/tsscrypto.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_AES_EncryptCFB);                \
+  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;
+
+    /* 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);
+
+    /* encrypt hash and sensitive in place */
+    pTSS_AES_EncryptCFB(p->t.buffer,
+                        symdef->keyBits.aes,
+                        innerkey->b.buffer,
+                        null_iv,
+                        p->t.size,
+                        p->t.buffer);
+  } 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 {
+    printf("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

_______________________________________________
Gnupg-devel mailing list
[hidden email]
http://lists.gnupg.org/mailman/listinfo/gnupg-devel
Reply | Threaded
Open this post in threaded view
|

[RFC 3/4] agent: plumb in TPM handling

James Bottomley
In reply to this post by James Bottomley
This code installs diversions for pksign and pkdecrypt to do the
operations via the TPM if a TPM shadowed key is present.  It also adds
an extra assuan command KEYTOTPM which moves an existing private key
to a TPM shadowed key.

The way TPM shadowing works is that the public and private key parts
are fed in to the TPM command TPM2_Import.  The output of this command
is a TPM specific public and private key data where the private key
data is symmetrically encrypted using a TPM internal key.  If this
physical TPM is ever lost or cleared, that TPM internal key will
likewise be lost and nothing will ever be able to read the private
key.  Once the import is done, the shadow information for the key is
updated to be a three part list consisting of the parent key (hard
coded to 81000001 which is the Microsoft preferred RSA incarnation of
the storage seed) and the public and private TPM data blobs.

Now when a TPM shadowed key is used, the data blobs must be loaded
into the TPM with TPM2_Load before any operation can be performed.

Signed-off-by: James Bottomley <[hidden email]>
---
 agent/Makefile.am   |   1 +
 agent/agent.h       |  13 ++++
 agent/command.c     |  57 ++++++++++++++++
 agent/divert-tpm2.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 agent/pkdecrypt.c   |   8 ++-
 agent/pksign.c      |  14 ++--
 6 files changed, 274 insertions(+), 6 deletions(-)
 create mode 100644 agent/divert-tpm2.c

diff --git a/agent/Makefile.am b/agent/Makefile.am
index bc3d43a0f..f0a52ad3b 100644
--- a/agent/Makefile.am
+++ b/agent/Makefile.am
@@ -51,6 +51,7 @@ gpg_agent_SOURCES = \
  protect.c \
  trustlist.c \
  divert-scd.c \
+ divert-tpm2.c \
  tpm2.c \
  cvt-openpgp.c cvt-openpgp.h \
  call-scd.c \
diff --git a/agent/agent.h b/agent/agent.h
index 2df2fde53..0ff487a59 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -417,6 +417,7 @@ gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
                                         gcry_sexp_t *result);
 int agent_is_dsa_key (gcry_sexp_t s_key);
 int agent_is_eddsa_key (gcry_sexp_t s_key);
+int agent_is_tpm2_key(gcry_sexp_t s_key);
 int agent_key_available (const unsigned char *grip);
 gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
                                       int *r_keytype,
@@ -532,6 +533,18 @@ gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
                                const char *fpr, int flag);
 void agent_reload_trustlist (void);
 
+/*-- divert-tpm2.c --*/
+int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text,
+                        const unsigned char *digest, size_t digestlen, int algo,
+                        const unsigned char *shadow_info, unsigned char **r_sig,
+                        size_t *r_siglen);
+int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+                           const unsigned char *cipher,
+                           const unsigned char *shadow_info,
+                           char **r_buf, size_t *r_len, int *r_padding);
+int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
+                          gcry_sexp_t s_skey);
+
 
 /*-- divert-scd.c --*/
 int divert_pksign (ctrl_t ctrl, const char *desc_text,
diff --git a/agent/command.c b/agent/command.c
index 57e9de75b..c5f275f01 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1222,6 +1222,11 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
           if (err)
             goto leave;
         }
+      else if (strcmp (shadow_info_type, "tpm2-v1") == 0)
+        {
+          serialno = xstrdup("TPM-Protected");
+          idstr = NULL;
+        }
       else
         {
           log_error ("Unrecognised shadow key type %s\n", shadow_info_type);
@@ -2608,6 +2613,57 @@ cmd_keytocard (assuan_context_t ctx, char *line)
 
 
 
+static const char hlp_keytotpm[] =
+  "KEYTOTPM <hexstring_with_keygrip>\n"
+  "\n";
+static gpg_error_t
+cmd_keytotpm (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err = 0;
+  unsigned char grip[20];
+  gcry_sexp_t s_skey;
+  unsigned char *shadow_info = NULL;
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  err = parse_keygrip (ctx, line, grip);
+  if (err)
+    goto leave;
+
+  if (agent_key_available (grip))
+    {
+      err =gpg_error (GPG_ERR_NO_SECKEY);
+      goto leave;
+    }
+
+  err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
+                             &shadow_info, CACHE_MODE_IGNORE, NULL,
+                             &s_skey, NULL);
+  if (err)
+    {
+      xfree (shadow_info);
+      goto leave;
+    }
+  if (shadow_info)
+    {
+      /* Key is on a TPM or smartcard already.  */
+      xfree (shadow_info);
+      gcry_sexp_release (s_skey);
+      err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+      goto leave;
+    }
+
+  err = divert_tpm2_writekey (ctrl, grip, s_skey);
+  gcry_sexp_release (s_skey);
+
+ leave:
+  return leave_cmd (ctx, err);
+}
+
+
+
 static const char hlp_getval[] =
   "GETVAL <key>\n"
   "\n"
@@ -3273,6 +3329,7 @@ register_commands (assuan_context_t ctx)
     { "RELOADAGENT",    cmd_reloadagent,hlp_reloadagent },
     { "GETINFO",        cmd_getinfo,   hlp_getinfo },
     { "KEYTOCARD",      cmd_keytocard, hlp_keytocard },
+    { "KEYTOTPM", cmd_keytotpm, hlp_keytotpm },
     { NULL }
   };
   int i, rc;
diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c
new file mode 100644
index 000000000..dc3110d0b
--- /dev/null
+++ b/agent/divert-tpm2.c
@@ -0,0 +1,187 @@
+#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 "agent.h"
+#include "../common/i18n.h"
+#include "../common/sexp-parse.h"
+
+#include "tpm2.h"
+
+int
+divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text,
+                    const unsigned char *digest, size_t digestlen, int algo,
+                    const unsigned char *shadow_info, unsigned char **r_sig,
+                    size_t *r_siglen)
+{
+  TSS_CONTEXT *tssc;
+  TPM_HANDLE key;
+  int ret;
+
+  ret = tpm2_start(&tssc);
+  if (ret)
+    return ret;
+  ret = tpm2_load_key(tssc, shadow_info, &key);
+  if (ret)
+    goto out;
+  ret = tpm2_sign(ctrl, tssc, key, digest, digestlen, r_sig, r_siglen);
+
+  tpm2_flush_handle(tssc, key);
+
+ out:
+  tpm2_end(tssc);
+  return ret;
+}
+
+static unsigned char *
+make_tpm2_shadow_info (uint32_t parent, const char *pub, int pub_len,
+                       const char *priv, int priv_len)
+{
+  gcry_sexp_t s_exp;
+  size_t len;
+  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);
+  gcry_sexp_sprint(s_exp, GCRYSEXP_FMT_CANON, info, len);
+
+  gcry_sexp_release(s_exp);
+
+  return (unsigned char *)info;
+}
+
+static gpg_error_t
+agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip,
+                             int parent, char *pub,  int pub_len,
+                             char *priv, int priv_len)
+{
+  gpg_error_t err;
+  unsigned char *shadow_info;
+  unsigned char *shdkey;
+  unsigned char *pkbuf;
+  size_t len;
+  gcry_sexp_t s_pkey;
+
+  err = agent_public_key_from_file (ctrl, grip, &s_pkey);
+  len = gcry_sexp_sprint(s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
+  pkbuf = xtrymalloc (len);
+  gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, len);
+  gcry_sexp_release (s_pkey);
+
+  shadow_info = make_tpm2_shadow_info (parent, pub, pub_len, priv, priv_len);
+  if (!shadow_info) {
+    xfree (pkbuf);
+    return gpg_error_from_syserror ();
+  }
+
+  err = agent_shadow_key_type (pkbuf, shadow_info, "tpm2-v1", &shdkey);
+  xfree (shadow_info);
+  xfree (pkbuf);
+  if (err)
+    {
+      log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
+      return err;
+    }
+
+  len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+  err = agent_write_private_key (grip, shdkey, len, 1 /*force*/);
+  xfree (shdkey);
+  if (err)
+    log_error ("error writing key: %s\n", gpg_strerror (err));
+
+  return err;
+}
+
+int
+divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
+                      gcry_sexp_t s_skey)
+{
+  TSS_CONTEXT *tssc;
+  int ret, pub_len, priv_len;
+  /* priv is always shielded so no special handling required */
+  char pub[sizeof(TPM2B_PUBLIC)], priv[sizeof(TPM2B_PRIVATE)];
+
+  ret = tpm2_start(&tssc);
+  if (ret)
+    return ret;
+  ret = tpm2_import_key (ctrl, tssc, pub, &pub_len, priv, &priv_len, s_skey);
+  if (ret)
+    goto out;
+  ret = agent_write_tpm2_shadow_key (ctrl, grip, TPM2_PARENT, pub, pub_len,
+                                     priv, priv_len);
+ out:
+  tpm2_end(tssc);
+  return ret;
+}
+
+int
+divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+                       const unsigned char *cipher,
+                       const unsigned char *shadow_info,
+                       char **r_buf, size_t *r_len, int *r_padding)
+{
+  TSS_CONTEXT *tssc;
+  TPM_HANDLE key;
+  int ret;
+  const unsigned char *s;
+  size_t n;
+
+  *r_padding = 0;
+
+  (void)desc_text;
+
+  s = cipher;
+  if (*s != '(')
+    return gpg_error (GPG_ERR_INV_SEXP);
+  s++;
+  n = snext (&s);
+  if (!n)
+    return gpg_error (GPG_ERR_INV_SEXP);
+  if (!smatch (&s, n, "enc-val"))
+    return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+  if (*s != '(')
+    return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+  s++;
+  n = snext (&s);
+  if (!n)
+    return gpg_error (GPG_ERR_INV_SEXP);
+  if (smatch (&s, n, "rsa"))
+    {
+      if (*s != '(')
+        return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+      s++;
+      n = snext (&s);
+      if (!n)
+        return gpg_error (GPG_ERR_INV_SEXP);
+      if (!smatch (&s, n, "a"))
+        return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+      n = snext (&s);
+    }
+  else
+    return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+  /* know we have RSA to decrypt at s,n */
+
+  ret = tpm2_start(&tssc);
+  if (ret)
+    return ret;
+  ret = tpm2_load_key(tssc, shadow_info, &key);
+  if (ret)
+    goto out;
+  ret = tpm2_decrypt(ctrl, tssc, key, s, n, r_buf, r_len);
+
+  tpm2_flush_handle(tssc, key);
+
+ out:
+  tpm2_end(tssc);
+  return ret;
+
+}
diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
index 06a8e0b6f..6f766ca63 100644
--- a/agent/pkdecrypt.c
+++ b/agent/pkdecrypt.c
@@ -86,8 +86,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
           goto leave;
         }
 
-      rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info,
-                             &buf, &len, r_padding);
+      if (agent_is_tpm2_key (s_skey))
+ rc = divert_tpm2_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info,
+    &buf, &len, r_padding);
+      else
+ rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info,
+       &buf, &len, r_padding);
       if (rc)
         {
           log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc));
diff --git a/agent/pksign.c b/agent/pksign.c
index f54af0817..dae2638f4 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -353,10 +353,16 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
         if (desc_text)
           agent_modify_description (desc_text, NULL, s_skey, &desc2);
 
-        err = divert_pksign (ctrl, desc2? desc2 : desc_text,
-                             data, datalen,
-                             ctrl->digest.algo,
-                             shadow_info, &buf, &len);
+ if (agent_is_tpm2_key (s_skey))
+  err = divert_tpm2_pksign (ctrl, desc2? desc2 : desc_text,
+    data, datalen,
+    ctrl->digest.algo,
+    shadow_info, &buf, &len);
+ else
+  err = divert_pksign (ctrl, desc2? desc2 : desc_text,
+       data, datalen,
+       ctrl->digest.algo,
+       shadow_info, &buf, &len);
         xfree (desc2);
       }
       if (err)
--
2.12.3

_______________________________________________
Gnupg-devel mailing list
[hidden email]
http://lists.gnupg.org/mailman/listinfo/gnupg-devel
Reply | Threaded
Open this post in threaded view
|

[RFC 4/4] g10: add ability to transfer a private key to the tpm

James Bottomley
In reply to this post by James Bottomley
Exactly like the gpg --edit-key command keytosc, keytotpm has been
added which immedately converts the private key file to TPM shadowed
form.  Once this is done, the key cannot be recovered and may only be
used via the TPM of the computer system on which the conversion was
done.  If that system is ever lost, or its TPM cleared, the shadowed
key becomes unusable.

Signed-off-by: James Bottomley <[hidden email]>
---
 g10/call-agent.c | 22 ++++++++++++++++++++++
 g10/call-agent.h |  3 +++
 g10/keyedit.c    | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/g10/call-agent.c b/g10/call-agent.c
index 61d06c663..356ec8539 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -759,6 +759,28 @@ agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
   return err;
 }
 
+int
+agent_keytotpm (ctrl_t ctrl, const char *hexgrip)
+{
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+  struct default_inq_parm_s parm;
+
+  snprintf(line, DIM(line), "KEYTOTPM %s\n", hexgrip);
+
+  rc = start_agent (ctrl, 0);
+  if (rc)
+    return rc;
+  parm.ctx = agent_ctx;
+  parm.ctrl = ctrl;
+
+  rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
+ NULL, NULL);
+  if (rc)
+    log_log (GPGRT_LOGLVL_ERROR, _("error from TPM: %s\n"), gpg_strerror (rc));
+  return rc;
+}
+
 
 int
 agent_keytocard (const char *hexgrip, int keyno, int force,
diff --git a/g10/call-agent.h b/g10/call-agent.h
index f45b64d90..85c939fa0 100644
--- a/g10/call-agent.h
+++ b/g10/call-agent.h
@@ -88,6 +88,9 @@ gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw);
 /* Update INFO with the attribute NAME. */
 int agent_scd_getattr (const char *name, struct agent_card_info_s *info);
 
+/* send the KEYTOTPM command */
+int agent_keytotpm (ctrl_t ctrl, const char *hexgrip);
+
 /* Send the KEYTOCARD command. */
 int agent_keytocard (const char *hexgrip, int keyno, int force,
                      const char *serialno, const char *timestamp);
diff --git a/g10/keyedit.c b/g10/keyedit.c
index 81344eb79..fd82f2d21 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -1241,7 +1241,7 @@ enum cmdids
 #endif /*!NO_TRUST_MODELS*/
   cmdSHOWPREF,
   cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST,
-  cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD,
+  cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdKEYTOTPM, cmdBKUPTOCARD,
   cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP
 };
 
@@ -1292,6 +1292,8 @@ static struct
     N_("add a key to a smartcard")},
   { "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
     N_("move a key to a smartcard")},
+  { "keytotpm", cmdKEYTOTPM, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
+    N_("convert a key to TPM form using the local TPM")},
   { "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
     N_("move a backup key to a smartcard")},
 #endif /*ENABLE_CARD_SUPPORT */
@@ -1789,6 +1791,47 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
     }
   break;
 
+ case cmdKEYTOTPM:
+  /* FIXME need to store the key and not commit until later */
+  {
+    KBNODE node = NULL;
+    switch (count_selected_keys (keyblock))
+      {
+      case 0:
+ if (cpr_get_answer_is_yes
+                    ("keyedit.keytocard.use_primary",
+                     /* TRANSLATORS: Please take care: This is about
+                        moving the key and not about removing it.  */
+                     _("Really move the primary key? (y/N) ")))
+  node = keyblock;
+ break;
+      case 1:
+ for (node = keyblock; node; node = node->next)
+  {
+    if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
+ && node->flag & NODFLG_SELKEY)
+      break;
+  }
+ break;
+      default:
+ tty_printf (_("You must select exactly one key.\n"));
+ break;
+      }
+    if (node)
+      {
+ PKT_public_key *xxpk = node->pkt->pkt.public_key;
+ char *hexgrip;
+
+ hexkeygrip_from_pk (xxpk, &hexgrip);
+ if (!agent_keytotpm (ctrl, hexgrip))
+  {
+    redisplay = 1;
+  }
+ xfree (hexgrip);
+      }
+  }
+  break;
+
  case cmdKEYTOCARD:
   {
     KBNODE node = NULL;
--
2.12.3

_______________________________________________
Gnupg-devel mailing list
[hidden email]
http://lists.gnupg.org/mailman/listinfo/gnupg-devel