agent: plumb in TPM handling
authorJames Bottomley <James.Bottomley@HansenPartnership.com>
Mon, 5 Mar 2018 19:15:29 +0000 (11:15 -0800)
committerWerner Koch <wk@gnupg.org>
Fri, 9 Mar 2018 09:01:59 +0000 (10:01 +0100)
* agent/divert-tpm2.c: New.
* Makefile.am (gpg_agent_SOURCES): Add it.
* agent/command.c (do_one_keyinfo): Fake serialno for TPM.
(cmd_keytotpm): New.
(register_commands): Register KEYTOTPM command.
* agent/pkdecrypt.c (agent_pkdecrypt): Divert to TPM.
* agent/pksign.c (agent_pksign_do): Divert to TPM.

--
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 <James.Bottomley@HansenPartnership.com>
- Added ChangeLog entries

Signed-off-by: Werner Koch <wk@gnupg.org>
agent/Makefile.am
agent/agent.h
agent/command.c
agent/divert-tpm2.c [new file with mode: 0644]
agent/pkdecrypt.c
agent/pksign.c

index 39e69cd..290ef12 100644 (file)
@@ -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 \
index e13ee1f..7a77eb6 100644 (file)
@@ -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,
@@ -533,6 +534,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,
index 32c12d9..c439aa5 100644 (file)
@@ -1192,6 +1192,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);
@@ -2578,6 +2583,57 @@ cmd_keytocard (assuan_context_t ctx, char *line)
 
 
 \f
+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);
+}
+
+
+\f
 static const char hlp_getval[] =
   "GETVAL <key>\n"
   "\n"
@@ -3243,6 +3299,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 (file)
index 0000000..dc3110d
--- /dev/null
@@ -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;
+
+}
index 06a8e0b..6f766ca 100644 (file)
@@ -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));
index f54af08..dae2638 100644 (file)
@@ -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)