Implement DTLS and CSTP rekeying.
authorDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 11 Aug 2010 23:14:35 +0000 (00:14 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 11 Aug 2010 23:14:35 +0000 (00:14 +0100)
Don't know if there's a way to pass a new DTLS master secret and get
back a new session-id over an existing CSTP connection; reconnecting the
CSTP works though. And is the way to rekey CSTP too, since SSL
renegotiation got deprecated (we never got round to doing it that way
either, anyway).

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
cstp.c
dtls.c
main.c
openconnect.h
openconnect.html

diff --git a/cstp.c b/cstp.c
index 20009d2..5916a58 100644 (file)
--- a/cstp.c
+++ b/cstp.c
@@ -32,6 +32,7 @@
 
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/rand.h>
 
 #include "openconnect.h"
 
@@ -68,7 +69,7 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
 {
        char buf[65536];
        int i;
-       int retried = 0;
+       int retried = 0, sessid_found = 0;
        struct vpn_option **next_dtls_option = &vpninfo->dtls_options;
        struct vpn_option **next_cstp_option = &vpninfo->cstp_options;
        struct vpn_option *old_cstp_opts = vpninfo->cstp_options;
@@ -100,6 +101,15 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                inc = next;
        }
        vpninfo->split_includes = vpninfo->split_excludes = NULL;
+
+       /* Create (new) random master key for DTLS connection, if needed */
+       if (vpninfo->dtls_times.last_rekey + vpninfo->dtls_times.rekey <
+           time(NULL) + 300 &&
+           RAND_bytes(vpninfo->dtls_secret, sizeof(vpninfo->dtls_secret)) != 1) {
+               fprintf(stderr, "Failed to initialise DTLS secret\n");
+               exit(1);
+       }
+
  retry:
        openconnect_SSL_printf(vpninfo->https_ssl, "CONNECT /CSCOSSLC/tunnel HTTP/1.1\r\n");
        openconnect_SSL_printf(vpninfo->https_ssl, "Host: %s\r\n", vpninfo->hostname);
@@ -197,6 +207,19 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                if (!strncmp(buf, "X-DTLS-", 7)) {
                        *next_dtls_option = new_option;
                        next_dtls_option = &new_option->next;
+
+                       if (!strcmp(buf + 7, "Session-ID")) {
+                               if (strlen(colon) != 64) {
+                                       vpninfo->progress(vpninfo, PRG_ERR, "X-DTLS-Session-ID not 64 characters\n");
+                                       vpninfo->progress(vpninfo, PRG_ERR, "Is: %s\n", colon);
+                                       vpninfo->dtls_attempt_period = 0;
+                                       return -EINVAL;
+                               }
+                               for (i = 0; i < 64; i += 2)
+                                       vpninfo->dtls_session_id[i/2] = unhex(colon + i);
+                               sessid_found = 1;
+                               time(&vpninfo->dtls_times.last_rekey);
+                       }
                        continue;
                }
                /* CSTP options... */
@@ -210,6 +233,8 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                        int j = atol(colon);
                        if (j && (!vpninfo->ssl_times.dpd || j < vpninfo->ssl_times.dpd))
                                vpninfo->ssl_times.dpd = j;
+               } else if (!strcmp(buf + 7, "Rekey-Time")) {
+                       vpninfo->ssl_times.rekey = atol(colon);
                } else if (!strcmp(buf + 7, "Content-Encoding")) {
                        if (!strcmp(colon, "deflate"))
                                vpninfo->deflate = 1;
@@ -330,7 +355,11 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
        FD_SET(vpninfo->ssl_fd, &vpninfo->select_rfds);
        FD_SET(vpninfo->ssl_fd, &vpninfo->select_efds);
 
-       vpninfo->ssl_times.last_rx = vpninfo->ssl_times.last_tx = time(NULL);
+       if (!sessid_found)
+               vpninfo->dtls_attempt_period = 0;
+
+       vpninfo->ssl_times.last_rekey = vpninfo->ssl_times.last_rx =
+               vpninfo->ssl_times.last_tx = time(NULL);
        return 0;
 }
 
@@ -368,7 +397,7 @@ int make_cstp_connection(struct openconnect_info *vpninfo)
        return start_cstp_connection(vpninfo);
 }
 
-static int cstp_reconnect(struct openconnect_info *vpninfo)
+int cstp_reconnect(struct openconnect_info *vpninfo)
 {
        int ret;
        int timeout;
@@ -589,9 +618,8 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
        case KA_REKEY:
                /* Not that this will ever happen; we don't even process
                   the setting when we're asked for it. */
-               vpninfo->progress(vpninfo, PRG_ERR, "CSTP rekey due but we don't know how\n");
-               time(&vpninfo->ssl_times.last_rekey);
-               work_done = 1;
+               vpninfo->progress(vpninfo, PRG_INFO, "CSTP rekey due\n");
+               goto do_reconnect;
                break;
 
        case KA_DPD_DEAD:
diff --git a/dtls.c b/dtls.c
index 507949f..2ce2c1c 100644 (file)
--- a/dtls.c
+++ b/dtls.c
@@ -177,15 +177,16 @@ int connect_dtls_socket(struct openconnect_info *vpninfo)
                        return -EINVAL;
                }
                vpninfo->dtls_session->ssl_version = 0x0100; // DTLS1_BAD_VER
+       }
 
-               vpninfo->dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
-               memcpy(vpninfo->dtls_session->master_key, vpninfo->dtls_secret,
-                      sizeof(vpninfo->dtls_secret));
+       /* Do this every time; it may have changed due to a rekey */
+       vpninfo->dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
+       memcpy(vpninfo->dtls_session->master_key, vpninfo->dtls_secret,
+              sizeof(vpninfo->dtls_secret));
 
-               vpninfo->dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
-               memcpy(vpninfo->dtls_session->session_id, vpninfo->dtls_session_id,
-                      sizeof(vpninfo->dtls_session_id));
-       }
+       vpninfo->dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
+       memcpy(vpninfo->dtls_session->session_id, vpninfo->dtls_session_id,
+              sizeof(vpninfo->dtls_session_id));
 
        dtls_ssl = SSL_new(vpninfo->dtls_ctx);
        SSL_set_connect_state(dtls_ssl);
@@ -269,8 +270,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
                vpninfo->new_dtls_ssl = NULL;
                vpninfo->new_dtls_fd = -1;
 
-               vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx =
-                       vpninfo->dtls_times.last_tx = time(NULL);
+               vpninfo->dtls_times.last_rx = vpninfo->dtls_times.last_tx = time(NULL);
 
                return 0;
        }
@@ -329,26 +329,14 @@ static int dtls_restart(struct openconnect_info *vpninfo)
 int setup_dtls(struct openconnect_info *vpninfo)
 {
        struct vpn_option *dtls_opt = vpninfo->dtls_options;
-       int sessid_found = 0;
        int dtls_port = 0;
-       int i;
 
        while (dtls_opt) {
                vpninfo->progress(vpninfo, PRG_TRACE,
                                  "DTLS option %s : %s\n",
                                  dtls_opt->option, dtls_opt->value);
 
-               if (!strcmp(dtls_opt->option, "X-DTLS-Session-ID")) {
-                       if (strlen(dtls_opt->value) != 64) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "X-DTLS-Session-ID not 64 characters\n");
-                               vpninfo->progress(vpninfo, PRG_ERR, "Is: %s\n", dtls_opt->value);
-                               vpninfo->dtls_attempt_period = 0;
-                               return -EINVAL;
-                       }
-                       for (i = 0; i < 64; i += 2)
-                               vpninfo->dtls_session_id[i/2] = unhex(dtls_opt->value + i);
-                       sessid_found = 1;
-               } else if (!strcmp(dtls_opt->option + 7, "Port")) {
+               if (!strcmp(dtls_opt->option + 7, "Port")) {
                        dtls_port = atol(dtls_opt->value);
                } else if (!strcmp(dtls_opt->option + 7, "Keepalive")) {
                        vpninfo->dtls_times.keepalive = atol(dtls_opt->value);
@@ -364,7 +352,7 @@ int setup_dtls(struct openconnect_info *vpninfo)
 
                dtls_opt = dtls_opt->next;
        }
-       if (!sessid_found || !dtls_port) {
+       if (!dtls_port) {
                vpninfo->dtls_attempt_period = 0;
                return -EINVAL;
        }
@@ -457,9 +445,17 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
        switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
        case KA_REKEY:
-               time(&vpninfo->dtls_times.last_rekey);
-               vpninfo->progress(vpninfo, PRG_TRACE, "DTLS rekey due\n");
-               if (connect_dtls_socket(vpninfo)) {
+               vpninfo->progress(vpninfo, PRG_INFO, "DTLS rekey due\n");
+
+               /* There ought to be a method of rekeying DTLS without tearing down
+                  the CSTP session and restarting, but we don't (yet) know it */
+               if (cstp_reconnect(vpninfo)) {
+                       vpninfo->progress(vpninfo, PRG_ERR, "Reconnect failed\n");
+                       vpninfo->quit_reason = "CSTP reconnect failed";
+                       return 1;
+               }
+
+               if (dtls_restart(vpninfo)) {
                        vpninfo->progress(vpninfo, PRG_ERR, "DTLS rekey failed\n");
                        return 1;
                }
diff --git a/main.c b/main.c
index dec3840..b0651a2 100644 (file)
--- a/main.c
+++ b/main.c
@@ -36,7 +36,6 @@
 #include <sys/syslog.h>
 #include <sys/utsname.h>
 #include <sys/types.h>
-#include <openssl/rand.h>
 #include <openssl/ui.h>
 #ifdef OPENCONNECT_LIBPROXY
 #include LIBPROXY_HDR
@@ -240,10 +239,6 @@ int main(int argc, char **argv)
        vpninfo->uid_csd_given = 0;
        vpninfo->validate_peer_cert = validate_peer_cert;
 
-       if (RAND_bytes(vpninfo->dtls_secret, sizeof(vpninfo->dtls_secret)) != 1) {
-               fprintf(stderr, "Failed to initialise DTLS secret\n");
-               exit(1);
-       }
        if (!uname(&utsbuf))
                vpninfo->localname = utsbuf.nodename;
        else
index 1789847..d899c93 100644 (file)
@@ -299,6 +299,7 @@ int connect_dtls_socket(struct openconnect_info *vpninfo);
 int make_cstp_connection(struct openconnect_info *vpninfo);
 int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout);
 int cstp_bye(struct openconnect_info *vpninfo, char *reason);
+int cstp_reconnect(struct openconnect_info *vpninfo);
 
 /* ssl.c */
 void openconnect_init_openssl(void);
index 90e4435..7a1eac5 100644 (file)
@@ -181,6 +181,7 @@ For full changelog entries including the latest development, see
 <UL>
   <LI><B>OpenConnect HEAD</B><BR>
      <UL>
+       <LI>Implement CSTP and DTLS rekeying <I>(both by reconnecting CSTP)</I>.</LI>
        <LI>Add <TT>--force-dpd</TT> option to set minimum DPD interval.</LI>
        <LI>Don't print <TT>webvpn</TT> cookie in debug output.</LI>
      </UL><BR>
@@ -436,6 +437,6 @@ An <TT>openconnect</TT> <A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/ports/sec
 <hr>
 <address>David Woodhouse &lt;<A HREF="mailto:dwmw2@infradead.org">dwmw2@infradead.org</A>&gt;</address>
 <!-- hhmts start -->
-Last modified: Sat Aug  7 18:50:17 BST 2010
+Last modified: Thu Aug 12 00:13:52 BST 2010
 <!-- hhmts end -->
 </body> </html>