diff --git a/Makefile.am b/Makefile.am index 5575909a..cf32ba62 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,7 @@ lib_srcs_oath = oath.c lib_srcs_yubikey = yubikey.c lib_srcs_stoken = stoken.c lib_srcs_esp = esp.c esp-seqno.c +lib_srcs_dtls = dtls.c POTFILES = $(openconnect_SOURCES) $(lib_srcs_cisco) $(lib_srcs_juniper) \ gnutls-esp.c openssl-esp.c esp.c esp-seqno.c \ @@ -54,13 +55,15 @@ endif if OPENCONNECT_GNUTLS library_srcs += $(lib_srcs_gnutls) lib_srcs_esp += gnutls-esp.c +lib_srcs_dtls += gnutls-dtls.c endif if OPENCONNECT_OPENSSL library_srcs += $(lib_srcs_openssl) lib_srcs_esp += openssl-esp.c +lib_srcs_dtls += openssl-dtls.c endif if OPENCONNECT_DTLS -lib_srcs_cisco += dtls.c +lib_srcs_cisco += $(lib_srcs_dtls) endif if OPENCONNECT_ESP lib_srcs_juniper += $(lib_srcs_esp) diff --git a/dtls.c b/dtls.c index fc22cb24..33e9bae6 100644 --- a/dtls.c +++ b/dtls.c @@ -32,10 +32,6 @@ #include "openconnect-internal.h" -#ifdef HAVE_DTLS - -static void detect_mtu(struct openconnect_info *vpninfo); - /* * The master-secret is generated randomly by the client. The server * responds with a DTLS Session-ID. These, done over the HTTPS @@ -66,634 +62,10 @@ static void detect_mtu(struct openconnect_info *vpninfo); #define DTLS_SEND SSL_write #define DTLS_RECV SSL_read #define DTLS_FREE SSL_free - -/* In the very early days there were cases where this wasn't found in - * the header files but it did still work somehow. I forget the details - * now but I was definitely avoiding using the macro. Let's just define - * it for ourselves instead.*/ -#ifndef DTLS1_BAD_VER -#define DTLS1_BAD_VER 0x100 -#endif - -#ifdef HAVE_DTLS1_STOP_TIMER -/* OpenSSL doesn't deliberately export this, but we need it to - workaround a DTLS bug in versions < 1.0.0e */ -extern void dtls1_stop_timer(SSL *); -#endif - -/* sets the DTLS MTU and returns the actual tunnel MTU */ -static unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu) -{ -#ifdef DTLS_set_link_mtu - DTLS_set_link_mtu(vpninfo->dtls_ssl, mtu); -#else - /* not sure if this is equivalent */ - SSL_set_mtu(vpninfo->dtls_ssl, LINK_TO_TUNNEL_MTU(mtu)); -#endif - return LINK_TO_TUNNEL_MTU(mtu); -} - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) -/* Since OpenSSL 1.1, the SSL_SESSION structure is opaque and we can't - * just fill it in directly. So we have to generate the OpenSSL ASN.1 - * representation of the SSL_SESSION, and use d2i_SSL_SESSION() to - * create the SSL_SESSION from that. */ - -static void buf_append_INTEGER(struct oc_text_buf *buf, uint32_t datum) -{ - int l; - - /* We only handle positive integers up to INT_MAX */ - if (datum < 0x80) - l = 1; - else if (datum < 0x8000) - l = 2; - else if (datum < 0x800000) - l = 3; - else - l = 4; - - if (buf_ensure_space(buf, 2 + l)) - return; - - buf->data[buf->pos++] = 0x02; - buf->data[buf->pos++] = l; - while (l--) - buf->data[buf->pos++] = datum >> (l * 8); -} - -static void buf_append_OCTET_STRING(struct oc_text_buf *buf, void *data, int len) -{ - /* We only (need to) cope with length < 0x80 for now */ - if (len >= 0x80) { - buf->error = -EINVAL; - return; - } - - if (buf_ensure_space(buf, 2 + len)) - return; - - buf->data[buf->pos++] = 0x04; - buf->data[buf->pos++] = len; - memcpy(buf->data + buf->pos, data, len); - buf->pos += len; -} - -static SSL_SESSION *generate_dtls_session(struct openconnect_info *vpninfo, - int dtlsver, const SSL_CIPHER *cipher) -{ - struct oc_text_buf *buf = buf_alloc(); - SSL_SESSION *dtls_session; - const unsigned char *asn; - uint16_t cid; - - buf_append_bytes(buf, "\x30\x80", 2); // SEQUENCE, indeterminate length - buf_append_INTEGER(buf, 1 /* SSL_SESSION_ASN1_VERSION */); - buf_append_INTEGER(buf, dtlsver); - store_be16(&cid, SSL_CIPHER_get_id(cipher) & 0xffff); - buf_append_OCTET_STRING(buf, &cid, 2); - buf_append_OCTET_STRING(buf, vpninfo->dtls_session_id, - sizeof(vpninfo->dtls_session_id)); - buf_append_OCTET_STRING(buf, vpninfo->dtls_secret, - sizeof(vpninfo->dtls_secret)); - /* If the length actually fits in one byte (which it should), do - * it that way. Else, leave it indeterminate and add two - * end-of-contents octets to mark the end of the SEQUENCE. */ - if (!buf_error(buf) && buf->pos <= 0x80) - buf->data[1] = buf->pos - 2; - else - buf_append_bytes(buf, "\0\0", 2); - - if (buf_error(buf)) { - vpn_progress(vpninfo, PRG_ERR, - _("Failed to create SSL_SESSION ASN.1 for OpenSSL: %s\n"), - strerror(buf_error(buf))); - buf_free(buf); - return NULL; - } - - asn = (void *)buf->data; - dtls_session = d2i_SSL_SESSION(NULL, &asn, buf->pos); - buf_free(buf); - if (!dtls_session) { - vpn_progress(vpninfo, PRG_ERR, - _("OpenSSL failed to parse SSL_SESSION ASN.1\n")); - openconnect_report_ssl_errors(vpninfo); - return NULL; - } - - return dtls_session; -} -#else /* OpenSSL before 1.1 */ -static SSL_SESSION *generate_dtls_session(struct openconnect_info *vpninfo, - int dtlsver, const SSL_CIPHER *cipher) -{ - SSL_SESSION *dtls_session = SSL_SESSION_new(); - if (!dtls_session) { - vpn_progress(vpninfo, PRG_ERR, - _("Initialise DTLSv1 session failed\n")); - return NULL; - } - - dtls_session->ssl_version = dtlsver; - dtls_session->master_key_length = sizeof(vpninfo->dtls_secret); - memcpy(dtls_session->master_key, vpninfo->dtls_secret, - sizeof(vpninfo->dtls_secret)); - - dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id); - memcpy(dtls_session->session_id, vpninfo->dtls_session_id, - sizeof(vpninfo->dtls_session_id)); - - dtls_session->cipher = (SSL_CIPHER *)cipher; - dtls_session->cipher_id = cipher->id; - - return dtls_session; -} -#endif - -static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) -{ - STACK_OF(SSL_CIPHER) *ciphers; - method_const SSL_METHOD *dtls_method; - SSL_SESSION *dtls_session; - SSL *dtls_ssl; - BIO *dtls_bio; - int dtlsver = DTLS1_BAD_VER; - const char *cipher = vpninfo->dtls_cipher; - -#ifdef HAVE_DTLS12 - if (!strcmp(cipher, "OC-DTLS1_2-AES128-GCM")) { - dtlsver = DTLS1_2_VERSION; - cipher = "AES128-GCM-SHA256"; - } else if (!strcmp(cipher, "OC-DTLS1_2-AES256-GCM")) { - dtlsver = DTLS1_2_VERSION; - cipher = "AES256-GCM-SHA384"; - } -#endif - - if (!vpninfo->dtls_ctx) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) -#ifdef HAVE_DTLS12 - if (dtlsver == DTLS1_2_VERSION) - dtls_method = DTLSv1_2_client_method(); - else -#endif - dtls_method = DTLSv1_client_method(); -#else - dtls_method = DTLS_client_method(); -#endif - vpninfo->dtls_ctx = SSL_CTX_new(dtls_method); - if (!vpninfo->dtls_ctx) { - vpn_progress(vpninfo, PRG_ERR, - _("Initialise DTLSv1 CTX failed\n")); - openconnect_report_ssl_errors(vpninfo); - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } -#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - if (dtlsver == DTLS1_BAD_VER) - SSL_CTX_set_options(vpninfo->dtls_ctx, SSL_OP_CISCO_ANYCONNECT); -#else - if (!SSL_CTX_set_min_proto_version(vpninfo->dtls_ctx, dtlsver) || - !SSL_CTX_set_max_proto_version(vpninfo->dtls_ctx, dtlsver)) { - vpn_progress(vpninfo, PRG_ERR, - _("Set DTLS CTX version failed\n")); - openconnect_report_ssl_errors(vpninfo); - SSL_CTX_free(vpninfo->dtls_ctx); - vpninfo->dtls_ctx = NULL; - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } -#endif - - /* If we don't readahead, then we do short reads and throw - away the tail of data packets. */ - SSL_CTX_set_read_ahead(vpninfo->dtls_ctx, 1); - - if (!SSL_CTX_set_cipher_list(vpninfo->dtls_ctx, cipher)) { - vpn_progress(vpninfo, PRG_ERR, - _("Set DTLS cipher list failed\n")); - SSL_CTX_free(vpninfo->dtls_ctx); - vpninfo->dtls_ctx = NULL; - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } - } - - dtls_ssl = SSL_new(vpninfo->dtls_ctx); - SSL_set_connect_state(dtls_ssl); - - ciphers = SSL_get_ciphers(dtls_ssl); - if (sk_SSL_CIPHER_num(ciphers) != 1) { - vpn_progress(vpninfo, PRG_ERR, _("Not precisely one DTLS cipher\n")); - SSL_CTX_free(vpninfo->dtls_ctx); - SSL_free(dtls_ssl); - vpninfo->dtls_ctx = NULL; - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } - - /* We're going to "resume" a session which never existed. Fake it... */ - dtls_session = generate_dtls_session(vpninfo, dtlsver, - sk_SSL_CIPHER_value(ciphers, 0)); - if (!dtls_session) { - SSL_CTX_free(vpninfo->dtls_ctx); - SSL_free(dtls_ssl); - vpninfo->dtls_ctx = NULL; - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } - - /* Add the generated session to the SSL */ - if (!SSL_set_session(dtls_ssl, dtls_session)) { - vpn_progress(vpninfo, PRG_ERR, - _("SSL_set_session() failed with old protocol version 0x%x\n" - "Are you using a version of OpenSSL older than 0.9.8m?\n" - "See http://rt.openssl.org/Ticket/Display.html?id=1751\n" - "Use the --no-dtls command line option to avoid this message\n"), - DTLS1_BAD_VER); - SSL_CTX_free(vpninfo->dtls_ctx); - SSL_free(dtls_ssl); - vpninfo->dtls_ctx = NULL; - vpninfo->dtls_attempt_period = 0; - SSL_SESSION_free(dtls_session); - return -EINVAL; - } - /* We don't need our own refcount on it any more */ - SSL_SESSION_free(dtls_session); - - dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE); - /* Set non-blocking */ - BIO_set_nbio(dtls_bio, 1); - SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio); - - vpninfo->dtls_ssl = dtls_ssl; - - return 0; -} - -static int dtls_try_handshake(struct openconnect_info *vpninfo) -{ - int ret = SSL_do_handshake(vpninfo->dtls_ssl); - - if (ret == 1) { - const char *c; - vpninfo->dtls_state = DTLS_CONNECTED; - vpn_progress(vpninfo, PRG_INFO, - _("Established DTLS connection (using OpenSSL). Ciphersuite %s.\n"), - vpninfo->dtls_cipher); - - c = openconnect_get_dtls_compression(vpninfo); - if (c) { - vpn_progress(vpninfo, PRG_INFO, - _("DTLS connection compression using %s.\n"), c); - } - - vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = - vpninfo->dtls_times.last_tx = time(NULL); - - /* From about 8.4.1(11) onwards, the ASA seems to get - very unhappy if we resend ChangeCipherSpec messages - after the initial setup. This was "fixed" in OpenSSL - 1.0.0e for RT#2505, but it's not clear if that was - the right fix. What happens if the original packet - *does* get lost? Surely we *wanted* the retransmits, - because without them the server will never be able - to decrypt anything we send? - Oh well, our retransmitted packets upset the server - because we don't get the Cisco-compatibility right - (this is one of the areas in which Cisco's DTLS differs - from the RFC4347 spec), and DPD should help us notice - if *nothing* is getting through. */ -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - /* OpenSSL 1.1.0 or above. Do nothing. The SSLeay() function - got renamed, and it's a pointless check in this case - anyway because there's *no* chance that we linked against - 1.1.0 and are running against something older than 1.0.0e. */ -#elif OPENSSL_VERSION_NUMBER >= 0x1000005fL - /* OpenSSL 1.0.0e or above doesn't resend anyway; do nothing. - However, if we were *built* against 1.0.0e or newer, but at - runtime we find that we are being run against an older - version, warn about it. */ - if (SSLeay() < 0x1000005fL) { - vpn_progress(vpninfo, PRG_ERR, - _("Your OpenSSL is older than the one you built against, so DTLS may fail!")); - } -#elif defined(HAVE_DTLS1_STOP_TIMER) - /* - * This works for any normal OpenSSL that supports - * Cisco DTLS compatibility (0.9.8m to 1.0.0d inclusive, - * and even later versions although it isn't needed there. - */ - dtls1_stop_timer(vpninfo->dtls_ssl); -#elif defined(BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT) - /* - * Debian restricts visibility of dtls1_stop_timer() - * so do it manually. This version also works on all - * sane versions of OpenSSL: - */ - memset(&(vpninfo->dtls_ssl->d1->next_timeout), 0, - sizeof((vpninfo->dtls_ssl->d1->next_timeout))); - vpninfo->dtls_ssl->d1->timeout_duration = 1; - BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl), - BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0, - &(vpninfo->dtls_ssl->d1->next_timeout)); -#elif defined(BIO_CTRL_DGRAM_SET_TIMEOUT) - /* - * OK, here it gets more fun... this shoul handle the case - * of older OpenSSL which has the Cisco DTLS compatibility - * backported, but *not* the fix for RT#1922. - */ - BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl), - BIO_CTRL_DGRAM_SET_TIMEOUT, 0, NULL); -#else - /* - * And if they don't have any of the above, they probably - * don't have RT#1829 fixed either, but that's OK because - * that's the "fix" that *introduces* the timeout we're - * trying to disable. So do nothing... - */ -#endif - detect_mtu(vpninfo); - return 0; - } - - ret = SSL_get_error(vpninfo->dtls_ssl, ret); - if (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ) { - static int badossl_bitched = 0; - if (time(NULL) < vpninfo->new_dtls_started + 12) - return 0; - if (((OPENSSL_VERSION_NUMBER >= 0x100000b0L && OPENSSL_VERSION_NUMBER <= 0x100000c0L) || \ - (OPENSSL_VERSION_NUMBER >= 0x10001040L && OPENSSL_VERSION_NUMBER <= 0x10001060L) || \ - OPENSSL_VERSION_NUMBER == 0x10002000L) && !badossl_bitched) { - badossl_bitched = 1; - vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake timed out\n")); - vpn_progress(vpninfo, PRG_ERR, _("This is probably because your OpenSSL is broken\n" - "See http://rt.openssl.org/Ticket/Display.html?id=2984\n")); - } else { - vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); - } - } - - vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %d\n"), ret); - openconnect_report_ssl_errors(vpninfo); - - dtls_close(vpninfo); - - vpninfo->dtls_state = DTLS_SLEEPING; - time(&vpninfo->new_dtls_started); - return -EINVAL; -} - -void dtls_shutdown(struct openconnect_info *vpninfo) -{ - dtls_close(vpninfo); - SSL_CTX_free(vpninfo->dtls_ctx); -} - -void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf) -{ -#ifdef HAVE_DTLS12 - buf_append(buf, "OC-DTLS1_2-AES256-GCM:OC-DTLS1_2-AES128-GCM:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA"); -#else - buf_append(buf, "AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA"); -#endif -} - #elif defined(OPENCONNECT_GNUTLS) -#include -#include "gnutls.h" - -#if GNUTLS_VERSION_NUMBER < 0x030200 -# define GNUTLS_DTLS1_2 202 -#endif -#if GNUTLS_VERSION_NUMBER < 0x030400 -# define GNUTLS_CIPHER_CHACHA20_POLY1305 23 -#endif - -/* sets the DTLS MTU and returns the actual tunnel MTU */ -static unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu) -{ - gnutls_dtls_set_mtu(vpninfo->dtls_ssl, mtu); - return gnutls_dtls_get_data_mtu(vpninfo->dtls_ssl); -} - -struct { - const char *name; - gnutls_protocol_t version; - gnutls_cipher_algorithm_t cipher; - gnutls_kx_algorithm_t kx; - gnutls_mac_algorithm_t mac; - const char *prio; - const char *min_gnutls_version; -} gnutls_dtls_ciphers[] = { - { "AES128-SHA", GNUTLS_DTLS0_9, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_KX_RSA, GNUTLS_MAC_SHA1, - "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT", "3.0.0" }, - { "AES256-SHA", GNUTLS_DTLS0_9, GNUTLS_CIPHER_AES_256_CBC, GNUTLS_KX_RSA, GNUTLS_MAC_SHA1, - "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-256-CBC:+SHA1:+RSA:%COMPAT", "3.0.0" }, - { "DES-CBC3-SHA", GNUTLS_DTLS0_9, GNUTLS_CIPHER_3DES_CBC, GNUTLS_KX_RSA, GNUTLS_MAC_SHA1, - "NONE:+VERS-DTLS0.9:+COMP-NULL:+3DES-CBC:+SHA1:+RSA:%COMPAT", "3.0.0" }, - { "OC-DTLS1_2-AES128-GCM", GNUTLS_DTLS1_2, GNUTLS_CIPHER_AES_128_GCM, GNUTLS_KX_RSA, GNUTLS_MAC_AEAD, - "NONE:+VERS-DTLS1.2:+COMP-NULL:+AES-128-GCM:+AEAD:+RSA:%COMPAT:+SIGN-ALL", "3.2.7" }, - { "OC-DTLS1_2-AES256-GCM", GNUTLS_DTLS1_2, GNUTLS_CIPHER_AES_256_GCM, GNUTLS_KX_RSA, GNUTLS_MAC_AEAD, - "NONE:+VERS-DTLS1.2:+COMP-NULL:+AES-256-GCM:+AEAD:+RSA:%COMPAT:+SIGN-ALL", "3.2.7" }, - { "OC2-DTLS1_2-CHACHA20-POLY1305", GNUTLS_DTLS1_2, GNUTLS_CIPHER_CHACHA20_POLY1305, GNUTLS_KX_PSK, GNUTLS_MAC_AEAD, - "NONE:+VERS-DTLS1.2:+COMP-NULL:+CHACHA20-POLY1305:+AEAD:+PSK:%COMPAT:+SIGN-ALL", "3.4.8" }, - /* NB. We agreed that any new cipher suites probably shouldn't use - * Cisco's session resume hack (which ties us to a specific version - * of DTLS). Instead, we'll use GNUTLS_KX_PSK and let it negotiate - * the session properly. We might want to wait for - * draft-jay-tls-psk-identity-extension before we do that. */ -}; - -#if GNUTLS_VERSION_NUMBER < 0x030009 -void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf) -{ - int i, first = 1; - - for (i = 0; i < sizeof(gnutls_dtls_ciphers) / sizeof(gnutls_dtls_ciphers[0]); i++) { - if (gnutls_check_version(gnutls_dtls_ciphers[i].min_gnutls_version)) { - buf_append(buf, "%s%s", first ? "" : ":", - gnutls_dtls_ciphers[i].name); - first = 0; - } - } -#else -void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf) -{ - /* only enable the ciphers that would have been negotiated in the TLS channel */ - unsigned i, j, first = 1; - int ret; - unsigned idx; - gnutls_cipher_algorithm_t cipher; - gnutls_mac_algorithm_t mac; - gnutls_priority_t cache; - uint32_t used = 0; - - ret = gnutls_priority_init(&cache, vpninfo->gnutls_prio, NULL); - if (ret < 0) { - buf->error = -EIO; - return; - } - - for (j=0; ; j++) { - ret = gnutls_priority_get_cipher_suite_index(cache, j, &idx); - if (ret == GNUTLS_E_UNKNOWN_CIPHER_SUITE) - continue; - else if (ret < 0) - break; - - if (gnutls_cipher_suite_info(idx, NULL, NULL, &cipher, &mac, NULL) != NULL) { - for (i = 0; i < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); i++) { - if (used & (1 << i)) - continue; - if (gnutls_dtls_ciphers[i].mac == mac && gnutls_dtls_ciphers[i].cipher == cipher) { - buf_append(buf, "%s%s", first ? "" : ":", - gnutls_dtls_ciphers[i].name); - first = 0; - used |= (1 << i); - break; - } - } - } - } - - gnutls_priority_deinit(cache); -} -#endif - #define DTLS_SEND gnutls_record_send #define DTLS_RECV gnutls_record_recv #define DTLS_FREE gnutls_deinit -static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) -{ - gnutls_session_t dtls_ssl; - gnutls_datum_t master_secret, session_id; - int err; - int cipher; - - for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) { - if (gnutls_check_version(gnutls_dtls_ciphers[cipher].min_gnutls_version) == NULL) - continue; - if (!strcmp(vpninfo->dtls_cipher, gnutls_dtls_ciphers[cipher].name)) - goto found_cipher; - } - vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"), - vpninfo->dtls_cipher); - vpninfo->dtls_attempt_period = 0; - - return -EINVAL; - - found_cipher: - gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK); - err = gnutls_priority_set_direct(dtls_ssl, - gnutls_dtls_ciphers[cipher].prio, - NULL); - if (err) { - vpn_progress(vpninfo, PRG_ERR, - _("Failed to set DTLS priority: %s\n"), - gnutls_strerror(err)); - gnutls_deinit(dtls_ssl); - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } - - gnutls_transport_set_ptr(dtls_ssl, - (gnutls_transport_ptr_t)(intptr_t)dtls_fd); - - gnutls_record_disable_padding(dtls_ssl); - master_secret.data = vpninfo->dtls_secret; - master_secret.size = sizeof(vpninfo->dtls_secret); - session_id.data = vpninfo->dtls_session_id; - session_id.size = sizeof(vpninfo->dtls_session_id); - err = gnutls_session_set_premaster(dtls_ssl, GNUTLS_CLIENT, gnutls_dtls_ciphers[cipher].version, - gnutls_dtls_ciphers[cipher].kx, gnutls_dtls_ciphers[cipher].cipher, - gnutls_dtls_ciphers[cipher].mac, GNUTLS_COMP_NULL, - &master_secret, &session_id); - if (err) { - vpn_progress(vpninfo, PRG_ERR, - _("Failed to set DTLS session parameters: %s\n"), - gnutls_strerror(err)); - gnutls_deinit(dtls_ssl); - vpninfo->dtls_attempt_period = 0; - return -EINVAL; - } - - vpninfo->dtls_ssl = dtls_ssl; - return 0; -} - -static int dtls_try_handshake(struct openconnect_info *vpninfo) -{ - int err = gnutls_handshake(vpninfo->dtls_ssl); - char *str; - - if (!err) { -#ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU - /* Make sure GnuTLS's idea of the MTU is sufficient to take - a full VPN MTU (with 1-byte header) in a data record. */ - err = gnutls_dtls_set_data_mtu(vpninfo->dtls_ssl, vpninfo->ip_info.mtu + 1); - if (err) { - vpn_progress(vpninfo, PRG_ERR, - _("Failed to set DTLS MTU: %s\n"), - gnutls_strerror(err)); - goto error; - } -#else - /* If we don't have gnutls_dtls_set_data_mtu() then make sure - we leave enough headroom by adding the worst-case overhead. - We only support AES128-CBC and DES-CBC3-SHA anyway, so - working out the worst case isn't hard. */ - gnutls_dtls_set_mtu(vpninfo->dtls_ssl, - vpninfo->ip_info.mtu + DTLS_OVERHEAD); -#endif - - vpninfo->dtls_state = DTLS_CONNECTED; - str = get_gnutls_cipher(vpninfo->dtls_ssl); - if (str) { - const char *c; - vpn_progress(vpninfo, PRG_INFO, - _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"), - str); - gnutls_free(str); - c = openconnect_get_dtls_compression(vpninfo); - if (c) { - vpn_progress(vpninfo, PRG_INFO, - _("DTLS connection compression using %s.\n"), c); - } - } - - vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = - vpninfo->dtls_times.last_tx = time(NULL); - - detect_mtu(vpninfo); - /* XXX: For OpenSSL we explicitly prevent retransmits here. */ - return 0; - } - - if (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) { - if (time(NULL) < vpninfo->new_dtls_started + 12) - return 0; - vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); - } - - vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"), - gnutls_strerror(err)); - if (err == GNUTLS_E_PUSH_ERROR) - vpn_progress(vpninfo, PRG_ERR, - _("(Is a firewall preventing you from sending UDP packets?)\n")); - error: - dtls_close(vpninfo); - - vpninfo->dtls_state = DTLS_SLEEPING; - time(&vpninfo->new_dtls_started); - return -EINVAL; -} - -void dtls_shutdown(struct openconnect_info *vpninfo) -{ - dtls_close(vpninfo); -} #endif static int connect_dtls_socket(struct openconnect_info *vpninfo) @@ -1299,7 +671,7 @@ static int detect_mtu_ipv6(struct openconnect_info *vpninfo, unsigned char *buf) } #endif -static void detect_mtu(struct openconnect_info *vpninfo) +void dtls_detect_mtu(struct openconnect_info *vpninfo) { int mtu = vpninfo->ip_info.mtu; int prev_mtu = vpninfo->ip_info.mtu; @@ -1340,6 +712,3 @@ static void detect_mtu(struct openconnect_info *vpninfo) free(buf); } -#else /* !HAVE_DTLS */ -#warning Your SSL library does not seem to support Cisco DTLS compatibility -#endif diff --git a/gnutls-dtls.c b/gnutls-dtls.c new file mode 100644 index 00000000..2bdf8b25 --- /dev/null +++ b/gnutls-dtls.c @@ -0,0 +1,265 @@ + +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2016 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 +#ifndef _WIN32 +#include +#include +#endif + + +#include +#include "gnutls.h" + +#if GNUTLS_VERSION_NUMBER < 0x030200 +# define GNUTLS_DTLS1_2 202 +#endif +#if GNUTLS_VERSION_NUMBER < 0x030400 +# define GNUTLS_CIPHER_CHACHA20_POLY1305 23 +#endif + +/* sets the DTLS MTU and returns the actual tunnel MTU */ +unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu) +{ + gnutls_dtls_set_mtu(vpninfo->dtls_ssl, mtu); + return gnutls_dtls_get_data_mtu(vpninfo->dtls_ssl); +} + +struct { + const char *name; + gnutls_protocol_t version; + gnutls_cipher_algorithm_t cipher; + gnutls_kx_algorithm_t kx; + gnutls_mac_algorithm_t mac; + const char *prio; + const char *min_gnutls_version; +} gnutls_dtls_ciphers[] = { + { "AES128-SHA", GNUTLS_DTLS0_9, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_KX_RSA, GNUTLS_MAC_SHA1, + "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT", "3.0.0" }, + { "AES256-SHA", GNUTLS_DTLS0_9, GNUTLS_CIPHER_AES_256_CBC, GNUTLS_KX_RSA, GNUTLS_MAC_SHA1, + "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-256-CBC:+SHA1:+RSA:%COMPAT", "3.0.0" }, + { "DES-CBC3-SHA", GNUTLS_DTLS0_9, GNUTLS_CIPHER_3DES_CBC, GNUTLS_KX_RSA, GNUTLS_MAC_SHA1, + "NONE:+VERS-DTLS0.9:+COMP-NULL:+3DES-CBC:+SHA1:+RSA:%COMPAT", "3.0.0" }, + { "OC-DTLS1_2-AES128-GCM", GNUTLS_DTLS1_2, GNUTLS_CIPHER_AES_128_GCM, GNUTLS_KX_RSA, GNUTLS_MAC_AEAD, + "NONE:+VERS-DTLS1.2:+COMP-NULL:+AES-128-GCM:+AEAD:+RSA:%COMPAT:+SIGN-ALL", "3.2.7" }, + { "OC-DTLS1_2-AES256-GCM", GNUTLS_DTLS1_2, GNUTLS_CIPHER_AES_256_GCM, GNUTLS_KX_RSA, GNUTLS_MAC_AEAD, + "NONE:+VERS-DTLS1.2:+COMP-NULL:+AES-256-GCM:+AEAD:+RSA:%COMPAT:+SIGN-ALL", "3.2.7" }, + { "OC2-DTLS1_2-CHACHA20-POLY1305", GNUTLS_DTLS1_2, GNUTLS_CIPHER_CHACHA20_POLY1305, GNUTLS_KX_PSK, GNUTLS_MAC_AEAD, + "NONE:+VERS-DTLS1.2:+COMP-NULL:+CHACHA20-POLY1305:+AEAD:+PSK:%COMPAT:+SIGN-ALL", "3.4.8" }, + /* NB. We agreed that any new cipher suites probably shouldn't use + * Cisco's session resume hack (which ties us to a specific version + * of DTLS). Instead, we'll use GNUTLS_KX_PSK and let it negotiate + * the session properly. We might want to wait for + * draft-jay-tls-psk-identity-extension before we do that. */ +}; + +#if GNUTLS_VERSION_NUMBER < 0x030009 +void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf) +{ + int i, first = 1; + + for (i = 0; i < sizeof(gnutls_dtls_ciphers) / sizeof(gnutls_dtls_ciphers[0]); i++) { + if (gnutls_check_version(gnutls_dtls_ciphers[i].min_gnutls_version)) { + buf_append(buf, "%s%s", first ? "" : ":", + gnutls_dtls_ciphers[i].name); + first = 0; + } + } +#else +void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf) +{ + /* only enable the ciphers that would have been negotiated in the TLS channel */ + unsigned i, j, first = 1; + int ret; + unsigned idx; + gnutls_cipher_algorithm_t cipher; + gnutls_mac_algorithm_t mac; + gnutls_priority_t cache; + uint32_t used = 0; + + ret = gnutls_priority_init(&cache, vpninfo->gnutls_prio, NULL); + if (ret < 0) { + buf->error = -EIO; + return; + } + + for (j=0; ; j++) { + ret = gnutls_priority_get_cipher_suite_index(cache, j, &idx); + if (ret == GNUTLS_E_UNKNOWN_CIPHER_SUITE) + continue; + else if (ret < 0) + break; + + if (gnutls_cipher_suite_info(idx, NULL, NULL, &cipher, &mac, NULL) != NULL) { + for (i = 0; i < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); i++) { + if (used & (1 << i)) + continue; + if (gnutls_dtls_ciphers[i].mac == mac && gnutls_dtls_ciphers[i].cipher == cipher) { + buf_append(buf, "%s%s", first ? "" : ":", + gnutls_dtls_ciphers[i].name); + first = 0; + used |= (1 << i); + break; + } + } + } + } + + gnutls_priority_deinit(cache); +} +#endif + +int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) +{ + gnutls_session_t dtls_ssl; + gnutls_datum_t master_secret, session_id; + int err; + int cipher; + + for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) { + if (gnutls_check_version(gnutls_dtls_ciphers[cipher].min_gnutls_version) == NULL) + continue; + if (!strcmp(vpninfo->dtls_cipher, gnutls_dtls_ciphers[cipher].name)) + goto found_cipher; + } + vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"), + vpninfo->dtls_cipher); + vpninfo->dtls_attempt_period = 0; + + return -EINVAL; + + found_cipher: + gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK); + err = gnutls_priority_set_direct(dtls_ssl, + gnutls_dtls_ciphers[cipher].prio, + NULL); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to set DTLS priority: %s\n"), + gnutls_strerror(err)); + gnutls_deinit(dtls_ssl); + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + + gnutls_transport_set_ptr(dtls_ssl, + (gnutls_transport_ptr_t)(intptr_t)dtls_fd); + + gnutls_record_disable_padding(dtls_ssl); + master_secret.data = vpninfo->dtls_secret; + master_secret.size = sizeof(vpninfo->dtls_secret); + session_id.data = vpninfo->dtls_session_id; + session_id.size = sizeof(vpninfo->dtls_session_id); + err = gnutls_session_set_premaster(dtls_ssl, GNUTLS_CLIENT, gnutls_dtls_ciphers[cipher].version, + gnutls_dtls_ciphers[cipher].kx, gnutls_dtls_ciphers[cipher].cipher, + gnutls_dtls_ciphers[cipher].mac, GNUTLS_COMP_NULL, + &master_secret, &session_id); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to set DTLS session parameters: %s\n"), + gnutls_strerror(err)); + gnutls_deinit(dtls_ssl); + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + + vpninfo->dtls_ssl = dtls_ssl; + return 0; +} + +int dtls_try_handshake(struct openconnect_info *vpninfo) +{ + int err = gnutls_handshake(vpninfo->dtls_ssl); + char *str; + + if (!err) { +#ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU + /* Make sure GnuTLS's idea of the MTU is sufficient to take + a full VPN MTU (with 1-byte header) in a data record. */ + err = gnutls_dtls_set_data_mtu(vpninfo->dtls_ssl, vpninfo->ip_info.mtu + 1); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to set DTLS MTU: %s\n"), + gnutls_strerror(err)); + goto error; + } +#else + /* If we don't have gnutls_dtls_set_data_mtu() then make sure + we leave enough headroom by adding the worst-case overhead. + We only support AES128-CBC and DES-CBC3-SHA anyway, so + working out the worst case isn't hard. */ + gnutls_dtls_set_mtu(vpninfo->dtls_ssl, + vpninfo->ip_info.mtu + DTLS_OVERHEAD); +#endif + + vpninfo->dtls_state = DTLS_CONNECTED; + str = get_gnutls_cipher(vpninfo->dtls_ssl); + if (str) { + const char *c; + vpn_progress(vpninfo, PRG_INFO, + _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"), + str); + gnutls_free(str); + c = openconnect_get_dtls_compression(vpninfo); + if (c) { + vpn_progress(vpninfo, PRG_INFO, + _("DTLS connection compression using %s.\n"), c); + } + } + + vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = + vpninfo->dtls_times.last_tx = time(NULL); + + dtls_detect_mtu(vpninfo); + /* XXX: For OpenSSL we explicitly prevent retransmits here. */ + return 0; + } + + if (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) { + if (time(NULL) < vpninfo->new_dtls_started + 12) + return 0; + vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); + } + + vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"), + gnutls_strerror(err)); + if (err == GNUTLS_E_PUSH_ERROR) + vpn_progress(vpninfo, PRG_ERR, + _("(Is a firewall preventing you from sending UDP packets?)\n")); + error: + dtls_close(vpninfo); + + vpninfo->dtls_state = DTLS_SLEEPING; + time(&vpninfo->new_dtls_started); + return -EINVAL; +} + +void dtls_shutdown(struct openconnect_info *vpninfo) +{ + dtls_close(vpninfo); +} + diff --git a/openconnect-internal.h b/openconnect-internal.h index 9e4546ba..df90e637 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -796,13 +796,18 @@ int os_read_tun(struct openconnect_info *vpninfo, struct pkt *pkt); int os_write_tun(struct openconnect_info *vpninfo, struct pkt *pkt); intptr_t os_setup_tun(struct openconnect_info *vpninfo); +/* {gnutls,openssl}-dtls.c */ +int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd); +int dtls_try_handshake(struct openconnect_info *vpninfo); +unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu); + /* dtls.c */ int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period); int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout); void dtls_close(struct openconnect_info *vpninfo); void dtls_shutdown(struct openconnect_info *vpninfo); void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf); - +void dtls_detect_mtu(struct openconnect_info *vpninfo); int openconnect_dtls_read(struct openconnect_info *vpninfo, void *buf, size_t len, unsigned ms); int openconnect_dtls_write(struct openconnect_info *vpninfo, void *buf, size_t len); diff --git a/openssl-dtls.c b/openssl-dtls.c new file mode 100644 index 00000000..a0a09038 --- /dev/null +++ b/openssl-dtls.c @@ -0,0 +1,427 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2016 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 +#ifndef _WIN32 +#include +#include +#endif + +#include "openconnect-internal.h" + +/* In the very early days there were cases where this wasn't found in + * the header files but it did still work somehow. I forget the details + * now but I was definitely avoiding using the macro. Let's just define + * it for ourselves instead.*/ +#ifndef DTLS1_BAD_VER +#define DTLS1_BAD_VER 0x100 +#endif + +#ifdef HAVE_DTLS1_STOP_TIMER +/* OpenSSL doesn't deliberately export this, but we need it to + workaround a DTLS bug in versions < 1.0.0e */ +extern void dtls1_stop_timer(SSL *); +#endif + +/* sets the DTLS MTU and returns the actual tunnel MTU */ +unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu) +{ +#ifdef DTLS_set_link_mtu + DTLS_set_link_mtu(vpninfo->dtls_ssl, mtu); +#else + /* not sure if this is equivalent */ + SSL_set_mtu(vpninfo->dtls_ssl, LINK_TO_TUNNEL_MTU(mtu)); +#endif + return LINK_TO_TUNNEL_MTU(mtu); +} + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +/* Since OpenSSL 1.1, the SSL_SESSION structure is opaque and we can't + * just fill it in directly. So we have to generate the OpenSSL ASN.1 + * representation of the SSL_SESSION, and use d2i_SSL_SESSION() to + * create the SSL_SESSION from that. */ + +static void buf_append_INTEGER(struct oc_text_buf *buf, uint32_t datum) +{ + int l; + + /* We only handle positive integers up to INT_MAX */ + if (datum < 0x80) + l = 1; + else if (datum < 0x8000) + l = 2; + else if (datum < 0x800000) + l = 3; + else + l = 4; + + if (buf_ensure_space(buf, 2 + l)) + return; + + buf->data[buf->pos++] = 0x02; + buf->data[buf->pos++] = l; + while (l--) + buf->data[buf->pos++] = datum >> (l * 8); +} + +static void buf_append_OCTET_STRING(struct oc_text_buf *buf, void *data, int len) +{ + /* We only (need to) cope with length < 0x80 for now */ + if (len >= 0x80) { + buf->error = -EINVAL; + return; + } + + if (buf_ensure_space(buf, 2 + len)) + return; + + buf->data[buf->pos++] = 0x04; + buf->data[buf->pos++] = len; + memcpy(buf->data + buf->pos, data, len); + buf->pos += len; +} + +static SSL_SESSION *generate_dtls_session(struct openconnect_info *vpninfo, + int dtlsver, const SSL_CIPHER *cipher) +{ + struct oc_text_buf *buf = buf_alloc(); + SSL_SESSION *dtls_session; + const unsigned char *asn; + uint16_t cid; + + buf_append_bytes(buf, "\x30\x80", 2); // SEQUENCE, indeterminate length + buf_append_INTEGER(buf, 1 /* SSL_SESSION_ASN1_VERSION */); + buf_append_INTEGER(buf, dtlsver); + store_be16(&cid, SSL_CIPHER_get_id(cipher) & 0xffff); + buf_append_OCTET_STRING(buf, &cid, 2); + buf_append_OCTET_STRING(buf, vpninfo->dtls_session_id, + sizeof(vpninfo->dtls_session_id)); + buf_append_OCTET_STRING(buf, vpninfo->dtls_secret, + sizeof(vpninfo->dtls_secret)); + /* If the length actually fits in one byte (which it should), do + * it that way. Else, leave it indeterminate and add two + * end-of-contents octets to mark the end of the SEQUENCE. */ + if (!buf_error(buf) && buf->pos <= 0x80) + buf->data[1] = buf->pos - 2; + else + buf_append_bytes(buf, "\0\0", 2); + + if (buf_error(buf)) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to create SSL_SESSION ASN.1 for OpenSSL: %s\n"), + strerror(buf_error(buf))); + buf_free(buf); + return NULL; + } + + asn = (void *)buf->data; + dtls_session = d2i_SSL_SESSION(NULL, &asn, buf->pos); + buf_free(buf); + if (!dtls_session) { + vpn_progress(vpninfo, PRG_ERR, + _("OpenSSL failed to parse SSL_SESSION ASN.1\n")); + openconnect_report_ssl_errors(vpninfo); + return NULL; + } + + return dtls_session; +} +#else /* OpenSSL before 1.1 */ +static SSL_SESSION *generate_dtls_session(struct openconnect_info *vpninfo, + int dtlsver, const SSL_CIPHER *cipher) +{ + SSL_SESSION *dtls_session = SSL_SESSION_new(); + if (!dtls_session) { + vpn_progress(vpninfo, PRG_ERR, + _("Initialise DTLSv1 session failed\n")); + return NULL; + } + + dtls_session->ssl_version = dtlsver; + dtls_session->master_key_length = sizeof(vpninfo->dtls_secret); + memcpy(dtls_session->master_key, vpninfo->dtls_secret, + sizeof(vpninfo->dtls_secret)); + + dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id); + memcpy(dtls_session->session_id, vpninfo->dtls_session_id, + sizeof(vpninfo->dtls_session_id)); + + dtls_session->cipher = (SSL_CIPHER *)cipher; + dtls_session->cipher_id = cipher->id; + + return dtls_session; +} +#endif + +int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) +{ + STACK_OF(SSL_CIPHER) *ciphers; + method_const SSL_METHOD *dtls_method; + SSL_SESSION *dtls_session; + SSL *dtls_ssl; + BIO *dtls_bio; + int dtlsver = DTLS1_BAD_VER; + const char *cipher = vpninfo->dtls_cipher; + +#ifdef HAVE_DTLS12 + if (!strcmp(cipher, "OC-DTLS1_2-AES128-GCM")) { + dtlsver = DTLS1_2_VERSION; + cipher = "AES128-GCM-SHA256"; + } else if (!strcmp(cipher, "OC-DTLS1_2-AES256-GCM")) { + dtlsver = DTLS1_2_VERSION; + cipher = "AES256-GCM-SHA384"; + } +#endif + + if (!vpninfo->dtls_ctx) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +#ifdef HAVE_DTLS12 + if (dtlsver == DTLS1_2_VERSION) + dtls_method = DTLSv1_2_client_method(); + else +#endif + dtls_method = DTLSv1_client_method(); +#else + dtls_method = DTLS_client_method(); +#endif + vpninfo->dtls_ctx = SSL_CTX_new(dtls_method); + if (!vpninfo->dtls_ctx) { + vpn_progress(vpninfo, PRG_ERR, + _("Initialise DTLSv1 CTX failed\n")); + openconnect_report_ssl_errors(vpninfo); + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + if (dtlsver == DTLS1_BAD_VER) + SSL_CTX_set_options(vpninfo->dtls_ctx, SSL_OP_CISCO_ANYCONNECT); +#else + if (!SSL_CTX_set_min_proto_version(vpninfo->dtls_ctx, dtlsver) || + !SSL_CTX_set_max_proto_version(vpninfo->dtls_ctx, dtlsver)) { + vpn_progress(vpninfo, PRG_ERR, + _("Set DTLS CTX version failed\n")); + openconnect_report_ssl_errors(vpninfo); + SSL_CTX_free(vpninfo->dtls_ctx); + vpninfo->dtls_ctx = NULL; + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } +#endif + + /* If we don't readahead, then we do short reads and throw + away the tail of data packets. */ + SSL_CTX_set_read_ahead(vpninfo->dtls_ctx, 1); + + if (!SSL_CTX_set_cipher_list(vpninfo->dtls_ctx, cipher)) { + vpn_progress(vpninfo, PRG_ERR, + _("Set DTLS cipher list failed\n")); + SSL_CTX_free(vpninfo->dtls_ctx); + vpninfo->dtls_ctx = NULL; + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + } + + dtls_ssl = SSL_new(vpninfo->dtls_ctx); + SSL_set_connect_state(dtls_ssl); + + ciphers = SSL_get_ciphers(dtls_ssl); + if (sk_SSL_CIPHER_num(ciphers) != 1) { + vpn_progress(vpninfo, PRG_ERR, _("Not precisely one DTLS cipher\n")); + SSL_CTX_free(vpninfo->dtls_ctx); + SSL_free(dtls_ssl); + vpninfo->dtls_ctx = NULL; + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + + /* We're going to "resume" a session which never existed. Fake it... */ + dtls_session = generate_dtls_session(vpninfo, dtlsver, + sk_SSL_CIPHER_value(ciphers, 0)); + if (!dtls_session) { + SSL_CTX_free(vpninfo->dtls_ctx); + SSL_free(dtls_ssl); + vpninfo->dtls_ctx = NULL; + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + + /* Add the generated session to the SSL */ + if (!SSL_set_session(dtls_ssl, dtls_session)) { + vpn_progress(vpninfo, PRG_ERR, + _("SSL_set_session() failed with old protocol version 0x%x\n" + "Are you using a version of OpenSSL older than 0.9.8m?\n" + "See http://rt.openssl.org/Ticket/Display.html?id=1751\n" + "Use the --no-dtls command line option to avoid this message\n"), + DTLS1_BAD_VER); + SSL_CTX_free(vpninfo->dtls_ctx); + SSL_free(dtls_ssl); + vpninfo->dtls_ctx = NULL; + vpninfo->dtls_attempt_period = 0; + SSL_SESSION_free(dtls_session); + return -EINVAL; + } + /* We don't need our own refcount on it any more */ + SSL_SESSION_free(dtls_session); + + dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE); + /* Set non-blocking */ + BIO_set_nbio(dtls_bio, 1); + SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio); + + vpninfo->dtls_ssl = dtls_ssl; + + return 0; +} + +int dtls_try_handshake(struct openconnect_info *vpninfo) +{ + int ret = SSL_do_handshake(vpninfo->dtls_ssl); + + if (ret == 1) { + const char *c; + vpninfo->dtls_state = DTLS_CONNECTED; + vpn_progress(vpninfo, PRG_INFO, + _("Established DTLS connection (using OpenSSL). Ciphersuite %s.\n"), + vpninfo->dtls_cipher); + + c = openconnect_get_dtls_compression(vpninfo); + if (c) { + vpn_progress(vpninfo, PRG_INFO, + _("DTLS connection compression using %s.\n"), c); + } + + vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = + vpninfo->dtls_times.last_tx = time(NULL); + + /* From about 8.4.1(11) onwards, the ASA seems to get + very unhappy if we resend ChangeCipherSpec messages + after the initial setup. This was "fixed" in OpenSSL + 1.0.0e for RT#2505, but it's not clear if that was + the right fix. What happens if the original packet + *does* get lost? Surely we *wanted* the retransmits, + because without them the server will never be able + to decrypt anything we send? + Oh well, our retransmitted packets upset the server + because we don't get the Cisco-compatibility right + (this is one of the areas in which Cisco's DTLS differs + from the RFC4347 spec), and DPD should help us notice + if *nothing* is getting through. */ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* OpenSSL 1.1.0 or above. Do nothing. The SSLeay() function + got renamed, and it's a pointless check in this case + anyway because there's *no* chance that we linked against + 1.1.0 and are running against something older than 1.0.0e. */ +#elif OPENSSL_VERSION_NUMBER >= 0x1000005fL + /* OpenSSL 1.0.0e or above doesn't resend anyway; do nothing. + However, if we were *built* against 1.0.0e or newer, but at + runtime we find that we are being run against an older + version, warn about it. */ + if (SSLeay() < 0x1000005fL) { + vpn_progress(vpninfo, PRG_ERR, + _("Your OpenSSL is older than the one you built against, so DTLS may fail!")); + } +#elif defined(HAVE_DTLS1_STOP_TIMER) + /* + * This works for any normal OpenSSL that supports + * Cisco DTLS compatibility (0.9.8m to 1.0.0d inclusive, + * and even later versions although it isn't needed there. + */ + dtls1_stop_timer(vpninfo->dtls_ssl); +#elif defined(BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT) + /* + * Debian restricts visibility of dtls1_stop_timer() + * so do it manually. This version also works on all + * sane versions of OpenSSL: + */ + memset(&(vpninfo->dtls_ssl->d1->next_timeout), 0, + sizeof((vpninfo->dtls_ssl->d1->next_timeout))); + vpninfo->dtls_ssl->d1->timeout_duration = 1; + BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl), + BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0, + &(vpninfo->dtls_ssl->d1->next_timeout)); +#elif defined(BIO_CTRL_DGRAM_SET_TIMEOUT) + /* + * OK, here it gets more fun... this shoul handle the case + * of older OpenSSL which has the Cisco DTLS compatibility + * backported, but *not* the fix for RT#1922. + */ + BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl), + BIO_CTRL_DGRAM_SET_TIMEOUT, 0, NULL); +#else + /* + * And if they don't have any of the above, they probably + * don't have RT#1829 fixed either, but that's OK because + * that's the "fix" that *introduces* the timeout we're + * trying to disable. So do nothing... + */ +#endif + dtls_detect_mtu(vpninfo); + return 0; + } + + ret = SSL_get_error(vpninfo->dtls_ssl, ret); + if (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ) { + static int badossl_bitched = 0; + if (time(NULL) < vpninfo->new_dtls_started + 12) + return 0; + if (((OPENSSL_VERSION_NUMBER >= 0x100000b0L && OPENSSL_VERSION_NUMBER <= 0x100000c0L) || \ + (OPENSSL_VERSION_NUMBER >= 0x10001040L && OPENSSL_VERSION_NUMBER <= 0x10001060L) || \ + OPENSSL_VERSION_NUMBER == 0x10002000L) && !badossl_bitched) { + badossl_bitched = 1; + vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake timed out\n")); + vpn_progress(vpninfo, PRG_ERR, _("This is probably because your OpenSSL is broken\n" + "See http://rt.openssl.org/Ticket/Display.html?id=2984\n")); + } else { + vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); + } + } + + vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %d\n"), ret); + openconnect_report_ssl_errors(vpninfo); + + dtls_close(vpninfo); + + vpninfo->dtls_state = DTLS_SLEEPING; + time(&vpninfo->new_dtls_started); + return -EINVAL; +} + +void dtls_shutdown(struct openconnect_info *vpninfo) +{ + dtls_close(vpninfo); + SSL_CTX_free(vpninfo->dtls_ctx); +} + +void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf) +{ +#ifdef HAVE_DTLS12 + buf_append(buf, "OC-DTLS1_2-AES256-GCM:OC-DTLS1_2-AES128-GCM:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA"); +#else + buf_append(buf, "AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA"); +#endif +} +