Add --ncp-disable to completely disable cipher negotiation, and
--ncp-ciphers to specify which ciphers to accept from the server.

v2:
 * fix --disable-crypto builds
 * use register_signal() instead of operating directly on c->sig
 * add man-page entry for new options

Signed-off-by: Steffan Karger <stef...@karger.me>

restrict man fix
---
 doc/openvpn.8            | 13 +++++++++++++
 src/openvpn/init.c       | 36 ++++++++++++++++++++++++++++++------
 src/openvpn/init.h       |  4 ++--
 src/openvpn/openvpn.h    |  3 +++
 src/openvpn/options.c    | 40 +++++++++++++++++++++++++++++-----------
 src/openvpn/options.h    |  4 +++-
 src/openvpn/push.c       | 11 ++++++++++-
 src/openvpn/ssl.c        | 30 +++++++++++++++++++++++++++++-
 src/openvpn/ssl_common.h |  4 ++++
 9 files changed, 123 insertions(+), 22 deletions(-)

diff --git a/doc/openvpn.8 b/doc/openvpn.8
index 03f31bb..e349b77 100644
--- a/doc/openvpn.8
+++ b/doc/openvpn.8
@@ -4128,6 +4128,19 @@ Set
 to disable encryption.
 .\"*********************************************************
 .TP
+.B \-\-ncp\-ciphers cipher_list
+Restrict the allowed ciphers to be negotiated to the ciphers in
+.B cipher_list\fR.
+.B cipher_list
+is a colon-separated list of ciphers, and defaults to
+"AES-256-GCM:AES-128-GCM".
+.\"*********************************************************
+.TP
+.B \-\-ncp\-disable
+Disable "negotiable crypto parameters".  This completely disables cipher
+negotiation.
+.\"*********************************************************
+.TP
 .B \-\-keysize n
 Size of cipher key in bits (optional).
 If unspecified, defaults to cipher-specific default.  The
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 8f81b09..c2e3b2e 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1739,7 +1739,7 @@ options_hash_changed_or_zero(const struct md5_digest *a,
 }
 #endif /* P2MP */

-void
+bool
 do_up (struct context *c, bool pulled_options, unsigned int option_types_found)
 {
   if (!c->c2.do_up_ran)
@@ -1747,7 +1747,13 @@ do_up (struct context *c, bool pulled_options, unsigned 
int option_types_found)
       reset_coarse_timers (c);

       if (pulled_options && option_types_found)
-       do_deferred_options (c, option_types_found);
+       {
+         if (!do_deferred_options (c, option_types_found))
+           {
+             msg (D_PUSH_ERRORS, "ERROR: Failed to apply push options");
+             return false;
+           }
+       }

       /* if --up-delay specified, open tun, do ifconfig, and run up script now 
*/
       if (c->options.up_delay || PULL_DEFINED (&c->options))
@@ -1802,6 +1808,7 @@ do_up (struct context *c, bool pulled_options, unsigned 
int option_types_found)

       c->c2.do_up_ran = true;
     }
+  return true;
 }

 /*
@@ -1819,7 +1826,6 @@ 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
@@ -1830,13 +1836,18 @@ pull_permission_mask (const struct context *c)
   if (!c->options.route_nopull)
     flags |= (OPT_P_ROUTE | OPT_P_IPWIN32);

+#ifdef ENABLE_CRYPTO
+  if (c->options.ncp_enabled)
+    flags |= OPT_P_NCP;
+#endif
+
   return flags;
 }

 /*
  * Handle non-tun-related pulled options.
  */
-void
+bool
 do_deferred_options (struct context *c, const unsigned int found)
 {
   if (found & OPT_P_MESSAGES)
@@ -1927,10 +1938,17 @@ do_deferred_options (struct context *c, const unsigned 
int found)
   /* 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);
+      if (found & OPT_P_NCP)
+       msg (D_PUSH, "OPTIONS IMPORT: data channel crypto options modified");
+      if (!tls_session_update_crypto_params(
+         &c->c2.tls_multi->session[TM_ACTIVE], &c->options, &c->c2.frame))
+       {
+         msg (D_TLS_ERRORS, "OPTIONS ERROR: failed to import crypto options");
+         return false;
+       }
     }
 #endif
+  return true;
 }

 /*
@@ -2250,6 +2268,9 @@ do_init_crypto_tls_c1 (struct context *c)
              &c->c1.ks.tls_auth_key, file, options->key_direction, flags);
        }

+      c->c1.ciphername = options->ciphername;
+      c->c1.authname = options->authname;
+
 #if 0 /* was: #if ENABLE_INLINE_FILES --  Note that enabling this code will 
break restarts */
       if (options->priv_key_file_inline)
        {
@@ -2326,6 +2347,9 @@ do_init_crypto_tls (struct context *c, const unsigned int 
flags)
   to.replay_window = options->replay_window;
   to.replay_time = options->replay_time;
   to.tcp_mode = link_socket_proto_connection_oriented (options->ce.proto);
+  to.config_ciphername = c->c1.ciphername;
+  to.config_authname = c->c1.authname;
+  to.ncp_enabled = options->ncp_enabled;
   to.transition_window = options->transition_window;
   to.handshake_window = options->handshake_window;
   to.packet_timeout = options->tls_timeout;
diff --git a/src/openvpn/init.h b/src/openvpn/init.h
index a819bd2..524bc64 100644
--- a/src/openvpn/init.h
+++ b/src/openvpn/init.h
@@ -81,7 +81,7 @@ bool do_test_crypto (const struct options *o);

 void context_gc_free (struct context *c);

-void do_up (struct context *c,
+bool do_up (struct context *c,
            bool pulled_options,
            unsigned int option_types_found);

@@ -91,7 +91,7 @@ const char *format_common_name (struct context *c, struct 
gc_arena *gc);

 void reset_coarse_timers (struct context *c);

-void do_deferred_options (struct context *c, const unsigned int found);
+bool do_deferred_options (struct context *c, const unsigned int found);

 void inherit_context_child (struct context *dest,
                            const struct context *src);
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index 3281fd7..395195f 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -210,6 +210,9 @@ struct context_1
   struct user_pass *auth_user_pass;
                                 /**< Username and password for
                                  *   authentication. */
+
+  const char *ciphername;      /**< Data channel cipher from config file */
+  const char *authname;                /**< Data channel auth from config file 
*/
 #endif
 };

diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index a595dff..d17a2ce 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -523,6 +523,8 @@ static const char usage_message[] =
   "--cipher alg    : Encrypt packets with cipher algorithm alg\n"
   "                  (default=%s).\n"
   "                  Set alg=none to disable encryption.\n"
+  "--ncp-ciphers list : List of ciphers that are allowed to be negotiated.\n"
+  "--ncp-disable   : Disable cipher negotiation.\n"
   "--prng alg [nsl] : For PRNG, use digest algorithm alg, and\n"
   "                   nonce_secret_len=nsl.  Set alg=none to disable PRNG.\n"
 #ifdef HAVE_EVP_CIPHER_CTX_SET_KEY_LENGTH
@@ -830,6 +832,12 @@ init_options (struct options *o, const bool init_gc)
 #ifdef ENABLE_CRYPTO
   o->ciphername = "BF-CBC";
   o->ciphername_defined = true;
+#ifdef HAVE_AEAD_CIPHER_MODES /* IV_NCP=2 requires GCM support */
+  o->ncp_enabled = true;
+#else
+  o->ncp_enabled = false;
+#endif
+  o->ncp_ciphers = "AES-256-GCM:AES-128-GCM";
   o->authname = "SHA1";
   o->authname_defined = true;
   o->prng_hash = "SHA1";
@@ -6632,7 +6640,7 @@ add_option (struct options *options,
     }
   else if (streq (p[0], "auth") && p[1] && !p[2])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->authname_defined = true;
       options->authname = p[1];
       if (streq (options->authname, "none"))
@@ -6643,12 +6651,12 @@ add_option (struct options *options,
     }
   else if (streq (p[0], "auth") && !p[1])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->authname_defined = true;
     }
   else if (streq (p[0], "cipher") && p[1] && !p[2])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_NCP);
       options->ciphername_defined = true;
       options->ciphername = p[1];
       if (streq (options->ciphername, "none"))
@@ -6659,12 +6667,22 @@ add_option (struct options *options,
     }
   else if (streq (p[0], "cipher") && !p[1])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->ciphername_defined = true;
     }
+  else if (streq (p[0], "ncp-ciphers") && p[1] && !p[2])
+    {
+      VERIFY_PERMISSION (OPT_P_GENERAL);
+      options->ncp_ciphers = p[1];
+    }
+  else if (streq (p[0], "ncp-disable") && !p[1])
+    {
+      VERIFY_PERMISSION (OPT_P_GENERAL);
+      options->ncp_enabled = false;
+    }
   else if (streq (p[0], "prng") && p[1] && !p[3])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       if (streq (p[1], "none"))
        options->prng_hash = NULL;
       else
@@ -6686,12 +6704,12 @@ add_option (struct options *options,
     }
   else if (streq (p[0], "no-replay") && !p[1])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->replay = false;
     }
   else if (streq (p[0], "replay-window") && !p[3])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       if (p[1])
        {
          int replay_window;
@@ -6731,12 +6749,12 @@ add_option (struct options *options,
     }
   else if (streq (p[0], "mute-replay-warnings") && !p[1])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->mute_replay_warnings = true;
     }
   else if (streq (p[0], "no-iv") && !p[1])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->use_iv = false;
     }
   else if (streq (p[0], "replay-persist") && p[1] && !p[2])
@@ -6766,7 +6784,7 @@ add_option (struct options *options,
     {
       int keysize;

-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_NCP);
       keysize = atoi (p[1]) / 8;
       if (keysize < 0 || keysize > MAX_CIPHER_KEY_LENGTH)
        {
@@ -6795,7 +6813,7 @@ add_option (struct options *options,
     }
   else if (streq (p[0], "ecdh-curve") && p[1] && !p[2])
     {
-      VERIFY_PERMISSION (OPT_P_CRYPTO);
+      VERIFY_PERMISSION (OPT_P_GENERAL);
       options->ecdh_curve= p[1];
     }
   else if (streq (p[0], "tls-server") && !p[1])
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 514511b..ea63c4c 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -474,6 +474,8 @@ struct options
   int key_direction;
   bool ciphername_defined;
   const char *ciphername;
+  bool ncp_enabled;
+  const char *ncp_ciphers;
   bool authname_defined;
   const char *authname;
   int keysize;
@@ -618,7 +620,7 @@ struct options
 #define OPT_P_PERSIST_IP      (1<<9)
 #define OPT_P_COMP            (1<<10) /* TODO */
 #define OPT_P_MESSAGES        (1<<11)
-#define OPT_P_CRYPTO          (1<<12) /* TODO */
+#define OPT_P_NCP             (1<<12) /**< Negotiable crypto parameters */
 #define OPT_P_TLS_PARMS       (1<<13) /* TODO */
 #define OPT_P_MTU             (1<<14) /* TODO */
 #define OPT_P_NICE            (1<<15)
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 38ac59e..4239e3e 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -239,11 +239,20 @@ incoming_push_message (struct context *c, const struct 
buffer *buffer)
     {
       c->options.push_option_types_found |= option_types_found;

+      /* delay bringing tun/tap up until --push parms received from remote */
       if (status == PUSH_MSG_REPLY)
-       do_up (c, true, c->options.push_option_types_found ); /* delay bringing 
tun/tap up until --push parms received from remote */
+       {
+         if (!do_up (c, true, c->options.push_option_types_found))
+           {
+             msg (D_PUSH_ERRORS, "Failed to open tun/tap interface");
+             register_signal (c, SIGUSR1, "do_up-failed");
+             goto cleanup;
+           }
+       }
       event_timeout_clear (&c->c2.push_request_interval);
     }

+cleanup:
   gc_free (&gc);
 }

diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 0c0061c..7ced41d 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1636,6 +1636,24 @@ key_ctx_update_implicit_iv(struct key_ctx *ctx, uint8_t 
*key, size_t key_len) {
     }
 }

+static bool
+item_in_list(const char *item, const char *list)
+{
+  char *tmp_ciphers = string_alloc (list, NULL);
+  char *tmp_ciphers_orig = tmp_ciphers;
+
+  const char *token = strtok (tmp_ciphers, ":");
+  while(token)
+    {
+      if (0 == strcmp (token, item))
+       break;
+      token = strtok (NULL, ":");
+    }
+  free(tmp_ciphers_orig);
+
+  return token != NULL;
+}
+
 bool
 tls_session_update_crypto_params(struct tls_session *session,
     const struct options *options, struct frame *frame)
@@ -1646,6 +1664,15 @@ tls_session_update_crypto_params(struct tls_session 
*session,
   ASSERT (!session->opt->server);
   ASSERT (ks->authenticated);

+  if (0 != strcmp(options->ciphername, session->opt->config_ciphername) &&
+      !item_in_list(options->ciphername, options->ncp_ciphers))
+    {
+      msg (D_TLS_ERRORS, "Error: pushed cipher not allowed - %s not in %s or 
%s",
+         options->ciphername, session->opt->config_ciphername,
+         options->ncp_ciphers);
+      return false;
+    }
+
   init_key_type (&session->opt->key_type, options->ciphername,
     options->ciphername_defined, options->authname, options->authname_defined,
     options->keysize, true, true);
@@ -1927,7 +1954,8 @@ push_peer_info(struct buffer *buf, struct tls_session 
*session)
       buf_printf(&out, "IV_PROTO=2\n");

       /* support for Negotiable Crypto Paramters */
-      buf_printf(&out, "IV_NCP=2\n");
+      if (session->opt->ncp_enabled)
+       buf_printf(&out, "IV_NCP=2\n");

       /* push compression status */
 #ifdef USE_COMP
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index 9183dab..6bfde6b 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -273,6 +273,10 @@ struct tls_options
   int replay_time;                     /* --replay-window parm */
   bool tcp_mode;

+  const char *config_ciphername;
+  const char *config_authname;
+  bool ncp_enabled;
+
   /* packet authentication for TLS handshake */
   struct crypto_options tls_auth;

-- 
2.7.4


Reply via email to