diff --git a/Makefile.am b/Makefile.am index c2d34520..9b7f9384 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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) diff --git a/gnutls.c b/gnutls.c index be40ddab..d8a2d510 100644 --- 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 - 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_st* st) { + 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) { diff --git a/gnutls.h b/gnutls.h index ab34dc5e..68d59d2e 100644 --- a/gnutls.h +++ b/gnutls.h @@ -27,6 +27,9 @@ #include #include +#include + +#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 index 00000000..bc62d769 --- /dev/null +++ b/gnutls_tpm.c @@ -0,0 +1,340 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2012 Intel Corporation. + * + * Author: David Woodhouse + * + * 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 and Tobias Soder + */ + +#include +#include + +#include +#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 */