Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: Steffan Karger <steffan <at> karger.me>
Subject: [PATCHv2 3/5] Add client-side support for cipher negotiation
Newsgroups: gmane.network.openvpn.devel
Date: Wednesday 8th June 2016 18:34:42 UTC (about 1 year ago)
Based on the 'IV_NCP=2' mechanism described in
http://permalink.gmane.org/gmane.network.openvpn.devel/9385.

This is the first patch of a set that adds support for cipher negotiation.
Follow-up patches will add ways to restrict or disable the mechanism, and
add server-side support.

v2:
 * Account for crypto overhead through struct frame.  This is less
   transparant, but the code has been built to work this way.  The
   previous approach didn't work with TCP mode (or --port-share).
 * Calculate the link-mtu sent in the options string based on the crypto
   parameters specified in the config file (prevents link-mtu warnings in
   older peers when connecting).

Signed-off-by: Steffan Karger 
---
 src/openvpn/crypto.c         | 18 +++++++++---
 src/openvpn/crypto.h         |  4 +++
 src/openvpn/crypto_backend.h |  6 ++++
 src/openvpn/init.c           | 28 ++++++++++++++----
 src/openvpn/misc.h           |  7 +++++
 src/openvpn/mtu.c            |  2 --
 src/openvpn/options.c        | 35 ++++++++++++++++++++--
 src/openvpn/route.c          |  4 +--
 src/openvpn/ssl.c            | 69
+++++++++++++++++++++++++++++++++++++++-----
 src/openvpn/ssl.h            | 14 +++++++++
 src/openvpn/ssl_common.h     | 16 ++++++++--
 11 files changed, 177 insertions(+), 26 deletions(-)

diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index 5c392aa..e43c30b 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -709,10 +709,6 @@ openvpn_decrypt (struct buffer *buf, struct buffer
work,
   return ret;
 }
 
-/*
- * How many bytes will we add to frame buffer for a given
- * set of crypto options?
- */
 void
 crypto_adjust_frame_parameters(struct frame *frame,
 			       const struct key_type* kt,
@@ -746,6 +742,14 @@ crypto_adjust_frame_parameters(struct frame *frame,
       __func__, crypto_overhead);
 }
 
+size_t
+crypto_max_overhead(void)
+{
+  return packet_id_size(true) + OPENVPN_MAX_IV_LENGTH +
+      OPENVPN_MAX_CIPHER_BLOCK_SIZE +
+      MAX(OPENVPN_MAX_HMAC_SIZE, OPENVPN_AEAD_TAG_LENGTH);
+}
+
 /*
  * Build a struct key_type.
  */
@@ -774,6 +778,9 @@ init_key_type (struct key_type *kt, const char
*ciphername,
 #endif
 	    ))
 	msg (M_FATAL, "Cipher '%s' mode not supported", ciphername);
+
+      if (OPENVPN_MAX_CIPHER_BLOCK_SIZE <
cipher_kt_block_size(kt->cipher))
+	msg (M_FATAL, "Cipher '%s' not allowed: block size too big.",
ciphername);
     }
   else
     {
@@ -785,6 +792,9 @@ init_key_type (struct key_type *kt, const char
*ciphername,
       if (!aead_cipher) { /* Ignore auth for AEAD ciphers */
 	kt->digest = md_kt_get (authname);
 	kt->hmac_length = md_kt_size (kt->digest);
+
+	if (OPENVPN_MAX_HMAC_SIZE < kt->hmac_length)
+	  msg (M_FATAL, "HMAC '%s' not allowed: digest size too big.", authname);
       }
     }
   else if (!aead_cipher)
diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h
index 63d7040..de433ae 100644
--- a/src/openvpn/crypto.h
+++ b/src/openvpn/crypto.h
@@ -226,6 +226,7 @@ struct key_ctx_bi
 				 *   direction. */
   struct key_ctx decrypt;       /**< cipher and/or HMAC contexts for
                                  *   receiving direction. */
+  bool initialized;
 };
 
 /**
@@ -385,6 +386,7 @@ bool openvpn_decrypt (struct buffer *buf, struct buffer
work,
 
 /** @} name Functions for performing security operations on data channel
packets */
 
+/** Calculate crypto overhead and adjust frame to account for that */
 void crypto_adjust_frame_parameters(struct frame *frame,
 				    const struct key_type* kt,
 				    bool cipher_defined,
@@ -392,6 +394,8 @@ void crypto_adjust_frame_parameters(struct frame
*frame,
 				    bool packet_id,
 				    bool packet_id_long_form);
 
+/** Return the worst-case OpenVPN crypto overhead (in bytes) */
+size_t crypto_max_overhead(void);
 
 /* Minimum length of the nonce used by the PRNG */
 #define NONCE_SECRET_LEN_MIN 16
diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h
index 3893f14..a699673 100644
--- a/src/openvpn/crypto_backend.h
+++ b/src/openvpn/crypto_backend.h
@@ -41,6 +41,12 @@
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
 
+/* Maximum cipher block size (bytes) */
+#define OPENVPN_MAX_CIPHER_BLOCK_SIZE 32
+
+/* Maximum HMAC digest size (bytes) */
+#define OPENVPN_MAX_HMAC_SIZE 	64
+
 /** Struct used in cipher name translation table */
 typedef struct {
   const char *openvpn_name;	/**< Cipher name used by OpenVPN */
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 50cbf90..8f81b09 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1819,6 +1819,7 @@ pull_permission_mask (const struct context *c)
     | OPT_P_SHAPER
     | OPT_P_TIMER
     | OPT_P_COMP
+    | OPT_P_CRYPTO
     | OPT_P_PERSIST
     | OPT_P_MESSAGES
     | OPT_P_EXPLICIT_NOTIFY
@@ -1922,6 +1923,13 @@ do_deferred_options (struct context *c, const
unsigned int found)
                        " MTU problems", TUN_MTU_SIZE(&c->c2.frame) );
 	}
     }
+
+  /* process (potenitally pushed) crypto options */
+  if (c->options.pull)
+    {
+     
tls_session_update_crypto_params(&c->c2.tls_multi->session[TM_ACTIVE],
+          &c->options, &c->c2.frame);
+    }
 #endif
 }
 
@@ -2027,6 +2035,7 @@ frame_finalize_options (struct context *c, const
struct options *o)
 			    |FRAME_HEADROOM_MARKER_READ_STREAM);
     }
   
+  frame_add_to_extra_buffer (&c->c2.frame, PAYLOAD_ALIGN);
   frame_finalize (&c->c2.frame,
 		  o->ce.link_mtu_defined,
 		  o->ce.link_mtu,
@@ -2282,12 +2291,18 @@ do_init_crypto_tls (struct context *c, const
unsigned int flags)
   /* In short form, unique datagram identifier is 32 bits, in long form 64
bits */
   packet_id_long_form = cipher_kt_mode_ofb_cfb (c->c1.ks.key_type.cipher);
 
-  /* Compute MTU parameters */
-  crypto_adjust_frame_parameters (&c->c2.frame,
-				  &c->c1.ks.key_type,
-				  options->ciphername_defined,
-				  options->use_iv,
-				  options->replay, packet_id_long_form);
+  /* Compute MTU parameters (postpone if we pull options) */
+  if (c->options.pull)
+    {
+      /* Account for worst-case crypto overhead before allocating buffers
*/
+      frame_add_to_extra_frame (&c->c2.frame, crypto_max_overhead());
+    }
+  else
+    {
+      crypto_adjust_frame_parameters(&c->c2.frame, &c->c1.ks.key_type,
+	  options->ciphername_defined, options->use_iv, options->replay,
+	  packet_id_long_form);
+    }
   tls_adjust_frame_parameters (&c->c2.frame);
 
   /* Set all command-line TLS-related options */
@@ -2318,6 +2333,7 @@ do_init_crypto_tls (struct context *c, const unsigned
int flags)
   to.renegotiate_packets = options->renegotiate_packets;
   to.renegotiate_seconds = options->renegotiate_seconds;
   to.single_session = options->single_session;
+  to.pull = options->pull;
 #ifdef ENABLE_PUSH_PEER_INFO
   if (options->push_peer_info)		/* all there is */
     to.push_peer_info_detail = 2;
diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h
index 65a6e55..82d1d48 100644
--- a/src/openvpn/misc.h
+++ b/src/openvpn/misc.h
@@ -102,6 +102,13 @@ openvpn_run_script (const struct argv *a, const struct
env_set *es, const unsign
   return openvpn_execve_check(a, es, flags | S_SCRIPT, msg);
 }
 
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
 
 #ifdef HAVE_STRERROR
 /* a thread-safe version of strerror */
diff --git a/src/openvpn/mtu.c b/src/openvpn/mtu.c
index 24531c9..64d1cf3 100644
--- a/src/openvpn/mtu.c
+++ b/src/openvpn/mtu.c
@@ -78,8 +78,6 @@ frame_finalize (struct frame *frame,
     }
 
   frame->link_mtu_dynamic = frame->link_mtu;
-
-  frame->extra_buffer += PAYLOAD_ALIGN;
 }
 
 /*
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 23f407c..a595dff 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -2974,6 +2974,38 @@ pre_pull_restore (struct options *o, struct gc_arena
*gc)
 
 #ifdef ENABLE_OCC
 
+/**
+ * Calculate the link-mtu to advertise to our peer.  The actual value is
not
+ * relevant, because we will possibly perform data channel cipher
negotiation
+ * after this, but older clients will log warnings if we do not supply
them the
+ * value they expect.  This assumes that the traditional cipher/auth
directives
+ * in the config match the config of the peer.
+ */
+static size_t
+calc_options_string_link_mtu(const struct options *o, const struct frame
*frame)
+{
+  size_t link_mtu = EXPANDED_SIZE (frame);
+#ifdef ENABLE_CRYPTO
+  if (o->pull || o->mode == MODE_SERVER)
+    {
+      struct frame fake_frame = *frame;
+      struct key_type fake_kt;
+      init_key_type (&fake_kt, o->ciphername, o->ciphername_defined,
+	  o->authname, o->authname_defined, o->keysize, true, false);
+      frame_add_to_extra_frame (&fake_frame, -(crypto_max_overhead()));
+      crypto_adjust_frame_parameters (&fake_frame, &fake_kt,
+	  o->ciphername_defined, o->use_iv, o->replay,
+	  cipher_kt_mode_ofb_cfb (fake_kt.cipher));
+      frame_finalize(&fake_frame, o->ce.link_mtu_defined, o->ce.link_mtu,
+            o->ce.tun_mtu_defined, o->ce.tun_mtu);
+      msg (D_MTU_DEBUG, "%s: link-mtu %zu -> %d", __func__, link_mtu,
+	  EXPANDED_SIZE (&fake_frame));
+      link_mtu = EXPANDED_SIZE (&fake_frame);
+    }
+#endif
+  return link_mtu;
+}
+
 /*
  * Build an options string to represent data channel encryption options.
  * This string must match exactly between peers.  The keysize is checked
@@ -3018,7 +3050,6 @@ pre_pull_restore (struct options *o, struct gc_arena
*gc)
  * --tls-server [matched with --tls-client on
  *               the other end of the connection]
  */
-
 char *
 options_string (const struct options *o,
 		const struct frame *frame,
@@ -3036,7 +3067,7 @@ options_string (const struct options *o,
    */
 
   buf_printf (&out, ",dev-type %s", dev_type_string (o->dev,
o->dev_type));
-  buf_printf (&out, ",link-mtu %d", EXPANDED_SIZE (frame));
+  buf_printf (&out, ",link-mtu %zu", calc_options_string_link_mtu(o,
frame));
   buf_printf (&out, ",tun-mtu %d", PAYLOAD_SIZE (frame));
   buf_printf (&out, ",proto %s",  proto_remote (o->ce.proto, remote));
 
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index a90195f..578a708 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -3265,8 +3265,6 @@ struct rtmsg {
 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
 #endif
 
-#define max(a,b) ((a) > (b) ? (a) : (b))
-
 void
 get_default_gateway (struct route_gateway_info *rgi)
 {
@@ -3433,7 +3431,7 @@ get_default_gateway (struct route_gateway_info *rgi)
 #if defined(TARGET_SOLARIS)
 	  const size_t len = sizeof(ifr->ifr_name) + sizeof(ifr->ifr_addr);
 #else
-	  const size_t len = sizeof(ifr->ifr_name) + max(sizeof(ifr->ifr_addr),
ifr->ifr_addr.sa_len);
+	  const size_t len = sizeof(ifr->ifr_name) + MAX(sizeof(ifr->ifr_addr),
ifr->ifr_addr.sa_len);
 #endif
 
 	  if (!ifr->ifr_addr.sa_family)
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 4291314..0c0061c 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -767,10 +767,6 @@ key_state_init (struct tls_session *session, struct
key_state *ks)
   ks->state = S_INITIAL;
   ks->key_id = session->key_id;
 
-  /*
-   * key_id increments to KEY_ID_MASK then recycles back to 1.
-   * This way you know that if key_id is 0, it is the first key.
-   */
   ++session->key_id;
   session->key_id &= P_KEY_ID_MASK;
   if (!session->key_id)
@@ -1613,6 +1609,7 @@ generate_key_expansion (struct key_ctx_bi *key,
   key_ctx_update_implicit_iv (&key->decrypt,
key2.keys[1-(int)server].hmac,
       MAX_HMAC_KEY_LENGTH);
 
+  key->initialized = true;
   ret = true;
 
  exit:
@@ -1639,6 +1636,50 @@ key_ctx_update_implicit_iv(struct key_ctx *ctx,
uint8_t *key, size_t key_len) {
     }
 }
 
+bool
+tls_session_update_crypto_params(struct tls_session *session,
+    const struct options *options, struct frame *frame)
+{
+  bool ret = false;
+  struct key_state *ks = &session->key[KS_PRIMARY];	/* primary key */
+
+  ASSERT (!session->opt->server);
+  ASSERT (ks->authenticated);
+
+  init_key_type (&session->opt->key_type, options->ciphername,
+    options->ciphername_defined, options->authname,
options->authname_defined,
+    options->keysize, true, true);
+
+  bool packet_id_long_form = cipher_kt_mode_ofb_cfb
(session->opt->key_type.cipher);
+  session->opt->crypto_flags_and &= ~(CO_PACKET_ID_LONG_FORM);
+  if (packet_id_long_form)
+    session->opt->crypto_flags_and = CO_PACKET_ID_LONG_FORM;
+
+  /* Update frame parameters: undo worst-case overhead, add actual
overhead */
+  frame_add_to_extra_frame (frame, -(crypto_max_overhead()));
+  crypto_adjust_frame_parameters (frame, &session->opt->key_type,
+      options->ciphername_defined, options->use_iv, options->replay,
+      packet_id_long_form);
+  frame_finalize(frame, options->ce.link_mtu_defined,
options->ce.link_mtu,
+      options->ce.tun_mtu_defined, options->ce.tun_mtu);
+  frame_print (frame, D_MTU_INFO, "Data Channel MTU parms");
+
+  if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi,
+			       &session->opt->key_type,
+			       ks->key_src,
+			       &session->session_id,
+			       &ks->session_id_remote,
+			       false))
+    {
+      msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion
failed");
+      goto cleanup;
+    }
+  ret = true;
+cleanup:
+  CLEAR (*ks->key_src);
+  return ret;
+}
+
 static bool
 random_bytes_to_buf (struct buffer *buf,
 		     uint8_t *out,
@@ -1885,6 +1926,9 @@ push_peer_info(struct buffer *buf, struct tls_session
*session)
       /* support for P_DATA_V2 */
       buf_printf(&out, "IV_PROTO=2\n");
 
+      /* support for Negotiable Crypto Paramters */
+      buf_printf(&out, "IV_NCP=2\n");
+
       /* push compression status */
 #ifdef USE_COMP
       comp_generate_peer_info_string(&session->opt->comp_options, &out);
@@ -2209,9 +2253,11 @@ key_method_2_read (struct buffer *buf, struct
tls_multi *multi, struct tls_sessi
     }
 
   /*
-   * Generate tunnel keys if client
+   * Generate tunnel keys if we're a client.
+   * If --pull is enabled, the first key generation is postponed until
after the
+   * pull/push, so we can process pushed cipher directives.
    */
-  if (!session->opt->server)
+  if (!session->opt->server && (!session->opt->pull || ks->key_id > 0))
     {
       if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi,
 				   &session->opt->key_type,
@@ -2223,7 +2269,7 @@ key_method_2_read (struct buffer *buf, struct
tls_multi *multi, struct tls_sessi
 	  msg (D_TLS_ERRORS, "TLS Error: client generate_key_expansion failed");
 	  goto error;
 	}
-		      
+
       CLEAR (*ks->key_src);
     }
 
@@ -2889,6 +2935,14 @@ tls_pre_decrypt (struct tls_multi *multi,
 #endif
 		  && (floated || link_socket_actual_match (from, &ks->remote_addr)))
 		{
+		  if (!ks->crypto_options.key_ctx_bi.initialized)
+		    {
+		      msg (D_TLS_DEBUG_LOW,
+			  "Key %s [%d] not initialized (yet), dropping packet.",
+			  print_link_socket_actual (from, &gc), key_id);
+		      goto error_lite;
+		    }
+
 		  /* return appropriate data channel decrypt key in opt */
 		  *opt = &ks->crypto_options;
 		  if (op == P_DATA_V2)
@@ -3426,6 +3480,7 @@ tls_pre_encrypt (struct tls_multi *multi,
 	  struct key_state *ks = multi->key_scan[i];
 	  if (ks->state >= S_ACTIVE
 	      && ks->authenticated
+	      && ks->crypto_options.key_ctx_bi.initialized
 #ifdef ENABLE_DEF_AUTH
 	      && !ks->auth_deferred
 #endif
diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h
index d9ff8d0..416f426 100644
--- a/src/openvpn/ssl.h
+++ b/src/openvpn/ssl.h
@@ -475,6 +475,20 @@ bool tls_rec_payload (struct tls_multi *multi,
 void tls_update_remote_addr (struct tls_multi *multi,
 			     const struct link_socket_actual *addr);
 
+/**
+ * Update TLS session crypto parameters (cipher and auth) and derive data
+ * channel keys based on the supplied options.
+ *
+ * @param session	The TLS session to update.
+ * @param options	The options to use when updating session.
+ * @param frame		The frame options for this session (frame overhead is
+ * 			adjusted based on the selected cipher/auth).
+ *
+ * @return true if updating succeeded, false otherwise.
+ */
+bool tls_session_update_crypto_params(struct tls_session *session,
+    const struct options *options, struct frame *frame);
+
 #ifdef MANAGEMENT_DEF_AUTH
 static inline char *
 tls_get_peer_info(const struct tls_multi *multi)
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index a0df0ff..9183dab 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -149,7 +149,12 @@ struct key_source2 {
 struct key_state
 {
   int state;
-  int key_id;			/* inherited from struct tls_session below */
+
+  /**
+   * Key id for this key_state,  inherited from struct tls_session.
+   * @see tls_session::key_id.
+   */
+  int key_id;
 
   struct key_state_ssl ks_ssl;	/* contains SSL object and BIOs for the
control channel */
 
@@ -231,6 +236,7 @@ struct tls_options
 #ifdef ENABLE_OCC
   bool disable_occ;
 #endif
+  bool pull;
 #ifdef ENABLE_PUSH_PEER_INFO
   int push_peer_info_detail;
 #endif
@@ -367,7 +373,13 @@ struct tls_session
 
   int initial_opcode;		/* our initial P_ opcode */
   struct session_id session_id;	/* our random session ID */
-  int key_id;			/* increments with each soft reset (for key renegotiation)
*/
+
+  /**
+   * The current active key id, used to keep track of renegotiations.
+   * key_id increments with each soft reset to KEY_ID_MASK then recycles
back
+   * to 1.  This way you know that if key_id is 0, it is the first key.
+   */
+  int key_id;
 
   int limit_next;               /* used for traffic shaping on the control
channel */
 
-- 
2.7.4


------------------------------------------------------------------------------
What NetFlow Analyzer can do for you? Monitors network bandwidth and
traffic
patterns at an interface-level. Reveals which users, apps, and protocols
are 
consuming the most bandwidth. Provides multi-vendor support for NetFlow, 
J-Flow, sFlow and other flows. Make informed decisions using capacity 
planning reports. https://ad.doubleclick.net/ddm/clk/305295220;132659582;e
 
CD: 3ms