From 845e629fae7852ad57693b54a7a06bcc6d52cfd0 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 7 Jun 2012 14:50:10 +0100 Subject: [PATCH] Add DTLS support for GnuTLS This requires the patches I just sent to Nikos... Signed-off-by: David Woodhouse --- configure.ac | 2 + dtls.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++--- mainloop.c | 2 +- openconnect-internal.h | 13 ++++- www/changelog.xml | 2 +- 5 files changed, 160 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index ec3101b..ee05215 100644 --- a/configure.ac +++ b/configure.ac @@ -202,6 +202,8 @@ elif test "$with_gnutls" = "shibboleet"; then LIBS="$LIBS $GNUTLS_LIBS" AC_CHECK_FUNC(gnutls_pkcs12_simple_parse, [AC_DEFINE(HAVE_GNUTLS_PKCS12_SIMPLE_PARSE, 1)], []) + AC_CHECK_FUNC(gnutls_session_set_master, + [AC_DEFINE(HAVE_GNUTLS_SESSION_SET_MASTER, 1)], []) LIBS="$oldLIBS" elif test "$with_gnutls" != "" && test "$with_gnutls" != "no"; then AC_MSG_ERROR([Values other than 'yes' or 'no' for --with-gnutls are not supported]) diff --git a/dtls.c b/dtls.c index 3d81e17..e29eaae 100644 --- a/dtls.c +++ b/dtls.c @@ -54,7 +54,8 @@ unsigned char unhex(const char *data) return (nybble(data[0]) << 4) | nybble(data[1]); } -#if defined (OPENCONNECT_OPENSSL) && defined (SSL_OP_CISCO_ANYCONNECT) +#ifdef HAVE_DTLS + #if 0 /* * Useful for catching test cases, where we want everything to be @@ -109,6 +110,9 @@ int RAND_bytes(char *buf, int len) * their clients use anyway. */ +#if defined (OPENCONNECT_OPENSSL) +#define DTLS_SEND SSL_write +#define DTLS_RECV SSL_read static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) { STACK_OF(SSL_CIPHER) *ciphers; @@ -324,6 +328,113 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) return -EINVAL; } +#elif defined (OPENCONNECT_GNUTLS) +#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; + + gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK); + err = gnutls_priority_set_direct(dtls_ssl, + "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION", + 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_master(dtls_ssl, GNUTLS_CLIENT, GNUTLS_DTLS0_9, + GNUTLS_KX_RSA, GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_MAC_SHA1, 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) { + vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\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)); + + /* 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; @@ -384,7 +495,11 @@ int connect_dtls_socket(struct openconnect_info *vpninfo) static int dtls_restart(struct openconnect_info *vpninfo) { if (vpninfo->dtls_ssl) { +#if defined (OPENCONNECT_OPENSSL) SSL_free(vpninfo->dtls_ssl); +#elif defined (OPENCONNECT_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); @@ -479,7 +594,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) } 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; @@ -502,7 +617,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) /* 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; @@ -564,7 +679,10 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) 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; @@ -579,7 +697,9 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) 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; @@ -599,6 +719,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) /* One byte of header */ this->hdr[7] = AC_PKT_DATA; +#if defined(OPENCONNECT_OPENSSL) ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1); if (ret <= 0) { ret = SSL_get_error(vpninfo->dtls_ssl, ret); @@ -616,21 +737,35 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) } return 1; } +#elif defined (OPENCONNECT_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 diff --git a/mainloop.c b/mainloop.c index 279ff6c..0886e05 100644 --- a/mainloop.c +++ b/mainloop.c @@ -79,7 +79,7 @@ int vpn_mainloop(struct openconnect_info *vpninfo) struct timeval tv; fd_set rfds, wfds, efds; -#if defined(OPENCONNECT_OPENSSL) && defined(SSL_OP_CISCO_ANYCONNECT) +#ifdef HAVE_DTLS if (vpninfo->new_dtls_ssl) dtls_try_handshake(vpninfo); diff --git a/openconnect-internal.h b/openconnect-internal.h index 2245e4a..e8d22f2 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -182,7 +182,13 @@ struct openconnect_info { SSL *new_dtls_ssl; SSL_SESSION *dtls_session; #elif defined(OPENCONNECT_GNUTLS) - /* FIXME */ + /* Call these *_ssl rather than *_sess because they're just + pointers, and generic code (in mainloop.c for example) + wants to check if they're NULL or not. No point in being + differently named to the OpenSSL variant, and forcing us to + have ifdefs or accessor macros for them. */ + gnutls_session_t dtls_ssl; + gnutls_session_t new_dtls_ssl; #endif struct keepalive_info dtls_times; unsigned char dtls_session_id[32]; @@ -242,6 +248,11 @@ struct openconnect_info { openconnect_progress_vfn progress; }; +#if (defined (OPENCONNECT_OPENSSL) && defined (SSL_OP_CISCO_ANYCONNECT)) || \ + (defined(OPENCONNECT_GNUTLS) && defined (HAVE_GNUTLS_SESSION_SET_MASTER)) +#define HAVE_DTLS 1 +#endif + /* Packet types */ #define AC_PKT_DATA 0 /* Uncompressed data */ diff --git a/www/changelog.xml b/www/changelog.xml index e4a5cbf..ca14375 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -17,7 +17,7 @@