Move TPM code out into gnutls_tpm.c
authorDavid Woodhouse <David.Woodhouse@intel.com>
Thu, 14 Jun 2012 21:48:38 +0000 (22:48 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Thu, 14 Jun 2012 21:58:48 +0000 (22:58 +0100)
Slightly reduce the #ifdef hell in gnutls.c

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Makefile.am
gnutls.c
gnutls.h
gnutls_tpm.c [new file with mode: 0644]

index c2d3452..9b7f938 100644 (file)
@@ -18,7 +18,7 @@ openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPRO
 openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL)
 
 library_srcs = ssl.c http.c auth.c library.c compat.c
-lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c
+lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c
 lib_srcs_openssl = openssl.c
 if OPENCONNECT_GNUTLS
 library_srcs += $(lib_srcs_gnutls)
index be40dda..d8a2d51 100644 (file)
--- a/gnutls.c
+++ b/gnutls.c
@@ -429,323 +429,8 @@ static int get_cert_name(gnutls_x509_crt_t cert, char *name, size_t namelen)
        return 0;
 }
 
-/* In GnuTLS 2.12 this can't be a real private key; we have to use the sign_callback
-   instead. But we want to set the 'pkey' variable to *something* non-NULL in order
-   to indicate that we aren't just using an x509 key. */
-#define OPENCONNECT_TPM_PKEY ((void *)1UL)
-
-#ifdef HAVE_TROUSERS
-/* TPM code based on client-tpm.c from Carolin Latze <latze@angry-red-pla.net>
-   and Tobias Soder */
-static int tpm_sign_fn(gnutls_privkey_t key, void *_vpninfo,
-                      const gnutls_datum_t *data, gnutls_datum_t *sig)
-{
-       struct openconnect_info *vpninfo = _vpninfo;
-       TSS_HHASH hash;
-       int err;
-
-       vpn_progress(vpninfo, PRG_TRACE,
-                    _("TPM sign function called for %d bytes.\n"),
-                    data->size);
-
-       err = Tspi_Context_CreateObject(vpninfo->tpm_context, TSS_OBJECT_TYPE_HASH,
-                                       TSS_HASH_OTHER, &hash);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Failed to create TPM hash object: %s\n"),
-                            Trspi_Error_String(err));
-               return GNUTLS_E_PK_SIGN_FAILED;
-       }
-       err = Tspi_Hash_SetHashValue(hash, data->size, data->data);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Failed to set value in TPM hash object: %s\n"),
-                            Trspi_Error_String(err));
-               Tspi_Context_CloseObject(vpninfo->tpm_context, hash);
-               return GNUTLS_E_PK_SIGN_FAILED;
-       }
-       err = Tspi_Hash_Sign(hash, vpninfo->tpm_key, &sig->size, &sig->data);
-       Tspi_Context_CloseObject(vpninfo->tpm_context, hash);
-       if (err) {
-               if (vpninfo->tpm_key_policy || err != TPM_E_AUTHFAIL)
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("TPM hash signature failed: %s\n"),
-                                    Trspi_Error_String(err));
-               if (err == TPM_E_AUTHFAIL)
-                       return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
-               else
-                       return GNUTLS_E_PK_SIGN_FAILED;
-       }
-       return 0;
-}
-static int sign_dummy_data(struct openconnect_info *vpninfo, gnutls_privkey_t pkey,
-                          const gnutls_datum_t *data, gnutls_datum_t *sig);
-
-static int load_tpm_key(struct openconnect_info *vpninfo, gnutls_datum_t *fdata, gnutls_privkey_t *pkey,
-                       gnutls_datum_t *pkey_sig)
-{
-       static const TSS_UUID SRK_UUID = TSS_UUID_SRK;
-       gnutls_datum_t asn1;
-       unsigned int tss_len;
-       char *pass;
-       int ofs, err;
-
-       err = gnutls_pem_base64_decode_alloc("TSS KEY BLOB", fdata, &asn1);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Error decoding TSS key blob: %s\n"),
-                            gnutls_strerror(err));
-               return -EINVAL;
-       }
-       /* Ick. We have to parse the ASN1 OCTET_STRING for ourselves. */
-       if (asn1.size < 2 || asn1.data[0] != 0x04 /* OCTET_STRING */) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Error in TSS key blob\n"));
-               goto out_blob;
-       }
-
-       tss_len = asn1.data[1];
-       ofs = 2;
-       if (tss_len & 0x80) {
-               int lenlen = tss_len & 0x7f;
-
-               if (asn1.size < 2 + lenlen || lenlen > 3) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Error in TSS key blob\n"));
-                       goto out_blob;
-               }
-
-               tss_len = 0;
-               while (lenlen) {
-                       tss_len <<= 8;
-                       tss_len |= asn1.data[ofs++];
-                       lenlen--;
-               }
-       }
-       if (tss_len + ofs != asn1.size) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Error in TSS key blob\n"));
-               goto out_blob;
-       }
-
-       err = Tspi_Context_Create(&vpninfo->tpm_context);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Failed to create TPM context: %s\n"),
-                            Trspi_Error_String(err));
-               goto out_blob;
-       }
-       err = Tspi_Context_Connect(vpninfo->tpm_context, NULL);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Failed to connect TPM context: %s\n"),
-                            Trspi_Error_String(err));
-               goto out_context;
-       }
-       err = Tspi_Context_LoadKeyByUUID(vpninfo->tpm_context, TSS_PS_TYPE_SYSTEM,
-                                        SRK_UUID, &vpninfo->srk);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Failed to load TPM SRK key: %s\n"),
-                            Trspi_Error_String(err));
-               goto out_context;
-       }
-       err = Tspi_GetPolicyObject(vpninfo->srk, TSS_POLICY_USAGE, &vpninfo->srk_policy);
-       if (err) {
-               vpn_progress(vpninfo, PRG_ERR,
-                            _("Failed to load TPM SRK policy object: %s\n"),
-                            Trspi_Error_String(err));
-               goto out_srk;
-       }
-
-       pass = vpninfo->cert_password;
-       vpninfo->cert_password = NULL;
-       while (1) {
-               static const char nullpass[20];
-
-               /* We don't seem to get the error here... */
-               if (pass)
-                       err = Tspi_Policy_SetSecret(vpninfo->srk_policy, TSS_SECRET_MODE_PLAIN,
-                                                   strlen(pass), (BYTE *)pass);
-               else /* Well-known NULL key */
-                       err = Tspi_Policy_SetSecret(vpninfo->srk_policy, TSS_SECRET_MODE_SHA1,
-                                                   sizeof(nullpass), (BYTE *)nullpass);
-               if (err) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to set TPM PIN: %s\n"),
-                                    Trspi_Error_String(err));
-                       goto out_srkpol;
-               }
-
-               free(pass);
-
-               /* ... we get it here instead. */
-               err = Tspi_Context_LoadKeyByBlob(vpninfo->tpm_context, vpninfo->srk,
-                                                tss_len, asn1.data + ofs, &vpninfo->tpm_key);
-               if (!err)
-                       break;
-
-               if (pass)
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to load TPM key blob: %s\n"),
-                                    Trspi_Error_String(err));
-
-               if (err != TPM_E_AUTHFAIL)
-                       goto out_srkpol;
-
-               err = request_passphrase(vpninfo, "openconnect_tpm_srk",
-                                        &pass, _("Enter TPM SRK PIN:"));
-               if (err)
-                       goto out_srkpol;
-       }
-
-#ifdef HAVE_GNUTLS_CERTIFICATE_SET_KEY
-       gnutls_privkey_init(pkey);
-       /* This would be nicer if there was a destructor callback. I could
-          allocate a data structure with the TPM handles and the vpninfo
-          pointer, and destroy that properly when the key is destroyed. */
-       gnutls_privkey_import_ext(*pkey, GNUTLS_PK_RSA, vpninfo, tpm_sign_fn, NULL, 0);
-#else
-       *pkey = OPENCONNECT_TPM_PKEY;
-#endif
-
- retry_sign:
-       err = sign_dummy_data(vpninfo, *pkey, fdata, pkey_sig);
-       if (err == GNUTLS_E_INSUFFICIENT_CREDENTIALS) {
-               if (!vpninfo->tpm_key_policy) {
-                       err = Tspi_Context_CreateObject(vpninfo->tpm_context,
-                                                       TSS_OBJECT_TYPE_POLICY,
-                                                       TSS_POLICY_USAGE,
-                                                       &vpninfo->tpm_key_policy);
-                       if (err) {
-                               vpn_progress(vpninfo, PRG_ERR,
-                                            _("Failed to create key policy object: %s\n"),
-                                            Trspi_Error_String(err));
-                               goto out_key;
-                       }
-                       err = Tspi_Policy_AssignToObject(vpninfo->tpm_key_policy,
-                                                        vpninfo->tpm_key);
-                       if (err) {
-                               vpn_progress(vpninfo, PRG_ERR,
-                                            _("Failed to assign policy to key: %s\n"),
-                                            Trspi_Error_String(err));
-                               goto out_key_policy;
-                       }
-               }
-               err = request_passphrase(vpninfo, "openconnect_tpm_key",
-                                        &pass, _("Enter TPM key PIN:"));
-               if (err)
-                       goto out_key_policy;
-
-               err = Tspi_Policy_SetSecret(vpninfo->tpm_key_policy,
-                                           TSS_SECRET_MODE_PLAIN,
-                                           strlen(pass), (void *)pass);
-               free (pass);
-
-               if (err) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to set key PIN: %s\n"),
-                                    Trspi_Error_String(err));
-                       goto out_key_policy;
-               }
-               goto retry_sign;
-       }
-
-       free (asn1.data);
-       return 0;
- out_key_policy:
-       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->tpm_key_policy);
-       vpninfo->tpm_key_policy = 0;
- out_key:
-       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->tpm_key);
-       vpninfo->tpm_key = 0;
- out_srkpol:
-       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->srk_policy);
-       vpninfo->srk_policy = 0;
- out_srk:
-       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->srk);
-       vpninfo->srk = 0;
- out_context:
-       Tspi_Context_Close(vpninfo->tpm_context);
-       vpninfo->tpm_context = 0;
- out_blob:
-       free (asn1.data);
-       return -EIO;
-}
-
-#ifndef HAVE_GNUTLS_CERTIFICATE_SET_KEY
-/* We *want* to use gnutls_privkey_import_ext() to create a privkey with our
-   own signing function tpm_sign_fn(). But GnuTLS 2.12 doesn't support that,
-   so instead we have to register this sign_callback function with the
-   *session* */
-static int gtls_tpm_sign_cb(gnutls_session_t sess, void *_vpninfo,
-                           gnutls_certificate_type_t cert_type,
-                           const gnutls_datum_t *cert, const gnutls_datum_t *data,
-                           gnutls_datum_t *sig)
-{
-       struct openconnect_info *vpninfo = _vpninfo;
-
-       if (cert_type != GNUTLS_CRT_X509)
-               return GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
-
-       return tpm_sign_fn(NULL, vpninfo, data, sig);
-}
-#endif /* !SET_KEY */
-#endif /* HAVE_TROUSERS */
-
-#if defined (HAVE_P11KIT) || defined (HAVE_TROUSERS)
-/* We do a test signing of data, to check which certificate matches our key.
-   This helper does that with with gnutls_privkey_sign_data() in the sane
-   cases, or in the case of GnuTLS 2.12 with TPM it will generate the
-   appropriate BER-encoded hash and call our special TPM-signing function. */
-static int sign_dummy_data(struct openconnect_info *vpninfo, gnutls_privkey_t pkey,
-                          const gnutls_datum_t *data, gnutls_datum_t *sig)
-{
-#if defined (HAVE_TROUSERS) && !defined (GNUTLS_CERTIFICATE_SET_KEY)
-       /* Actually the thing we care about is gnutls_privkey_import_ext()
-          not gnutls_certificate_set_key() — if we could create a privkey
-          to represent the TPM key, we wouldn't have to jump through these
-          hoops to reproduce what gnutls_privkey_sign_data() would have
-          done for us. But there's no point in a separate check for the
-          import_ext() function; it arrived with set_key() in GnuTLS 3.0 */
-
-       if (pkey == OPENCONNECT_TPM_PKEY) {
-               static const unsigned char ber_encode[15] = {
-                       0x30, 0x21, /* SEQUENCE, length 31 */
-                       0x30, 0x09,   /* SEQUENCE, length 9 */
-                       0x06, 0x05,      /* OBJECT_ID, length 5 */
-                       0x2b, 0x0e, 0x03, 0x02, 0x1a,  /* SHA1: 1.3.14.3.2.26 */
-                       0x05, 0x00,      /* NULL (parameters) */
-                       0x04, 0x14,      /* OCTET_STRING, length 20 */
-                       /* followed by the 20-byte sha1 */
-               };
-               gnutls_datum_t hash;
-               unsigned char digest[sizeof(ber_encode) + SHA1_SIZE];
-               size_t shalen = SHA1_SIZE;
-               int err;
-
-               err = gnutls_fingerprint(GNUTLS_DIG_SHA1, data,
-                                        &digest[sizeof(ber_encode)], &shalen);
-               if (err) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to SHA1 input data for signing: %s\n"),
-                                    gnutls_strerror(err));
-                       return err;
-               }
-
-               memcpy(digest, ber_encode, sizeof(ber_encode));
-
-               hash.data = digest;
-               hash.size = sizeof(digest);
-
-               return tpm_sign_fn(NULL, vpninfo, &hash, sig);
-       }
-#endif
-       return gnutls_privkey_sign_data(pkey, GNUTLS_DIG_SHA1, 0, data, sig);
-}
-
-
-#ifndef HAVE_GNUTLS_CERTIFICATE_SET_KEY
+#if !defined (HAVE_GNUTLS_CERTIFICATE_SET_KEY) && \
+       (defined (HAVE_P11KIT) || defined (HAVE_TROUSERS))
 /* For GnuTLS 2.12 even if we *have* a privkey (as we do for PKCS#11), we
    can't register it. So we have to use the cert_callback function. This
    just hands out the certificate chain we prepared in load_certificate().
@@ -754,7 +439,7 @@ static int sign_dummy_data(struct openconnect_info *vpninfo, gnutls_privkey_t pk
    handle that. */
 static int gtls_cert_cb(gnutls_session_t sess, const gnutls_datum_t *req_ca_dn,
                        int nreqs, const gnutls_pk_algorithm_t *pk_algos,
-                       int pk_algos_length, gnutls_retr2_stst) {
+                       int pk_algos_length, gnutls_retr2_st *st) {
 
        struct openconnect_info *vpninfo = gnutls_session_get_ptr(sess);
        int algo = GNUTLS_PK_RSA; /* TPM */
@@ -781,8 +466,7 @@ static int gtls_cert_cb(gnutls_session_t sess, const gnutls_datum_t *req_ca_dn,
 
        return 0;
 }
-#endif /* !SET_KEY */
-#endif /* P11KIT || TROUSERS */
+#endif /* !SET_KEY && (P11KIT || TROUSERS) */
 
 static int load_certificate(struct openconnect_info *vpninfo)
 {
@@ -1624,7 +1308,7 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
        gnutls_session_set_ptr (vpninfo->https_sess, (void *) vpninfo);
 #if defined(HAVE_TROUSERS) && !defined(HAVE_GNUTLS_CERTIFICATE_SET_KEY)
        if (vpninfo->my_pkey == OPENCONNECT_TPM_PKEY)
-               gnutls_sign_callback_set(vpninfo->https_sess, gtls_tpm_sign_cb, vpninfo);
+               gnutls_sign_callback_set(vpninfo->https_sess, gtls2_tpm_sign_cb, vpninfo);
 #endif
        err = gnutls_priority_set_direct (vpninfo->https_sess, "NONE:+VERS-TLS1.0:+SHA1:+AES-128-CBC:+RSA:+COMP-NULL:%COMPAT:%DISABLE_SAFE_RENEGOTIATION", NULL);
        if (err) {
index ab34dc5..68d59d2 100644 (file)
--- a/gnutls.h
+++ b/gnutls.h
@@ -27,6 +27,9 @@
 
 #include <gnutls/gnutls.h>
 #include <gnutls/pkcs12.h>
+#include <gnutls/abstract.h>
+
+#include "openconnect-internal.h"
 
 #ifndef HAVE_GNUTLS_PKCS12_SIMPLE_PARSE
 /* If we're using a version of GnuTLS from before this was 
@@ -42,4 +45,35 @@ int gnutls_pkcs12_simple_parse (gnutls_pkcs12_t p12, const char *password,
 
 #endif /* !HAVE_GNUTLS_PKCS12_SIMPLE_PARSE */
 
+
+#ifndef HAVE_GNUTLS_CERTIFICATE_SET_KEY
+int gtls2_tpm_sign_cb(gnutls_session_t sess, void *_vpninfo,
+                     gnutls_certificate_type_t cert_type,
+                     const gnutls_datum_t *cert, const gnutls_datum_t *data,
+                     gnutls_datum_t *sig);
+int gtls2_tpm_sign_dummy_data(struct openconnect_info *vpninfo,
+                             const gnutls_datum_t *data,
+                             gnutls_datum_t *sig);
+#endif /* !HAVE_GNUTLS_CERTIFICATE_SET_KEY */
+
+/* In GnuTLS 2.12 this can't be a real private key; we have to use the sign_callback
+   instead. But we want to set the 'pkey' variable to *something* non-NULL in order
+   to indicate that we aren't just using an x509 key. */
+#define OPENCONNECT_TPM_PKEY ((void *)1UL)
+
+static inline int sign_dummy_data(struct openconnect_info *vpninfo,
+                                 gnutls_privkey_t pkey,
+                                 const gnutls_datum_t *data,
+                                 gnutls_datum_t *sig)
+{
+#if defined (HAVE_TROUSERS) && !defined(HAVE_GNUTLS_CERTIFICATE_SET_KEY)
+       if (pkey == OPENCONNECT_TPM_PKEY)
+               return gtls2_tpm_sign_dummy_data(vpninfo, data, sig);
+#endif
+       return gnutls_privkey_sign_data(pkey, GNUTLS_DIG_SHA1, 0, data, sig);
+}
+
+int load_tpm_key(struct openconnect_info *vpninfo, gnutls_datum_t *fdata,
+                gnutls_privkey_t *pkey, gnutls_datum_t *pkey_sig);
+
 #endif /* __OPENCONNECT_GNUTLS_H__ */
diff --git a/gnutls_tpm.c b/gnutls_tpm.c
new file mode 100644 (file)
index 0000000..bc62d76
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * OpenConnect (SSL + DTLS) VPN client
+ *
+ * Copyright © 2008-2012 Intel Corporation.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to:
+ *
+ *   Free Software Foundation, Inc.
+ *   51 Franklin Street, Fifth Floor,
+ *   Boston, MA 02110-1301 USA
+ */
+
+/*
+ * TPM code based on client-tpm.c from
+ * Carolin Latze <latze@angry-red-pla.net> and Tobias Soder
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <gnutls/gnutls.h>
+#include "openconnect-internal.h"
+
+#include "gnutls.h"
+
+#ifdef HAVE_TROUSERS
+
+/* Signing function for TPM privkeys, set with gnutls_privkey_import_ext() */
+static int tpm_sign_fn(gnutls_privkey_t key, void *_vpninfo,
+                      const gnutls_datum_t *data, gnutls_datum_t *sig);
+
+
+#ifndef HAVE_GNUTLS_CERTIFICATE_SET_KEY
+/* We *want* to use gnutls_privkey_import_ext() to create a privkey with our
+   own signing function tpm_sign_fn(). But GnuTLS 2.12 doesn't support that,
+   so instead we have to register this sign_callback function with the
+   *session* */
+int gtls2_tpm_sign_cb(gnutls_session_t sess, void *_vpninfo,
+                     gnutls_certificate_type_t cert_type,
+                     const gnutls_datum_t *cert, const gnutls_datum_t *data,
+                     gnutls_datum_t *sig)
+{
+       struct openconnect_info *vpninfo = _vpninfo;
+
+       if (cert_type != GNUTLS_CRT_X509)
+               return GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
+
+       return tpm_sign_fn(NULL, vpninfo, data, sig);
+}
+
+/* In GnuTLS 2.12 since we don't have a normal privkey and hence can't just
+   use gnutls_privkey_sign_data() with it, we have to jump through hoops to
+   prepare the hash in exactly the right way and call our internal TPM
+   signing function. */
+int gtls2_tpm_sign_dummy_data(struct openconnect_info *vpninfo,
+                             const gnutls_datum_t *data,
+                             gnutls_datum_t *sig)
+{
+       static const unsigned char ber_encode[15] = {
+               0x30, 0x21, /* SEQUENCE, length 31 */
+               0x30, 0x09,   /* SEQUENCE, length 9 */
+               0x06, 0x05,      /* OBJECT_ID, length 5 */
+               0x2b, 0x0e, 0x03, 0x02, 0x1a,  /* SHA1 OID: 1.3.14.3.2.26 */
+               0x05, 0x00,      /* NULL (parameters) */
+               0x04, 0x14,   /* OCTET_STRING, length 20 */
+               /* followed by the 20-byte sha1 */
+       };
+       gnutls_datum_t hash;
+       unsigned char digest[sizeof(ber_encode) + SHA1_SIZE];
+       size_t shalen = SHA1_SIZE;
+       int err;
+
+       err = gnutls_fingerprint(GNUTLS_DIG_SHA1, data,
+                                &digest[sizeof(ber_encode)], &shalen);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to SHA1 input data for signing: %s\n"),
+                            gnutls_strerror(err));
+               return err;
+       }
+
+       memcpy(digest, ber_encode, sizeof(ber_encode));
+
+       hash.data = digest;
+       hash.size = sizeof(digest);
+
+       return tpm_sign_fn(NULL, vpninfo, &hash, sig);
+}
+#endif /* !HAVE_GNUTLS_CERTIFICATE_SET_KEY */
+
+static int tpm_sign_fn(gnutls_privkey_t key, void *_vpninfo,
+                      const gnutls_datum_t *data, gnutls_datum_t *sig)
+{
+       struct openconnect_info *vpninfo = _vpninfo;
+       TSS_HHASH hash;
+       int err;
+
+       vpn_progress(vpninfo, PRG_TRACE,
+                    _("TPM sign function called for %d bytes.\n"),
+                    data->size);
+
+       err = Tspi_Context_CreateObject(vpninfo->tpm_context, TSS_OBJECT_TYPE_HASH,
+                                       TSS_HASH_OTHER, &hash);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to create TPM hash object: %s\n"),
+                            Trspi_Error_String(err));
+               return GNUTLS_E_PK_SIGN_FAILED;
+       }
+       err = Tspi_Hash_SetHashValue(hash, data->size, data->data);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to set value in TPM hash object: %s\n"),
+                            Trspi_Error_String(err));
+               Tspi_Context_CloseObject(vpninfo->tpm_context, hash);
+               return GNUTLS_E_PK_SIGN_FAILED;
+       }
+       err = Tspi_Hash_Sign(hash, vpninfo->tpm_key, &sig->size, &sig->data);
+       Tspi_Context_CloseObject(vpninfo->tpm_context, hash);
+       if (err) {
+               if (vpninfo->tpm_key_policy || err != TPM_E_AUTHFAIL)
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("TPM hash signature failed: %s\n"),
+                                    Trspi_Error_String(err));
+               if (err == TPM_E_AUTHFAIL)
+                       return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
+               else
+                       return GNUTLS_E_PK_SIGN_FAILED;
+       }
+       return 0;
+}
+
+int load_tpm_key(struct openconnect_info *vpninfo, gnutls_datum_t *fdata,
+                gnutls_privkey_t *pkey, gnutls_datum_t *pkey_sig)
+{
+       static const TSS_UUID SRK_UUID = TSS_UUID_SRK;
+       gnutls_datum_t asn1;
+       unsigned int tss_len;
+       char *pass;
+       int ofs, err;
+
+       err = gnutls_pem_base64_decode_alloc("TSS KEY BLOB", fdata, &asn1);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error decoding TSS key blob: %s\n"),
+                            gnutls_strerror(err));
+               return -EINVAL;
+       }
+       /* Ick. We have to parse the ASN1 OCTET_STRING for ourselves. */
+       if (asn1.size < 2 || asn1.data[0] != 0x04 /* OCTET_STRING */) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error in TSS key blob\n"));
+               goto out_blob;
+       }
+
+       tss_len = asn1.data[1];
+       ofs = 2;
+       if (tss_len & 0x80) {
+               int lenlen = tss_len & 0x7f;
+
+               if (asn1.size < 2 + lenlen || lenlen > 3) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Error in TSS key blob\n"));
+                       goto out_blob;
+               }
+
+               tss_len = 0;
+               while (lenlen) {
+                       tss_len <<= 8;
+                       tss_len |= asn1.data[ofs++];
+                       lenlen--;
+               }
+       }
+       if (tss_len + ofs != asn1.size) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error in TSS key blob\n"));
+               goto out_blob;
+       }
+
+       err = Tspi_Context_Create(&vpninfo->tpm_context);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to create TPM context: %s\n"),
+                            Trspi_Error_String(err));
+               goto out_blob;
+       }
+       err = Tspi_Context_Connect(vpninfo->tpm_context, NULL);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to connect TPM context: %s\n"),
+                            Trspi_Error_String(err));
+               goto out_context;
+       }
+       err = Tspi_Context_LoadKeyByUUID(vpninfo->tpm_context, TSS_PS_TYPE_SYSTEM,
+                                        SRK_UUID, &vpninfo->srk);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to load TPM SRK key: %s\n"),
+                            Trspi_Error_String(err));
+               goto out_context;
+       }
+       err = Tspi_GetPolicyObject(vpninfo->srk, TSS_POLICY_USAGE, &vpninfo->srk_policy);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to load TPM SRK policy object: %s\n"),
+                            Trspi_Error_String(err));
+               goto out_srk;
+       }
+
+       pass = vpninfo->cert_password;
+       vpninfo->cert_password = NULL;
+       while (1) {
+               static const char nullpass[20];
+
+               /* We don't seem to get the error here... */
+               if (pass)
+                       err = Tspi_Policy_SetSecret(vpninfo->srk_policy,
+                                                   TSS_SECRET_MODE_PLAIN,
+                                                   strlen(pass), (BYTE *)pass);
+               else /* Well-known NULL key */
+                       err = Tspi_Policy_SetSecret(vpninfo->srk_policy,
+                                                   TSS_SECRET_MODE_SHA1,
+                                                   sizeof(nullpass), (BYTE *)nullpass);
+               if (err) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to set TPM PIN: %s\n"),
+                                    Trspi_Error_String(err));
+                       goto out_srkpol;
+               }
+
+               free(pass);
+
+               /* ... we get it here instead. */
+               err = Tspi_Context_LoadKeyByBlob(vpninfo->tpm_context, vpninfo->srk,
+                                                tss_len, asn1.data + ofs,
+                                                &vpninfo->tpm_key);
+               if (!err)
+                       break;
+
+               if (pass)
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to load TPM key blob: %s\n"),
+                                    Trspi_Error_String(err));
+
+               if (err != TPM_E_AUTHFAIL)
+                       goto out_srkpol;
+
+               err = request_passphrase(vpninfo, "openconnect_tpm_srk",
+                                        &pass, _("Enter TPM SRK PIN:"));
+               if (err)
+                       goto out_srkpol;
+       }
+
+#ifdef HAVE_GNUTLS_CERTIFICATE_SET_KEY
+       gnutls_privkey_init(pkey);
+       /* This would be nicer if there was a destructor callback. I could
+          allocate a data structure with the TPM handles and the vpninfo
+          pointer, and destroy that properly when the key is destroyed. */
+       gnutls_privkey_import_ext(*pkey, GNUTLS_PK_RSA, vpninfo, tpm_sign_fn, NULL, 0);
+#else
+       *pkey = OPENCONNECT_TPM_PKEY;
+#endif
+
+ retry_sign:
+       err = sign_dummy_data(vpninfo, *pkey, fdata, pkey_sig);
+       if (err == GNUTLS_E_INSUFFICIENT_CREDENTIALS) {
+               if (!vpninfo->tpm_key_policy) {
+                       err = Tspi_Context_CreateObject(vpninfo->tpm_context,
+                                                       TSS_OBJECT_TYPE_POLICY,
+                                                       TSS_POLICY_USAGE,
+                                                       &vpninfo->tpm_key_policy);
+                       if (err) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Failed to create key policy object: %s\n"),
+                                            Trspi_Error_String(err));
+                               goto out_key;
+                       }
+                       err = Tspi_Policy_AssignToObject(vpninfo->tpm_key_policy,
+                                                        vpninfo->tpm_key);
+                       if (err) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Failed to assign policy to key: %s\n"),
+                                            Trspi_Error_String(err));
+                               goto out_key_policy;
+                       }
+               }
+               err = request_passphrase(vpninfo, "openconnect_tpm_key",
+                                        &pass, _("Enter TPM key PIN:"));
+               if (err)
+                       goto out_key_policy;
+
+               err = Tspi_Policy_SetSecret(vpninfo->tpm_key_policy,
+                                           TSS_SECRET_MODE_PLAIN,
+                                           strlen(pass), (void *)pass);
+               free (pass);
+
+               if (err) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to set key PIN: %s\n"),
+                                    Trspi_Error_String(err));
+                       goto out_key_policy;
+               }
+               goto retry_sign;
+       }
+
+       free (asn1.data);
+       return 0;
+ out_key_policy:
+       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->tpm_key_policy);
+       vpninfo->tpm_key_policy = 0;
+ out_key:
+       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->tpm_key);
+       vpninfo->tpm_key = 0;
+ out_srkpol:
+       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->srk_policy);
+       vpninfo->srk_policy = 0;
+ out_srk:
+       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->srk);
+       vpninfo->srk = 0;
+ out_context:
+       Tspi_Context_Close(vpninfo->tpm_context);
+       vpninfo->tpm_context = 0;
+ out_blob:
+       free (asn1.data);
+       return -EIO;
+}
+
+#endif /* HAVE_TROUSERS */