]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Add TPM support for GnuTLS
authorDavid Woodhouse <David.Woodhouse@intel.com>
Tue, 12 Jun 2012 22:27:42 +0000 (23:27 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 13 Jun 2012 11:08:39 +0000 (12:08 +0100)
Based on GnuTLS TPM code by Carolin Latze <latze@angry-red-pla.net>
and Tobias Soder.

Like the OpenSSL TPM ENGINE, this only supports a key 'blob' rather than
using keys by UUID. That shouldn't be hard to fix if someone wants it.

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Makefile.am
configure.ac
gnutls.c
library.c
openconnect-internal.h
www/changelog.xml

index 934ab7c891e04085136266f6ae30033b18f6b1c4..5640a7d5bf0d2616b00c7eab84c7c15351d9fbc0 100644 (file)
@@ -14,8 +14,8 @@ man8_MANS = openconnect.8
 AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\""
 openconnect_SOURCES = xml.c main.c dtls.c cstp.c mainloop.c tun.c
 
-openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS)
-openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL)
+openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(TSS_CFLAGS)
+openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(TSS_LIBS) $(LIBINTL)
 
 library_srcs = ssl.c http.c auth.c library.c compat.c @SSL_LIBRARY@.c
 libopenconnect_la_SOURCES = version.c $(library_srcs)
index 7d6ebf6959489787bbe7d4a5ac7f179863d14b01..ce5527f72f6970f10a5b79aeb3216d11005e1f3e 100644 (file)
@@ -210,7 +210,9 @@ if test "$with_gnutls" = "yes"; then
     AC_CHECK_FUNC(gnutls_pkcs12_simple_parse,
                 [AC_DEFINE(HAVE_GNUTLS_PKCS12_SIMPLE_PARSE, 1)], [])
     AC_CHECK_FUNC(gnutls_certificate_set_key,
-                [AC_DEFINE(HAVE_GNUTLS_CERTIFICATE_SET_KEY, 1)], [])
+                [have_set_key=yes
+                 AC_DEFINE(HAVE_GNUTLS_CERTIFICATE_SET_KEY, 1)],
+                [have_set_key=no])
     if test "$with_openssl" = "" || test "$with_openssl" = "no"; then
        AC_CHECK_FUNC(gnutls_session_set_premaster,
                 [have_gnutls_dtls=yes], [have_gnutls_dtls=no])
@@ -244,6 +246,21 @@ if test "$with_gnutls" = "yes"; then
                 [PKG_CHECK_MODULES(P11KIT, p11-kit-1, [AC_DEFINE(HAVE_P11KIT)
                                          AC_SUBST(P11KIT_PC, p11-kit-1)], [:])], [])
     LIBS="$oldLIBS"
+    if test "$have_set_key" = "yes"; then
+       LIBS="$oldlibs -ltspi"
+       AC_MSG_CHECKING([for tss library])
+       AC_LINK_IFELSE([AC_LANG_PROGRAM([
+                  #include <trousers/tss.h>
+                  #include <trousers/trousers.h>],[
+                  int err = Tspi_Context_Create((void *)0);
+                  Trspi_Error_String(err);])],
+                 [AC_MSG_RESULT(yes)
+                  AC_SUBST([TSS_LIBS], [-ltspi])
+                  AC_SUBST([TSS_CFLAGS], [])
+                  AC_DEFINE(HAVE_TROUSERS, 1)],
+                 [AC_MSG_RESULT(no)])
+       LIBS="$oldlibs"
+    fi
 elif test "$with_gnutls" != "" && test "$with_gnutls" != "no"; then
     AC_MSG_ERROR([Values other than 'yes' or 'no' for --with-gnutls are not supported])
 fi
index f673df92c2dd4ac42199cbcb16986b512c3cf93d..c02be1227d0244faeea41071c32ef4d160f33963 100644 (file)
--- a/gnutls.c
+++ b/gnutls.c
 #include <gnutls/pkcs12.h>
 #include <gnutls/abstract.h>
 
+#ifdef HAVE_TROUSERS
+#include <trousers/tss.h>
+#include <trousers/trousers.h>
+#endif
 #ifdef HAVE_P11KIT
 #include <p11-kit/p11-kit.h>
 #include <p11-kit/pkcs11.h>
@@ -424,6 +428,182 @@ static int get_cert_name(gnutls_x509_crt_t cert, char *name, size_t namelen)
        return 0;
 }
 
+#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.\n"));
+               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.\n"));
+               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) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("TPM hash signature failed\n"));
+               return GNUTLS_E_PK_SIGN_FAILED;
+       }
+       return 0;
+}
+
+static int load_tpm_key(struct openconnect_info *vpninfo, gnutls_datum_t *fdata, gnutls_privkey_t *pkey)
+{
+       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) {
+               if (!pass) {
+                       err = request_passphrase(vpninfo, &pass, _("Enter TPM SRK PIN:"));
+                       if (err)
+                               goto out_srkpol;
+               }
+               /* We don't seem to get the error here... */
+               err = Tspi_Policy_SetSecret(vpninfo->srk_policy, TSS_SECRET_MODE_PLAIN,
+                                           strlen(pass), (void *)pass);
+               if (err) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to set TPM PIN: %s\n"),
+                                    Trspi_Error_String(err));
+                       goto out_srkpol;
+               }
+
+               free(pass);
+               pass = NULL;
+
+               /* ... 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;
+
+               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;
+       }
+
+       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);
+
+       /* FIXME: Get key id using TSS_TSPATTRIB_KEYINFO_RSA_MODULUS etc. so
+          that we can ensure we have a matching cert. */
+       free (asn1.data);
+       return 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 */
+
 static int load_certificate(struct openconnect_info *vpninfo)
 {
        gnutls_datum_t fdata;
@@ -691,9 +871,28 @@ static int load_certificate(struct openconnect_info *vpninfo)
        if (vpninfo->cert_type == CERT_TYPE_TPM ||
            (vpninfo->cert_type == CERT_TYPE_UNKNOWN &&
             strstr((char *)fdata.data, "-----BEGIN TSS KEY BLOB-----"))) {
+#ifndef HAVE_TROUSERS
                vpn_progress(vpninfo, PRG_ERR,
                             _("This version of OpenConnect was built without TPM support\n"));
                return -EINVAL;
+#else
+               ret = load_tpm_key(vpninfo, &fdata, &pkey);
+               if (ret)
+                       goto out;
+
+               if (!cert) {
+                       /* FIXME: How do we check which cert matches the pkey?
+                          For now we just assume that the first one in the list is the right one. */
+                       cert = extra_certs[0];
+
+                       /* Move the rest of the array down */
+                       for (i = 0; i < nr_extra_certs - 1; i++)
+                               extra_certs[i] = extra_certs[i+1];
+
+                       nr_extra_certs--;
+               }
+               goto got_key;
+#endif
        }
 
        gnutls_x509_privkey_init(&key);
@@ -1278,6 +1477,24 @@ void openconnect_close_https(struct openconnect_info *vpninfo, int final)
                                free(cache);
                        }
                }
+#endif
+#ifdef HAVE_TROUSERS
+               if (vpninfo->tpm_key) {
+                       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->tpm_key);
+                       vpninfo->tpm_key = 0;
+               }
+               if (vpninfo->srk_policy) {
+                       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->srk_policy);
+                       vpninfo->srk_policy = 0;
+               }
+               if (vpninfo->srk) {
+                       Tspi_Context_CloseObject(vpninfo->tpm_context, vpninfo->srk);
+                       vpninfo->srk = 0;
+               }
+               if (vpninfo->tpm_context) {
+                       Tspi_Context_Close(vpninfo->tpm_context);
+                       vpninfo->tpm_context = 0;
+               }
 #endif
        }
 }
index 9c8c8a0312fd7c96f8d399fcdeec7c4a107ddd2c..121d4ec6b86c6db914e1918ae02316f405849ddc 100644 (file)
--- a/library.c
+++ b/library.c
@@ -252,6 +252,8 @@ int openconnect_has_tss_blob_support(void)
                ENGINE_free(e);
                return 1;
        }
+#elif defined (OPENCONNECT_GNUTLS) && defined (HAVE_TROUSERS)
+       return 1;
 #endif
        return 0;
 }
index 91a97313153eb47b1b9501a9731c72b971964002..d40e91859d1f28ed2e261f68c07cf56d42d8aa75 100644 (file)
 #if defined (OPENCONNECT_GNUTLS)
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
+#ifdef HAVE_TROUSERS
+#include <trousers/tss.h>
+#include <trousers/trousers.h>
+#endif
 #endif
 
 #include <zlib.h>
@@ -168,6 +172,12 @@ struct openconnect_info {
        gnutls_session_t https_sess;
        gnutls_certificate_credentials_t https_cred;
        struct pin_cache *pin_cache;
+#ifdef HAVE_TROUSERS
+       TSS_HCONTEXT tpm_context;
+       TSS_HKEY srk;
+       TSS_HPOLICY srk_policy;
+       TSS_HKEY tpm_key;
+#endif
 #endif
        struct keepalive_info ssl_times;
        int owe_ssl_dpd_response;
index e8432152928ff416c02882d1ee2f95dc4d35bb44..3c42b765a0222a1343d697a124dba39ba3309849 100644 (file)
@@ -17,6 +17,7 @@
 <ul>
    <li><b>OpenConnect HEAD</b>
      <ul>
+       <li>Enable native TPM support when built with GnuTLS.</li>
        <li>Enable PKCS#11 token support when built with GnuTLS.</li>
        <li>Eliminate all SSL library exposure through <tt>libopenconnect</tt>.</li>
        <li>Parse split DNS information, provide <tt>$CISCO_SPLIT_DNS</tt> environment variable to <tt>vpnc-script</tt>.</li>