/*
* OpenConnect (SSL + DTLS) VPN client
*
- * Copyright © 2008-2010 Intel Corporation.
+ * Copyright © 2008-2012 Intel Corporation.
*
* Author: David Woodhouse <dwmw2@infradead.org>
*
#include <netdb.h>
#include <unistd.h>
#include <netinet/in.h>
-#include <openssl/err.h>
-#include <openssl/ssl.h>
#include <fcntl.h>
#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
#include "openconnect-internal.h"
-#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
-
static unsigned char nybble(unsigned char n)
{
if (n >= '0' && n <= '9') return n - '0';
return (nybble(data[0]) << 4) | nybble(data[1]);
}
-#ifdef SSL_OP_CISCO_ANYCONNECT
+#ifdef HAVE_DTLS
+
#if 0
/*
* Useful for catching test cases, where we want everything to be
* their clients use anyway.
*/
-int connect_dtls_socket(struct openconnect_info *vpninfo)
+#if defined (DTLS_OPENSSL)
+#define DTLS_SEND SSL_write
+#define DTLS_RECV SSL_read
+
+#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
+
+static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
{
STACK_OF(SSL_CIPHER) *ciphers;
method_const SSL_METHOD *dtls_method;
SSL_CIPHER *dtls_cipher;
SSL *dtls_ssl;
BIO *dtls_bio;
- int dtls_fd;
-
- if (!vpninfo->dtls_addr) {
- vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
- vpninfo->dtls_attempt_period = 0;
- return -EINVAL;
- }
-
- if (!vpninfo->dtls_cipher) {
- /* We probably didn't offer it any ciphers it liked */
- vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
- vpninfo->dtls_attempt_period = 0;
- return -EINVAL;
- }
-
- if (vpninfo->proxy) {
- /* XXX: Theoretically, SOCKS5 proxies can do UDP too */
- vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
- vpninfo->dtls_attempt_period = 0;
- return -EINVAL;
- }
-
- dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
- if (dtls_fd < 0) {
- perror(_("Open UDP socket for DTLS:"));
- return -EINVAL;
- }
-
- if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) {
- perror(_("UDP (DTLS) connect:\n"));
- close(dtls_fd);
- return -EINVAL;
- }
-
- fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
if (!vpninfo->dtls_ctx) {
dtls_method = DTLSv1_client_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;
}
return -EINVAL;
}
- /* Go Go Go! */
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);
SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
- /* Set non-blocking */
- BIO_set_nbio(SSL_get_rbio(dtls_ssl), 1);
- BIO_set_nbio(SSL_get_wbio(dtls_ssl), 1);
-
- fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);
-
- vpninfo->new_dtls_fd = dtls_fd;
vpninfo->new_dtls_ssl = dtls_ssl;
- if (vpninfo->select_nfds <= dtls_fd)
- vpninfo->select_nfds = dtls_fd + 1;
-
- FD_SET(dtls_fd, &vpninfo->select_rfds);
- FD_SET(dtls_fd, &vpninfo->select_efds);
-
- time(&vpninfo->new_dtls_started);
- return dtls_try_handshake(vpninfo);
+ return 0;
}
int dtls_try_handshake(struct openconnect_info *vpninfo)
int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);
if (ret == 1) {
- vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n"));
+ vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n"));
if (vpninfo->dtls_ssl) {
/* We are replacing an old connection */
_("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);
-#else
- /* Debian restricts visibility of dtls1_stop_timer()
- so do it manually. Thankfully this *should* work,
- from 0.9.8m to 1.0.0d inclusive, and we don't have
- to worry about future changes because we don't do
- this for 1.0.0e and above anyway */
+#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
return 0;
}
}
vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %d\n"), ret);
- report_ssl_errors(vpninfo);
+ openconnect_report_ssl_errors(vpninfo);
/* Kill the new (failed) connection... */
SSL_free(vpninfo->new_dtls_ssl);
return -EINVAL;
}
+#elif defined (DTLS_GNUTLS)
+#include <gnutls/dtls.h>
+
+struct {
+ const char *name;
+ gnutls_cipher_algorithm_t cipher;
+ gnutls_mac_algorithm_t mac;
+ const char *prio;
+} gnutls_dtls_ciphers[] = {
+ { "AES128-SHA", GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1,
+ "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
+ { "DES-CBC3-SHA", GNUTLS_CIPHER_3DES_CBC, GNUTLS_MAC_SHA1,
+ "NONE:+VERS-DTLS0.9:+COMP-NULL:+3DES-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
+};
+
+#define DTLS_SEND gnutls_record_send
+#define DTLS_RECV gnutls_record_recv
+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 (!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)(long) 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_DTLS0_9,
+ GNUTLS_KX_RSA, 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->new_dtls_ssl = dtls_ssl;
+ return 0;
+}
+
+int dtls_try_handshake(struct openconnect_info *vpninfo)
+{
+ int err = gnutls_handshake(vpninfo->new_dtls_ssl);
+
+ 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->new_dtls_ssl, vpninfo->actual_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->new_dtls_ssl,
+ vpninfo->actual_mtu + 1 /* packet + header */
+ + 13 /* DTLS header */
+ + 20 /* biggest supported MAC (SHA1) */
+ + 16 /* biggest supported IV (AES-128) */
+ + 16 /* max padding */);
+#endif
+
+ vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n"));
+
+ if (vpninfo->dtls_ssl) {
+ /* We are replacing an old connection */
+ gnutls_deinit(vpninfo->dtls_ssl);
+ close(vpninfo->dtls_fd);
+ FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
+ FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
+ FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
+ }
+ vpninfo->dtls_ssl = vpninfo->new_dtls_ssl;
+ vpninfo->dtls_fd = vpninfo->new_dtls_fd;
+
+ vpninfo->new_dtls_ssl = NULL;
+ vpninfo->new_dtls_fd = -1;
+
+ vpninfo->dtls_times.last_rx = vpninfo->dtls_times.last_tx = time(NULL);
+
+ /* XXX: For OpenSSL we explicitly prevent retransmits here. */
+ return 0;
+ }
+
+ if (err == GNUTLS_E_AGAIN) {
+ if (time(NULL) < vpninfo->new_dtls_started + 5)
+ return 0;
+ vpn_progress(vpninfo, PRG_TRACE, _("DTLS handshake timed out\n"));
+ }
+
+ vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"),
+ gnutls_strerror(err));
+
+ goto error;
+ error:
+ /* Kill the new (failed) connection... */
+ gnutls_deinit(vpninfo->new_dtls_ssl);
+ FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_rfds);
+ FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_efds);
+ close(vpninfo->new_dtls_fd);
+ vpninfo->new_dtls_ssl = NULL;
+ vpninfo->new_dtls_fd = -1;
+
+ /* ... and kill the old one too. The only time there'll be a valid
+ existing session is when it was a rekey, and in that case it's
+ time for the old one to die. */
+ if (vpninfo->dtls_ssl) {
+ gnutls_deinit(vpninfo->dtls_ssl);
+ close(vpninfo->dtls_fd);
+ FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
+ FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
+ FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
+ vpninfo->dtls_ssl = NULL;
+ vpninfo->dtls_fd = -1;
+ }
+
+ time(&vpninfo->new_dtls_started);
+ return -EINVAL;
+}
+#endif
+
+int connect_dtls_socket(struct openconnect_info *vpninfo)
+{
+ int dtls_fd, ret;
+
+ if (!vpninfo->dtls_addr) {
+ vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
+ vpninfo->dtls_attempt_period = 0;
+ return -EINVAL;
+ }
+
+ if (!vpninfo->dtls_cipher) {
+ /* We probably didn't offer it any ciphers it liked */
+ vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
+ vpninfo->dtls_attempt_period = 0;
+ return -EINVAL;
+ }
+
+ if (vpninfo->proxy) {
+ /* XXX: Theoretically, SOCKS5 proxies can do UDP too */
+ vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
+ vpninfo->dtls_attempt_period = 0;
+ return -EINVAL;
+ }
+
+ dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (dtls_fd < 0) {
+ perror(_("Open UDP socket for DTLS:"));
+ return -EINVAL;
+ }
+
+ if (vpninfo->dtls_local_port) {
+ union {
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ } dtls_bind_addr;
+ int dtls_bind_addrlen;
+ memset(&dtls_bind_addr, 0, sizeof(dtls_bind_addr));
+
+ if (vpninfo->peer_addr->sa_family == AF_INET) {
+ struct sockaddr_in *addr = &dtls_bind_addr.in;
+ dtls_bind_addrlen = sizeof(*addr);
+ addr->sin_family = AF_INET;
+ addr->sin_addr.s_addr = INADDR_ANY;
+ addr->sin_port = htons(vpninfo->dtls_local_port);
+ } else if (vpninfo->peer_addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *addr = &dtls_bind_addr.in6;
+ dtls_bind_addrlen = sizeof(*addr);
+ addr->sin6_family = AF_INET6;
+ addr->sin6_addr = in6addr_any;
+ addr->sin6_port = htons(vpninfo->dtls_local_port);
+ } else {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Unknown protocol family %d. Cannot do DTLS\n"),
+ vpninfo->peer_addr->sa_family);
+ vpninfo->dtls_attempt_period = 0;
+ close(dtls_fd);
+ return -EINVAL;
+ }
+
+ if (bind(dtls_fd, (struct sockaddr *)&dtls_bind_addr, dtls_bind_addrlen)) {
+ perror(_("Bind UDP socket for DTLS"));
+ close(dtls_fd);
+ return -EINVAL;
+ }
+ }
+
+ if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) {
+ perror(_("UDP (DTLS) connect:\n"));
+ close(dtls_fd);
+ return -EINVAL;
+ }
+
+ fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
+ fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);
+
+ ret = start_dtls_handshake(vpninfo, dtls_fd);
+ if (ret) {
+ close(dtls_fd);
+ return ret;
+ }
+
+ vpninfo->new_dtls_fd = dtls_fd;
+ if (vpninfo->select_nfds <= dtls_fd)
+ vpninfo->select_nfds = dtls_fd + 1;
+
+ FD_SET(dtls_fd, &vpninfo->select_rfds);
+ FD_SET(dtls_fd, &vpninfo->select_efds);
+
+ time(&vpninfo->new_dtls_started);
+
+ return dtls_try_handshake(vpninfo);
+}
+
static int dtls_restart(struct openconnect_info *vpninfo)
{
if (vpninfo->dtls_ssl) {
+#if defined (DTLS_OPENSSL)
SSL_free(vpninfo->dtls_ssl);
+#elif defined (DTLS_GNUTLS)
+ gnutls_deinit(vpninfo->dtls_ssl);
+#endif
close(vpninfo->dtls_fd);
FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
struct vpn_option *dtls_opt = vpninfo->dtls_options;
int dtls_port = 0;
+#if defined (OPENCONNECT_GNUTLS) && defined (DTLS_OPENSSL)
+ /* If we're using GnuTLS for authentication but OpenSSL for DTLS,
+ we'll need to initialise OpenSSL now... */
+ SSL_library_init ();
+ ERR_clear_error ();
+ SSL_load_error_strings ();
+ OpenSSL_add_all_algorithms ();
+#endif
+
while (dtls_opt) {
vpn_progress(vpninfo, PRG_TRACE,
_("DTLS option %s : %s\n"),
char magic_pkt;
while (1) {
- int len = vpninfo->mtu;
+ int len = vpninfo->actual_mtu;
unsigned char *buf;
if (!dtls_pkt) {
}
buf = dtls_pkt->data - 1;
- len = SSL_read(vpninfo->dtls_ssl, buf, len + 1);
+ len = DTLS_RECV(vpninfo->dtls_ssl, buf, len + 1);
if (len <= 0)
break;
/* FIXME: What if the packet doesn't get through? */
magic_pkt = AC_PKT_DPD_RESP;
- if (SSL_write(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+ if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
vpn_progress(vpninfo, PRG_ERR,
_("Failed to send DPD response. Expect disconnect\n"));
continue;
vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS DPD\n"));
magic_pkt = AC_PKT_DPD_OUT;
- SSL_write(vpninfo->dtls_ssl, &magic_pkt, 1);
+ if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to send DPD request. Expect disconnect\n"));
+
/* last_dpd will just have been set */
vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
work_done = 1;
vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS Keepalive\n"));
magic_pkt = AC_PKT_KEEPALIVE;
- SSL_write(vpninfo->dtls_ssl, &magic_pkt, 1);
+ if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to send keepalive request. Expect disconnect\n"));
time(&vpninfo->dtls_times.last_tx);
work_done = 1;
break;
/* One byte of header */
this->hdr[7] = AC_PKT_DATA;
+#if defined(DTLS_OPENSSL)
ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
if (ret <= 0) {
ret = SSL_get_error(vpninfo->dtls_ssl, ret);
vpn_progress(vpninfo, PRG_ERR,
_("DTLS got write error %d. Falling back to SSL\n"),
ret);
- report_ssl_errors(vpninfo);
+ openconnect_report_ssl_errors(vpninfo);
dtls_restart(vpninfo);
vpninfo->outgoing_queue = this;
vpninfo->outgoing_qlen++;
}
return 1;
}
+#elif defined (DTLS_GNUTLS)
+ ret = gnutls_record_send(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
+ if (ret <= 0) {
+ if (ret != GNUTLS_E_AGAIN) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("DTLS got write error: %s. Falling back to SSL\n"),
+ gnutls_strerror(ret));
+ dtls_restart(vpninfo);
+ vpninfo->outgoing_queue = this;
+ vpninfo->outgoing_qlen++;
+ }
+ return 1;
+ }
+#endif
time(&vpninfo->dtls_times.last_tx);
vpn_progress(vpninfo, PRG_TRACE,
- _("Sent DTLS packet of %d bytes; SSL_write() returned %d\n"),
+ _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
this->len, ret);
free(this);
}
return work_done;
}
-#else /* No DTLS support in OpenSSL */
-#warning Your version of OpenSSL does not seem to support Cisco DTLS compatibility
+#else /* !HAVE_DTLS */
+#warning Your SSL library does not seem to support Cisco DTLS compatibility
int setup_dtls(struct openconnect_info *vpninfo)
{
vpn_progress(vpninfo, PRG_ERR,
- _("Built against OpenSSL with no Cisco DTLS support\n"));
+ _("Built against SSL library with no Cisco DTLS support\n"));
return -EINVAL;
}
#endif