Add support for floating in tls mode using the HMAC of a packet. It costs
a roundtrip through the clients. Its security comes from a secret key, both
peers have. This key and the data form the signature used, which is then
checked againts existing peer connections. Therefore a good auth algo is
recommended.

URL: https://community.openvpn.net/openvpn/ticket/49
Signed-off-by: André Valentin <avalen...@marcant.net>
---
 src/openvpn/crypto.c  |   55 ++++++++++++++++++++++++
 src/openvpn/crypto.h  |    4 ++
 src/openvpn/mudp.c    |  112 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/openvpn/mudp.h    |   18 ++++++++
 src/openvpn/options.c |    3 ++
 src/openvpn/perf.h    |    2 +
 src/openvpn/ssl.c     |   28 +++++++++++++
 src/openvpn/ssl.h     |    6 +++
 8 files changed, 228 insertions(+)

diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index c4c356d..891488d 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -389,6 +389,61 @@ openvpn_decrypt (struct buffer *buf, struct buffer work,
 }
 
 /*
+ * This verifies if a packet and its HMAC fit to a crypto context.
+ *
+ * On success true is returned.
+ */
+bool
+crypto_test_hmac (struct buffer *buf,
+          const struct crypto_options *opt)
+{
+  struct gc_arena gc;
+  gc_init (&gc);
+  int offset = 1;
+
+  if (buf->len > 0 && opt->key_ctx_bi)
+    {
+      struct key_ctx *ctx = &opt->key_ctx_bi->decrypt;
+
+      /* Verify the HMAC */
+      if (ctx->hmac)
+        {
+          int hmac_len;
+          uint8_t local_hmac[MAX_HMAC_KEY_LENGTH]; /* HMAC of ciphertext 
computed locally */
+
+          hmac_ctx_reset(ctx->hmac);
+
+          /* Assume the length of the input HMAC */
+          hmac_len = hmac_ctx_size (ctx->hmac);
+
+          /* Authentication fails if insufficient data in packet for HMAC */
+          if ((buf->len - offset) < hmac_len) 
+            {
+              gc_free (&gc);
+              return false;
+            }
+
+          hmac_ctx_update (ctx->hmac, BPTR (buf) + offset + hmac_len, BLEN 
(buf) - offset - hmac_len);
+          hmac_ctx_final (ctx->hmac, local_hmac);
+
+          /* Compare locally computed HMAC with packet HMAC */
+          if (memcmp_constant_time (local_hmac, BPTR (buf) + offset, hmac_len))
+            {
+              gc_free (&gc);
+              return false;
+            }
+            
+          gc_free (&gc);
+          return true;
+        }
+    }
+
+  gc_free (&gc);
+  return false;
+}
+
+
+/*
  * How many bytes will we add to frame buffer for a given
  * set of crypto options?
  */
diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h
index 3b4b88e..296519a 100644
--- a/src/openvpn/crypto.h
+++ b/src/openvpn/crypto.h
@@ -279,6 +279,10 @@ bool openvpn_decrypt (struct buffer *buf, struct buffer 
work,
                      const struct crypto_options *opt,
                      const struct frame* frame);
 
+
+bool crypto_test_hmac (struct buffer *buf,
+           const struct crypto_options *opt);
+           
 /** @} name Functions for performing security operations on data channel 
packets */
 
 void crypto_adjust_frame_parameters(struct frame *frame,
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 3468dab..9fb4fb5 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -63,6 +63,12 @@ multi_get_create_instance_udp (struct multi_context *m)
        {
          mi = (struct multi_instance *) he->value;
        }
+      else if (multi_find_instance_udp (m,  mi, real))
+        {
+          /* found instance */
+          msg (D_MULTI_LOW, "MULTI: Floated with HMAC authentication to a new 
client address: %s", 
+               print_link_socket_actual (&m->top.c2.from, &gc));
+        }
       else
        {
          if (!m->top.c2.tls_auth_standalone
@@ -111,6 +117,112 @@ multi_get_create_instance_udp (struct multi_context *m)
 }
 
 /*
+ * Find a client instance based on the HMAC, if auth is used. The function 
+ * iterates over all peers to find a fitting instance. The found instance is
+ * updated with the current peer address.
+ * If the instance doesn't exist, return false.
+ */
+bool
+multi_find_instance_udp (struct multi_context *m,  struct multi_instance *mi, 
+                         struct mroute_addr real)
+{
+  struct gc_arena gc = gc_new ();
+  struct hash *hash = m->hash;
+  struct hash_element *he;
+  const uint32_t hv = hash_value (hash, &real);
+  struct hash_bucket *bucket = hash_bucket (hash, hv);
+  struct hash_iterator hi;
+  struct mroute_addr real_old;
+  int op;
+  uint8_t c;
+  
+  perf_push (PERF_MULTI_FIND_INSTANCE);
+  
+  /* try to detect client floating */
+  if (!m->top.options.ce.remote_float 
+      || !m->top.options.authname_defined)
+   goto err;
+
+  /* minimum size 1 byte */
+  if (m->top.c2.buf.len < 1)
+    goto err;
+
+  /* Only accept DATA_V1 opcode */
+  c = *BPTR (&m->top.c2.buf);
+  op = c >> P_OPCODE_SHIFT;
+  if (op != P_DATA_V1)
+    goto err;
+
+  hash_iterator_init (hash, &hi);
+  while ((he = hash_iterator_next (&hi)))
+    {
+      mi = (struct multi_instance *) he->value;
+    
+      /* verify if this instance allows hmac verification */
+      if (!crypto_test_hmac (&m->top.c2.buf, &mi->context.c2.crypto_options))
+        continue;
+
+      generate_prefix (mi);
+      msg (D_MULTI_MEDIUM, "MULTI: Detected floating by hmac test, new client 
address: %s", 
+           print_link_socket_actual (&m->top.c2.from, &gc));
+
+      /* update address */
+      real_old = mi->real;
+      mi->real = real;
+      mi->context.c1.link_socket_addr = m->top.c1.link_socket_addr;
+      mi->context.c2.from = m->top.c2.from;             
+      mi->context.c2.to_link_addr = &mi->context.c2.from;
+  
+      /* switch to new log prefix */
+      generate_prefix (mi);
+  
+      /* inherit buffers */
+      mi->context.c2.buffers = m->top.c2.buffers;
+
+      /* inherit parent link_socket and link_socket_info */
+      mi->context.c2.link_socket = m->top.c2.link_socket;
+      mi->context.c2.link_socket_info->lsa->actual = m->top.c2.from;
+    
+      /* fix remote_addr in tls structure */
+      tls_update_remote_addr (mi->context.c2.tls_multi, &mi->context.c2.from);
+
+      mi->did_open_context = true;
+      if (IS_SIG (&mi->context))
+        goto errloop;
+
+      /* remove and readd this instance under the new address */
+      hash_iterator_delete_element (&hi);
+      hash_add_fast (hash, bucket, &mi->real, hv, mi);
+      hash_remove (m->iter, &real_old);
+      hash_add (m->iter, &mi->real, mi, false);
+#ifdef MANAGEMENT_DEF_AUTH
+      hash_remove (m->cid_hash, &mi->context.c2.mda_context.cid);
+      hash_add (m->cid_hash, &mi->context.c2.mda_context.cid, mi, false);
+#endif
+
+      /* enforce update */
+      mi->did_real_hash = true;
+      mi->did_iter = true;
+#ifdef MANAGEMENT_DEF_AUTH
+      mi->did_cid_hash = true;
+#endif
+      
+      /* cleanup */
+      hash_iterator_free (&hi);
+      perf_pop ();
+      gc_free (&gc);
+      return true;
+   }
+
+  errloop:   
+    hash_iterator_free (&hi);
+  err:
+    perf_pop ();
+    gc_free (&gc);
+    return false;
+}
+
+/*
  * Send a packet to TCP/UDP socket.
  */
 static inline void
diff --git a/src/openvpn/mudp.h b/src/openvpn/mudp.h
index 97f961b..759ea6d 100644
--- a/src/openvpn/mudp.h
+++ b/src/openvpn/mudp.h
@@ -67,5 +67,23 @@ void tunnel_server_udp (struct context *top);
  */
 struct multi_instance *multi_get_create_instance_udp (struct multi_context *m);
 
+
+/**************************************************************************/
+/**
+ * Find a client instance based on the HMAC, if auth is used.
+ * @ingroup external_multiplexer
+ *
+ * Find a client instance based on the HMAC, if auth is used. The function 
+ * iterates over all peers to find a fitting instance. The found instance is
+ * updated with the current peer address.
+ *  
+ * @param m            - The single multi_context structure.
+ * @param mi           - The multi_instance structure.
+ * @param real         - The mroute_addr structure.
+ *
+ * @return Boolen, true if peer found, false if not.
+ */
+bool multi_find_instance_udp (struct multi_context *m,  struct multi_instance 
*mi, struct mroute_addr real);
+
 #endif
 #endif
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 4d0271f..e14a1b7 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -166,6 +166,9 @@ static const char usage_message[] =
   "                  Set n=\"infinite\" to retry indefinitely.\n"
   "--float         : Allow remote to change its IP address/port, such as 
through\n"
   "                  DHCP (this is the default if --remote is not used).\n"
+#ifdef ENABLE_CRYPTO
+  "                  In server mode a valid/default auth algo is needed.\n"
+#endif
   "--ipchange cmd  : Run command cmd on remote ip address initial\n"
   "                  setting or change -- execute as: cmd ip-address port#\n"
   "--port port     : TCP/UDP port # for both local and remote.\n"
diff --git a/src/openvpn/perf.h b/src/openvpn/perf.h
index c531d9c..e1121d2 100644
--- a/src/openvpn/perf.h
+++ b/src/openvpn/perf.h
@@ -57,6 +57,8 @@
 #define PERF_PROC_OUT_TUN           18
 #define PERF_PROC_OUT_TUN_MTCP      19
 #define PERF_N                      20
+#define PERF_MULTI_FIND_INSTANCE    21
+
 
 #ifdef ENABLE_PERFORMANCE_METRICS
 
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 69f77f3..709edf7 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -3454,6 +3454,34 @@ tls_rec_payload (struct tls_multi *multi,
   return ret;
 }
 
+/* Update the remote_addr, needed if a client floats. */
+void
+tls_update_remote_addr (struct tls_multi *multi,
+               const struct link_socket_actual *from)
+{
+  struct gc_arena gc = gc_new ();
+  int i;
+  
+  for (i = 0; i < KEY_SCAN_SIZE; ++i)
+    {
+      struct key_state *ks = multi->key_scan[i];
+      if (DECRYPT_KEY_ENABLED (multi, ks)
+         && ks->authenticated
+         && link_socket_actual_defined(&ks->remote_addr)
+      )
+       {
+         if (link_socket_actual_match (from, &ks->remote_addr))
+           continue;
+          dmsg (D_TLS_KEYSELECT,
+              "TLS: tls_update_remote_addr from IP=%s to IP=%s",
+              print_link_socket_actual (&ks->remote_addr, &gc),
+              print_link_socket_actual (from, &gc));
+          ks->remote_addr = *from;
+       }
+    }
+  gc_free (&gc);
+}
+
 /*
  * Dump a human-readable rendition of an openvpn packet
  * into a garbage collectable string which is returned.
diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h
index cd7cae2..c501b64 100644
--- a/src/openvpn/ssl.h
+++ b/src/openvpn/ssl.h
@@ -431,6 +431,12 @@ bool tls_send_payload (struct tls_multi *multi,
 bool tls_rec_payload (struct tls_multi *multi,
                      struct buffer *buf);
 
+/*
+ * Update remote address of a tls_multi structure
+ */
+void tls_update_remote_addr (struct tls_multi *multi,
+                 const struct link_socket_actual *from);
+                 
 #ifdef MANAGEMENT_DEF_AUTH
 static inline char *
 tls_get_peer_info(const struct tls_multi *multi)
-- 
1.7.10.4


Reply via email to