Browse code

Poor man's NCP for non-NCP peers

Allows non-NCP peers (<= 2.3, or 2.4+ with --ncp-disable) to specify a
--cipher that is different from the one in our config, as long as the new
cipher value is allowed (i.e. in --ncp-ciphers at our side).

This works both client-to-server and server-to-client. I.e. a 2.4 client
with "cipher BF-CBC" and "ncp-ciphers AES-256-GCM:AES-256-CBC" can connect
to both a 2.3 server with "cipher BF-CBC" as well as a server with
"cipher AES-256-CBC" in its config. The other way around, a 2.3 client
with either "cipher BF-CBC" or "cipher AES-256-CBC" can connect to a 2.4
server with e.g. "cipher BF-CBC" and "ncp-ciphers AES-256-GCM:AES-256-CBC"
in its config.

This patch was inspired by Gert's "Poor man's NCP for 2.3 clients" patch,
but takes a different approach to avoid the need for server-side scripts
or client-side 'setenv UV_*' tricks.

Signed-off-by: Steffan Karger <steffan@karger.me>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <1479936104-4045-1-git-send-email-steffan@karger.me>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg13218.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Steffan Karger authored on 2016/11/24 06:21:44
Showing 8 changed files
... ...
@@ -4148,9 +4148,9 @@ to disable encryption.
4148 4148
 As of OpenVPN 2.4, cipher negotiation (NCP) can override the cipher specified by
4149 4149
 .B \-\-cipher\fR.
4150 4150
 See
4151
-.B \-\-ncp-ciphers
4151
+.B \-\-ncp\-ciphers
4152 4152
 and
4153
-.B \-\-ncp-disable
4153
+.B \-\-ncp\-disable
4154 4154
 for more on NCP.
4155 4155
 
4156 4156
 .\"*********************************************************
... ...
@@ -4178,6 +4178,16 @@ If both peers support and do not disable NCP, the negotiated cipher will
4178 4178
 override the cipher specified by
4179 4179
 .B \-\-cipher\fR.
4180 4180
 
4181
+Additionally, to allow for more smooth transition, if NCP is enabled, OpenVPN
4182
+will inherit the cipher of the peer if that cipher is different from the local
4183
+.B \-\-cipher
4184
+setting, but the peer cipher is one of the ciphers specified in
4185
+.B \-\-ncp\-ciphers\fR.
4186
+E.g. a non-NCP client (<=2.3, or with \-\-ncp\-disabled set) connecting to a
4187
+NCP server (2.4+) with "\-\-cipher BF-CBC" and "\-\-ncp-ciphers
4188
+AES-256-GCM:AES-256-CBC" set can either specify "\-\-cipher BF-CBC" or
4189
+"\-\-cipher AES-256-CBC" and both will work.
4190
+
4181 4191
 .\"*********************************************************
4182 4192
 .TP
4183 4193
 .B \-\-ncp\-disable
... ...
@@ -1932,8 +1932,14 @@ do_deferred_options (struct context *c, const unsigned int found)
1932 1932
     {
1933 1933
       struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
1934 1934
       if (found & OPT_P_NCP)
1935
-	msg (D_PUSH, "OPTIONS IMPORT: data channel crypto options modified");
1936
-      /* Do not regenerate keys if server sends an extra push request */
1935
+	{
1936
+	  msg (D_PUSH, "OPTIONS IMPORT: data channel crypto options modified");
1937
+	}
1938
+      else if (c->options.ncp_enabled)
1939
+	{
1940
+	  tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername);
1941
+	}
1942
+      /* Do not regenerate keys if server sends an extra push reply */
1937 1943
       if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized &&
1938 1944
 	  !tls_session_update_crypto_params(session, &c->options, &c->c2.frame))
1939 1945
 	{
... ...
@@ -1643,6 +1643,8 @@ show_settings (const struct options *o)
1643 1643
   SHOW_STR (shared_secret_file);
1644 1644
   SHOW_INT (key_direction);
1645 1645
   SHOW_STR (ciphername);
1646
+  SHOW_BOOL (ncp_enabled);
1647
+  SHOW_STR (ncp_ciphers);
1646 1648
   SHOW_STR (authname);
1647 1649
   SHOW_STR (prng_hash);
1648 1650
   SHOW_INT (prng_nonce_secret_len);
... ...
@@ -3445,6 +3447,36 @@ options_string_version (const char* s, struct gc_arena *gc)
3445 3445
 
3446 3446
 #endif /* ENABLE_OCC */
3447 3447
 
3448
+char *
3449
+options_string_extract_option (const char *options_string,const char *opt_name,
3450
+    struct gc_arena *gc)
3451
+{
3452
+  char *ret = NULL;
3453
+  const size_t opt_name_len = strlen(opt_name);
3454
+
3455
+  const char *p = options_string;
3456
+  while (p)
3457
+    {
3458
+      if (0 == strncmp(p, opt_name, opt_name_len) &&
3459
+	  strlen(p) > (opt_name_len+1) && p[opt_name_len] == ' ')
3460
+	{
3461
+	  /* option found, extract value */
3462
+	  const char *start = &p[opt_name_len+1];
3463
+	  const char *end = strchr (p, ',');
3464
+	  size_t val_len = end ? end - start : strlen (start);
3465
+	  ret = gc_malloc (val_len+1, true, gc);
3466
+	  memcpy (ret, start, val_len);
3467
+	  break;
3468
+	}
3469
+      p = strchr (p, ',');
3470
+      if (p)
3471
+	{
3472
+	  p++; /* skip delimiter */
3473
+	}
3474
+    }
3475
+  return ret;
3476
+}
3477
+
3448 3478
 static void
3449 3479
 foreign_option (struct options *o, char *argv[], int len, struct env_set *es)
3450 3480
 {
... ...
@@ -726,6 +726,20 @@ void options_warning (char *actual, const char *expected);
726 726
 
727 727
 #endif
728 728
 
729
+/**
730
+ * Given an OpenVPN options string, extract the value of an option.
731
+ *
732
+ * @param options_string	Zero-terminated, comma-separated options string
733
+ * @param opt_name		The name of the option to extract
734
+ * @param gc			The gc to allocate the return value
735
+ *
736
+ * @return gc-allocated value of option with name opt_name if option was found,
737
+ *         or NULL otherwise.
738
+ */
739
+char *options_string_extract_option (const char *options_string,
740
+    const char *opt_name, struct gc_arena *gc);
741
+
742
+
729 743
 void options_postprocess (struct options *options);
730 744
 
731 745
 void pre_pull_save (struct options *o);
... ...
@@ -262,7 +262,7 @@ incoming_push_message (struct context *c, const struct buffer *buffer)
262 262
 	      !tls_session_update_crypto_params (session, &c->options,
263 263
 		  &c->c2.frame))
264 264
 	    {
265
-	      msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed");
265
+	      msg (D_TLS_ERRORS, "TLS Error: initializing data channel failed");
266 266
 	      goto error;
267 267
 	    }
268 268
 	}
... ...
@@ -370,6 +370,10 @@ prepare_push_reply (struct context *c, struct gc_arena *gc,
370 370
 	  push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername);
371 371
 	}
372 372
     }
373
+  else if (o->ncp_enabled)
374
+    {
375
+      tls_poor_mans_ncp (o, tls_multi->remote_ciphername);
376
+    }
373 377
 
374 378
   /* If server uses --auth-gen-token and we have an auth token
375 379
    * to send to the client
... ...
@@ -1216,6 +1216,8 @@ tls_multi_free (struct tls_multi *multi, bool clear)
1216 1216
       free (multi->auth_token);
1217 1217
     }
1218 1218
 
1219
+  free (multi->remote_ciphername);
1220
+
1219 1221
   for (i = 0; i < TM_SIZE; ++i)
1220 1222
     tls_session_free (&multi->session[i], false);
1221 1223
 
... ...
@@ -1723,8 +1725,8 @@ key_ctx_update_implicit_iv(struct key_ctx *ctx, uint8_t *key, size_t key_len) {
1723 1723
     }
1724 1724
 }
1725 1725
 
1726
-static bool
1727
-item_in_list(const char *item, const char *list)
1726
+bool
1727
+tls_item_in_cipher_list(const char *item, const char *list)
1728 1728
 {
1729 1729
   char *tmp_ciphers = string_alloc (list, NULL);
1730 1730
   char *tmp_ciphers_orig = tmp_ciphers;
... ...
@@ -1741,6 +1743,20 @@ item_in_list(const char *item, const char *list)
1741 1741
   return token != NULL;
1742 1742
 }
1743 1743
 
1744
+void
1745
+tls_poor_mans_ncp(struct options *o, const char *remote_ciphername)
1746
+{
1747
+  if (o->ncp_enabled && remote_ciphername &&
1748
+      0 != strcmp(o->ciphername, remote_ciphername))
1749
+    {
1750
+      if (tls_item_in_cipher_list(remote_ciphername, o->ncp_ciphers))
1751
+	{
1752
+	  o->ciphername = string_alloc(remote_ciphername, &o->gc);
1753
+	  msg (D_TLS_DEBUG_LOW, "Using peer cipher '%s'", o->ciphername);
1754
+	}
1755
+    }
1756
+}
1757
+
1744 1758
 bool
1745 1759
 tls_session_update_crypto_params(struct tls_session *session,
1746 1760
     const struct options *options, struct frame *frame)
... ...
@@ -1752,7 +1768,7 @@ tls_session_update_crypto_params(struct tls_session *session,
1752 1752
 
1753 1753
   if (!session->opt->server &&
1754 1754
       0 != strcmp(options->ciphername, session->opt->config_ciphername) &&
1755
-      !item_in_list(options->ciphername, options->ncp_ciphers))
1755
+      !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))
1756 1756
     {
1757 1757
       msg (D_TLS_ERRORS, "Error: pushed cipher not allowed - %s not in %s or %s",
1758 1758
 	  options->ciphername, session->opt->config_ciphername,
... ...
@@ -2312,10 +2328,20 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi
2312 2312
   if ( multi->peer_info )
2313 2313
       output_peer_info_env (session->opt->es, multi->peer_info);
2314 2314
 
2315
+  free (multi->remote_ciphername);
2316
+  multi->remote_ciphername =
2317
+      options_string_extract_option (options, "cipher", NULL);
2318
+
2315 2319
   if (tls_peer_info_ncp_ver (multi->peer_info) < 2)
2316 2320
     {
2317
-      /* Peer does not support NCP */
2318
-      session->opt->ncp_enabled = false;
2321
+      /* Peer does not support NCP, but leave NCP enabled if the local and
2322
+       * remote cipher do not match to attempt 'poor-man's NCP'.
2323
+       */
2324
+      if (multi->remote_ciphername == NULL ||
2325
+           0 == strcmp(multi->remote_ciphername, multi->opt.config_ciphername))
2326
+       {
2327
+         session->opt->ncp_enabled = false;
2328
+       }
2319 2329
     }
2320 2330
 #endif
2321 2331
 
... ...
@@ -489,6 +489,15 @@ void tls_update_remote_addr (struct tls_multi *multi,
489 489
 bool tls_session_update_crypto_params(struct tls_session *session,
490 490
     const struct options *options, struct frame *frame);
491 491
 
492
+/**
493
+ * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.
494
+ * Allows non-NCP peers to upgrade their cipher individually.
495
+ *
496
+ * Make sure to call tls_session_update_crypto_params() after calling this
497
+ * function.
498
+ */
499
+void tls_poor_mans_ncp(struct options *o, const char *remote_ciphername);
500
+
492 501
 #ifdef MANAGEMENT_DEF_AUTH
493 502
 static inline char *
494 503
 tls_get_peer_info(const struct tls_multi *multi)
... ...
@@ -512,6 +521,13 @@ int tls_peer_info_ncp_ver(const char *peer_info);
512 512
  */
513 513
 bool tls_check_ncp_cipher_list(const char *list);
514 514
 
515
+/**
516
+ * Return true iff item is present in the colon-separated zero-terminated
517
+ * cipher list.
518
+ */
519
+bool tls_item_in_cipher_list(const char *item, const char *list);
520
+
521
+
515 522
 /*
516 523
  * inline functions
517 524
  */
... ...
@@ -540,6 +540,8 @@ struct tls_multi
540 540
   uint32_t peer_id;
541 541
   bool use_peer_id;
542 542
 
543
+  char *remote_ciphername;	/**< cipher specified in peer's config file */
544
+
543 545
   char *auth_token;      /**< If server sends a generated auth-token,
544 546
                           *   this is the token to use for future
545 547
                           *   user/pass authentications in this session.