Add DTLS support for GnuTLS
authorDavid Woodhouse <David.Woodhouse@intel.com>
Thu, 7 Jun 2012 13:50:10 +0000 (14:50 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Thu, 7 Jun 2012 13:52:10 +0000 (14:52 +0100)
This requires the patches I just sent to Nikos...

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
configure.ac
dtls.c
mainloop.c
openconnect-internal.h
www/changelog.xml

index ec3101b..ee05215 100644 (file)
@@ -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 (file)
--- 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
index 279ff6c..0886e05 100644 (file)
@@ -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);
 
index 2245e4a..e8d22f2 100644 (file)
@@ -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 */
index e4a5cbf..ca14375 100644 (file)
@@ -17,7 +17,7 @@
 <ul>
    <li><b>OpenConnect HEAD</b>
      <ul>
-       <li>Allow building against GnuTLS <i>(experimental, no DTLS support yet).</i></li>
+       <li>Allow building against GnuTLS, including DTLS support.</li>
        <li>Add <tt>--with-pkgconfigdir=</tt> option to <tt>configure</tt> for FreeBSD's benefit <i>(<a href="https://bugs.freedesktop.org/show_bug.cgi?id=48743">fd#48743</a>)</i>.</li>
      </ul><br/>
   </li>