From 63077480103397228ba13e1b31304c7cc2a49032 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 12 Jun 2012 23:27:42 +0100 Subject: [PATCH] Add TPM support for GnuTLS Based on GnuTLS TPM code by Carolin Latze 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 --- Makefile.am | 4 +- configure.ac | 19 +++- gnutls.c | 217 +++++++++++++++++++++++++++++++++++++++++ library.c | 2 + openconnect-internal.h | 10 ++ www/changelog.xml | 1 + 6 files changed, 250 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 934ab7c8..5640a7d5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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) diff --git a/configure.ac b/configure.ac index 7d6ebf69..ce5527f7 100644 --- a/configure.ac +++ b/configure.ac @@ -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 + #include ],[ + 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 diff --git a/gnutls.c b/gnutls.c index f673df92..c02be122 100644 --- a/gnutls.c +++ b/gnutls.c @@ -43,6 +43,10 @@ #include #include +#ifdef HAVE_TROUSERS +#include +#include +#endif #ifdef HAVE_P11KIT #include #include @@ -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 + 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 } } diff --git a/library.c b/library.c index 9c8c8a03..121d4ec6 100644 --- 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; } diff --git a/openconnect-internal.h b/openconnect-internal.h index 91a97313..d40e9185 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -35,6 +35,10 @@ #if defined (OPENCONNECT_GNUTLS) #include #include +#ifdef HAVE_TROUSERS +#include +#include +#endif #endif #include @@ -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; diff --git a/www/changelog.xml b/www/changelog.xml index e8432152..3c42b765 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -17,6 +17,7 @@
  • OpenConnect HEAD
      +
    • Enable native TPM support when built with GnuTLS.
    • Enable PKCS#11 token support when built with GnuTLS.
    • Eliminate all SSL library exposure through libopenconnect.
    • Parse split DNS information, provide $CISCO_SPLIT_DNS environment variable to vpnc-script.