diff --git a/Makefile.am b/Makefile.am index 85512715..e5788739 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,7 +25,7 @@ openconnect_LDADD = libopenconnect.la $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(INTL_LI library_srcs = ssl.c http.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c lib_srcs_cisco = auth.c cstp.c dtls.c -lib_srcs_juniper = oncp.c +lib_srcs_juniper = oncp.c gnutls-esp.c lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c lib_srcs_openssl = openssl.c openssl-pkcs11.c lib_srcs_win32 = tun-win32.c sspi.c diff --git a/gnutls-esp.c b/gnutls-esp.c new file mode 100644 index 00000000..b450bb2d --- /dev/null +++ b/gnutls-esp.c @@ -0,0 +1,133 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2015 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. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "openconnect-internal.h" + +void destroy_esp_ciphers(struct esp *esp) +{ + if (esp->cipher) { + gnutls_cipher_deinit(esp->cipher); + esp->cipher = NULL; + } + if (esp->hmac) { + gnutls_hmac_deinit(esp->hmac, NULL); + esp->hmac = NULL; + } +} + +static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, + gnutls_mac_algorithm_t macalg, gnutls_cipher_algorithm_t encalg) +{ + gnutls_datum_t enc_key; + int err; + + /* ∄ gnutls_cipher_get_key_size() */ + if (encalg == GNUTLS_CIPHER_AES_128_CBC) + enc_key.size = 16; + else if (encalg == GNUTLS_CIPHER_AES_256_CBC) + enc_key.size = 32; + else + return -EINVAL; + + enc_key.data = esp->secrets; + + err = gnutls_cipher_init(&esp->cipher, encalg, &enc_key, NULL); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to initialise ESP cipher: %s\n"), + gnutls_strerror(err)); + return -EIO; + } + + err = gnutls_hmac_init(&esp->hmac, macalg, + esp->secrets + enc_key.size, + gnutls_hmac_get_len(macalg)); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to initialize ESP HMAC: %s\n"), + gnutls_strerror(err)); + destroy_esp_ciphers(esp); + } + return 0; +} + +int setup_esp_keys(struct openconnect_info *vpninfo) +{ + gnutls_mac_algorithm_t macalg; + gnutls_cipher_algorithm_t encalg; + int ret; + + if (vpninfo->dtls_state == DTLS_DISABLED) + return -EOPNOTSUPP; + + switch (vpninfo->esp_enc) { + case 0x02: + encalg = GNUTLS_CIPHER_AES_128_CBC; + break; + case 0x05: + encalg = GNUTLS_CIPHER_AES_256_CBC; + break; + default: + return -EINVAL; + } + + switch (vpninfo->esp_hmac) { + case 0x01: + macalg = GNUTLS_MAC_MD5; + break; + case 0x02: + macalg = GNUTLS_MAC_SHA1; + break; + default: + return -EINVAL; + } + + ret = gnutls_rnd(GNUTLS_RND_NONCE, &vpninfo->esp_in.secrets, + sizeof(vpninfo->esp_in.secrets)); + if (ret) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to generate random keys for ESP: %s\n"), + gnutls_strerror(ret)); + return -EIO; + } + + ret = init_esp_ciphers(vpninfo, &vpninfo->esp_out, macalg, encalg); + if (ret) + return ret; + + ret = init_esp_ciphers(vpninfo, &vpninfo->esp_in, macalg, encalg); + if (ret) { + destroy_esp_ciphers(&vpninfo->esp_out); + return ret; + } + + vpninfo->dtls_state = DTLS_SECRET; + return 0; +} diff --git a/library.c b/library.c index 83720fcd..34695ba6 100644 --- a/library.c +++ b/library.c @@ -255,6 +255,8 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) #ifdef DTLS_GNUTLS gnutls_free(vpninfo->gnutls_dtls_cipher); #endif + destroy_esp_ciphers(&vpninfo->esp_in); + destroy_esp_ciphers(&vpninfo->esp_out); free(vpninfo->dtls_addr); if (vpninfo->csd_scriptname) { diff --git a/oncp.c b/oncp.c index 6aa0c8df..9d7d691f 100644 --- a/oncp.c +++ b/oncp.c @@ -767,6 +767,7 @@ static int process_attr(struct openconnect_info *vpninfo, int group, int attr, mactype = "unknown"; vpn_progress(vpninfo, PRG_DEBUG, _("ESP HMAC: 0x%02x (%s)\n"), data[0], mactype); + vpninfo->esp_hmac = data[0]; break; } @@ -783,45 +784,52 @@ static int process_attr(struct openconnect_info *vpninfo, int group, int attr, enctype = "unknown"; vpn_progress(vpninfo, PRG_DEBUG, _("ESP encryption: 0x%02x (%s)\n"), data[0], enctype); + vpninfo->esp_enc = data[0]; break; } case GRP_ATTR(8, 3): if (attrlen != 1) goto badlen; + vpninfo->esp_compr = data[0]; vpn_progress(vpninfo, PRG_DEBUG, _("ESP compression: %d\n"), data[0]); break; case GRP_ATTR(8, 4): if (attrlen != 2) goto badlen; - vpn_progress(vpninfo, PRG_DEBUG, _("ESP port: %d\n"), TLV_BE16(data)); + vpninfo->esp_port = TLV_BE16(data); + vpn_progress(vpninfo, PRG_DEBUG, _("ESP port: %d\n"), vpninfo->esp_port); break; case GRP_ATTR(8, 5): if (attrlen != 4) goto badlen; - vpn_progress(vpninfo, PRG_DEBUG, _("ESP key lifetime: %d bytes\n"), - TLV_BE32(data)); + vpninfo->esp_lifetime_bytes = TLV_BE32(data); + vpn_progress(vpninfo, PRG_DEBUG, _("ESP key lifetime: %u bytes\n"), + vpninfo->esp_lifetime_bytes); break; case GRP_ATTR(8, 6): if (attrlen != 4) goto badlen; - vpn_progress(vpninfo, PRG_DEBUG, _("ESP key lifetime: %d seconds\n"), - TLV_BE32(data)); + vpninfo->esp_lifetime_seconds = TLV_BE32(data); + vpn_progress(vpninfo, PRG_DEBUG, _("ESP key lifetime: %u seconds\n"), + vpninfo->esp_lifetime_seconds); break; case GRP_ATTR(8, 9): if (attrlen != 4) goto badlen; - vpn_progress(vpninfo, PRG_DEBUG, _("ESP to SSL fallback: %d seconds\n"), - TLV_BE32(data)); + vpninfo->esp_ssl_fallback = TLV_BE32(data); + vpn_progress(vpninfo, PRG_DEBUG, _("ESP to SSL fallback: %u seconds\n"), + vpninfo->esp_ssl_fallback); break; case GRP_ATTR(8, 10): if (attrlen != 4) goto badlen; + vpninfo->esp_replay_protect = TLV_BE32(data); vpn_progress(vpninfo, PRG_DEBUG, _("ESP replay protection: %d\n"), TLV_BE32(data)); break; @@ -829,11 +837,15 @@ static int process_attr(struct openconnect_info *vpninfo, int group, int attr, case GRP_ATTR(7, 1): if (attrlen != 4) goto badlen; + memcpy(vpninfo->esp_out.spi, data, 4); vpn_progress(vpninfo, PRG_DEBUG, _("ESP SPI (outbound): %d\n"), TLV_BE32(data)); break; case GRP_ATTR(7, 2): + if (attrlen != 0x40) + goto badlen; + memcpy(vpninfo->esp_out.secrets, data, 0x40); vpn_progress(vpninfo, PRG_DEBUG, _("%d bytes of ESP secrets\n"), attrlen); break; @@ -880,6 +892,21 @@ static const unsigned char kmp_tail_out[] = { 0x01, 0x00, 0x00, 0x00, 0x01, static const unsigned char data_hdr[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char esp_kmp_hdr[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2e, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, /* KMP header */ + 0x00, 0x56, /* KMP length */ + 0x00, 0x07, 0x00, 0x00, 0x00, 0x50, /* TLV group 7 */ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, /* Attr 1 (SPI) */ +}; +/* Followed by 4 bytes of SPI */ +static const unsigned char esp_kmp_part2[] = { + 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, /* Attr 2 (secrets) */ +}; +/* And now 0x40 bytes of random secret for encryption and HMAC key */ + + int oncp_connect(struct openconnect_info *vpninfo) { int ret, ofs, kmp, kmpend, kmplen, attr, attrlen, group, grouplen, groupend; @@ -1094,6 +1121,7 @@ int oncp_connect(struct openconnect_info *vpninfo) group = reqbuf->pos; buf_append_tlv_be32(reqbuf, 2, vpninfo->ip_info.mtu); if (buf_error(reqbuf)) { + enomem: vpn_progress(vpninfo, PRG_ERR, _("Error creating oNCP negotiation request\n")); ret = buf_error(reqbuf); @@ -1101,6 +1129,19 @@ int oncp_connect(struct openconnect_info *vpninfo) } put_len32(reqbuf, group); put_len16(reqbuf, kmp); + + if (!setup_esp_keys(vpninfo)) { + /* Since we'll want to do this in the oncp_mainloop too, where it's easier + * *not* to have an oc_text_buf and build it up manually, and since it's + * all fixed size and fairly simple anyway, just hard-code the packet */ + buf_append_bytes(reqbuf, esp_kmp_hdr, sizeof(esp_kmp_hdr)); + buf_append_bytes(reqbuf, vpninfo->esp_in.spi, sizeof(vpninfo->esp_in.spi)); + buf_append_bytes(reqbuf, esp_kmp_part2, sizeof(esp_kmp_part2)); + buf_append_bytes(reqbuf, vpninfo->esp_in.secrets, sizeof(vpninfo->esp_in.secrets)); + if (buf_error(reqbuf)) + goto enomem; + } + /* Length at the start of the packet is little-endian */ reqbuf->data[0] = (reqbuf->pos - 2); reqbuf->data[1] = (reqbuf->pos - 2) >> 8; diff --git a/openconnect-internal.h b/openconnect-internal.h index 507a539c..530b5b0e 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -56,6 +56,7 @@ #include #include #include +#include #ifdef HAVE_TROUSERS #include #include @@ -236,6 +237,17 @@ struct vpn_proto { void (*udp_shutdown)(struct openconnect_info *vpninfo); }; +struct esp { +#ifdef OPENCONNECT_GNUTLS + gnutls_cipher_hd_t cipher; + gnutls_hmac_hd_t hmac; +#else +#error No OpenSSL support for ESP yet +#endif + unsigned char spi[4]; + unsigned char secrets[0x40]; +}; + struct openconnect_info { struct vpn_proto proto; @@ -246,6 +258,17 @@ struct openconnect_info { char *redirect_url; int redirect_type; + unsigned char esp_hmac; + unsigned char esp_enc; + unsigned char esp_compr; + uint32_t esp_replay_protect; + uint32_t esp_lifetime_bytes; + uint32_t esp_lifetime_seconds; + uint32_t esp_ssl_fallback; + struct esp esp_in; + struct esp esp_out; + int esp_port; + int tncc_fd; /* For Juniper TNCC */ const char *csd_xmltag; int csd_nostub; @@ -725,6 +748,10 @@ void openconnect_clear_cookies(struct openconnect_info *vpninfo); int load_pkcs11_key(struct openconnect_info *vpninfo); int load_pkcs11_certificate(struct openconnect_info *vpninfo); +/* gnutls-esp.c */ +int setup_esp_keys(struct openconnect_info *vpninfo); +void destroy_esp_ciphers(struct esp *esp); + /* {gnutls,openssl}.c */ int ssl_nonblock_read(struct openconnect_info *vpninfo, void *buf, int maxlen); int ssl_nonblock_write(struct openconnect_info *vpninfo, void *buf, int buflen);