diff --git a/doc/man-sections/protocol-options.rst
b/doc/man-sections/protocol-options.rst
index 051f1d32..69d3bc67 100644
--- a/doc/man-sections/protocol-options.rst
+++ b/doc/man-sections/protocol-options.rst
@@ -57,6 +57,9 @@ configured in a compatible way between both the local and
remote side.
http://www.cs.ucsd.edu/users/mihir/papers/hmac.html
--cipher alg
+ This option is deprecated for server-client mode and ``--data-ciphers``
+ or rarely `--data-ciphers-fallback`` should be used instead.
+
Encrypt data channel packets with cipher algorithm ``alg``.
The default is :code:`BF-CBC`, an abbreviation for Blowfish in Cipher
@@ -183,8 +186,9 @@ configured in a compatible way between both the local and
remote side.
``--server`` ), or if ``--pull`` is specified (client-side, implied by
setting --client).
- If both peers support and do not disable NCP, the negotiated cipher will
- override the cipher specified by ``--cipher``.
+ If no common cipher is found during cipher negotiation, the connection
+ is terminated. To support old clients/server that do not provide any cipher
+ negotiation support see ``data-ciphers-fallback``.
Additionally, to allow for more smooth transition, if NCP is enabled,
OpenVPN will inherit the cipher of the peer if that cipher is different
@@ -201,8 +205,18 @@ configured in a compatible way between both the local and
remote side.
This list is restricted to be 127 chars long after conversion to OpenVPN
ciphers.
- This option was called ``ncp-ciphers`` in OpenVPN 2.4 but has been renamed
- to ``data-ciphers`` in OpenVPN 2.5 to more accurately reflect its meaning.
+ This option was called ``--ncp-ciphers`` in OpenVPN 2.4 but has been renamed
+ to ``--data-ciphers`` in OpenVPN 2.5 to more accurately reflect its meaning.
+
+--data-ciphers-fallback alg
+
+ Configure a cipher that is used to fall back to if we could not determine
+ which cipher the peer is willing to use.
+
+ This option should only be needed to
+ connect to peers that are running OpenVPN 2.3 and older version, and
+ have been configured with `--enable-small`
+ (typically used on routers or other embedded devices).
--ncp-disable
Disable "Negotiable Crypto Parameters". This completely disables cipher
diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index e92a0dc1..3a0bfbec 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -727,7 +727,9 @@ warn_insecure_key_type(const char *ciphername, const
cipher_kt_t *cipher)
{
msg(M_WARN, "WARNING: INSECURE cipher (%s) with block size less than
128"
" bit (%d bit). This allows attacks like SWEET32. Mitigate by "
- "using a --cipher with a larger block size (e.g. AES-256-CBC).",
+ "using a --cipher with a larger block size (e.g. AES-256-CBC). "
+ "Support for these insecure ciphers will be removed in "
+ "OpenVPN 2.6.",
ciphername, cipher_kt_block_size(cipher)*8);
}
}
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 1ea4735d..402d2652 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -2365,16 +2365,9 @@ do_deferred_options(struct context *c, const unsigned
int found)
/* process (potentially pushed) crypto options */
if (c->options.pull)
{
- struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
- if (found & OPT_P_NCP)
- {
- msg(D_PUSH, "OPTIONS IMPORT: data channel crypto options
modified");
- }
- else if (c->options.ncp_enabled)
+ if (!check_pull_client_ncp(c, found))
{
- /* If the server did not push a --cipher, we will switch to the
- * remote cipher if it is in our ncp-ciphers list */
- tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername);
+ return false;
}
struct frame *frame_fragment = NULL;
#ifdef ENABLE_FRAGMENT
@@ -2384,6 +2377,7 @@ do_deferred_options(struct context *c, const unsigned int
found)
}
#endif
+ struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
if (!tls_session_update_crypto_params(session, &c->options,
&c->c2.frame,
frame_fragment))
{
@@ -2757,9 +2751,13 @@ do_init_crypto_tls_c1(struct context *c)
#endif /* if P2MP */
}
+ /* Do not warn if only have BF-CBC in options->ciphername
+ * because it is still the default cipher */
+ bool warn = !streq(options->ciphername, "BF-CBC")
+ || options->enable_ncp_fallback;
/* Get cipher & hash algorithms */
init_key_type(&c->c1.ks.key_type, options->ciphername,
options->authname,
- options->keysize, true, true);
+ options->keysize, true, warn);
/* Initialize PRNG with config-specified digest */
prng_init(options->prng_hash, options->prng_nonce_secret_len);
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 0f9c586b..79b5c0c3 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -1777,7 +1777,7 @@ multi_client_connect_setenv(struct multi_context *m,
* - choosen cipher
* - peer id
*/
-static void
+static bool
multi_client_set_protocol_options(struct context *c)
{
@@ -1807,56 +1807,85 @@ multi_client_set_protocol_options(struct context *c)
}
/* Select cipher if client supports Negotiable Crypto Parameters */
- if (o->ncp_enabled)
+ if (!o->ncp_enabled)
{
- /* if we have already created our key, we cannot *change* our own
- * cipher -> so log the fact and push the "what we have now" cipher
- * (so the client is always told what we expect it to use)
- */
- const struct tls_session *session = &tls_multi->session[TM_ACTIVE];
- if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)
+ return true;
+ }
+
+ /* if we have already created our key, we cannot *change* our own
+ * cipher -> so log the fact and push the "what we have now" cipher
+ * (so the client is always told what we expect it to use)
+ */
+ const struct tls_session *session = &tls_multi->session[TM_ACTIVE];
+ if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)
+ {
+ msg( M_INFO, "PUSH: client wants to negotiate cipher (NCP), but "
No space after ( .
+ "server has already generated data channel keys, "
+ "re-sending previously negotiated cipher '%s'",
+ o->ciphername );
+ return true;
+ }
+
+ /*
+ * Push the first cipher from --data-ciphers to the client that
+ * the client announces to be supporting.
+ */
+ char *push_cipher = ncp_get_best_cipher(o->ncp_ciphers, peer_info,
+ tls_multi->remote_ciphername,
+ &o->gc);
+
+ if (push_cipher)
+ {
+ o->ciphername = push_cipher;
+ return true;
+ }
+
+ /* NCP cipher negotiation failed. Try to figure out why exactly it
+ * failed and give good error messages and potentially do a fallback
+ * for non NCP clients */
+ struct gc_arena gc = gc_new();
+ bool ret = false;
+
+ const char *peer_ciphers = tls_peer_ncp_list(peer_info, &gc);
+ /* If we are in a situation where we know the client ciphers, there is no
+ * reason to fall back to a cipher that will not be accepted by the other
+ * side, in this situation we fail the auth*/
+ if (strlen(peer_ciphers) > 0)
+ {
+ msg(M_INFO, "PUSH: No common cipher between server and client. "
+ "Server data-ciphers: '%s', client supported ciphers '%s'",
+ o->ncp_ciphers, peer_ciphers);
+ }
+ else if (tls_multi->remote_ciphername)
+ {
+ msg(M_INFO, "PUSH: No common cipher between server and client. "
+ "Server data-ciphers: '%s', client supports cipher '%s'",
+ o->ncp_ciphers, tls_multi->remote_ciphername);
+ }
+ else
+ {
+ msg(M_INFO, "PUSH: No NCP or OCC cipher data received from peer.");
+
+ if (o->enable_ncp_fallback && !tls_multi->remote_ciphername)
{
- msg( M_INFO, "PUSH: client wants to negotiate cipher (NCP), but "
- "server has already generated data channel keys, "
- "re-sending previously negotiated cipher '%s'",
- o->ciphername );
+ msg(M_INFO, "Using data channel cipher '%s' since "
+ "--data-ciphers-fallback is set.", o->ciphername);
+ ret = true;
}
else
{
- /*
- * Push the first cipher from --data-ciphers to the client that
- * the client announces to be supporting.
- */
- char *push_cipher = ncp_get_best_cipher(o->ncp_ciphers,
o->ciphername,
- peer_info,
-
tls_multi->remote_ciphername,
- &o->gc);
-
- if (push_cipher)
- {
- o->ciphername = push_cipher;
- }
- else
- {
- struct gc_arena gc = gc_new();
- const char *peer_ciphers = tls_peer_ncp_list(peer_info, &gc);
- if (strlen(peer_ciphers) > 0)
- {
- msg(M_INFO, "PUSH: No common cipher between server and "
- "client. Expect this connection not to work. Server "
- "data-ciphers: '%s', client supported ciphers '%s'",
- o->ncp_ciphers, peer_ciphers);
- }
- else
- {
- msg(M_INFO, "No NCP data received from peer, falling back "
- "to --cipher '%s'. Peer reports in OCC --cipher '%s'",
- o->ciphername, np(tls_multi->remote_ciphername));
- }
- gc_free(&gc);
- }
+ msg(M_INFO, "Use --data-ciphers-fallback with the cipher the "
+ "client is using if you want to allow the client to connect");
}
}
+ if (!ret)
+ {
+ auth_set_client_reason(tls_multi, "Data channel cipher negotiation "
+ "failed (no shared cipher)");
+ }
+
+ gc_free(&gc);
+ return ret;
}
/**
@@ -2322,7 +2351,7 @@ multi_client_connect_late_setup(struct multi_context *m,
if (!mi->context.c2.push_ifconfig_defined)
{
msg(D_MULTI_ERRORS, "MULTI: no dynamic or static remote"
- "--ifconfig address is available for %s",
+ "--ifconfig address is available for %s",
multi_instance_string(mi, false, &gc));
}
@@ -2338,7 +2367,7 @@ multi_client_connect_late_setup(struct multi_context *m,
/* JYFIXME -- this should cause the connection to fail */
msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s)"
- "violates tunnel network/netmask constraint (%s/%s)",
+ "violates tunnel network/netmask constraint
(%s/%s)",
multi_instance_string(mi, false, &gc),
print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
ifconfig_constraint_network, ifconfig_constraint_netmask);
@@ -2387,7 +2416,7 @@ multi_client_connect_late_setup(struct multi_context *m,
else if (mi->context.options.iroutes)
{
msg(D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s --
iroute"
- "only works with tun-style tunnels",
+ "only works with tun-style tunnels",
multi_instance_string(mi, false, &gc));
}
@@ -2399,11 +2428,15 @@ multi_client_connect_late_setup(struct multi_context *m,
mi->context.c2.context_auth = CAS_SUCCEEDED;
/* authentication complete, calculate dynamic client specific options */
- multi_client_set_protocol_options(&mi->context);
-
- /* Generate data channel keys */
- if (!multi_client_generate_tls_keys(&mi->context))
+ if (!multi_client_set_protocol_options(&mi->context))
+ {
+ mi->context.c2.context_auth = CAS_FAILED;
+ }
+ /* Generate data channel keys only if setting protocol options
+ * has not failed */
+ else if (!multi_client_generate_tls_keys(&mi->context))
{
+
mi->context.c2.context_auth = CAS_FAILED;
}
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index bc256b18..c53ef7f9 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -855,7 +855,6 @@ init_options(struct options *o, const bool init_gc)
#if P2MP
o->scheduled_exit_interval = 5;
#endif
- o->ciphername = "BF-CBC";
o->ncp_enabled = true;
o->ncp_ciphers = "AES-256-GCM:AES-128-GCM";
o->authname = "SHA1";
@@ -2053,7 +2052,7 @@ options_postprocess_verify_ce(const struct options
*options, const struct connec
if (options->inetd)
{
msg(M_WARN, "DEPRECATED OPTION: --inetd mode is deprecated "
- "and will be removed in OpenVPN 2.6");
+ "and will be removed in OpenVPN 2.6");
}
if (options->lladdr && dev != DEV_TYPE_TAP)
@@ -3046,6 +3045,67 @@ options_postprocess_verify(const struct options *o)
}
}
+static void
+options_postprocess_cipher(struct options *o)
+{
+ if (!o->pull && !(o->mode == MODE_SERVER))
+ {
+ /* we are in the classic P2P mode */
+ o->ncp_enabled = false;
+ msg( M_WARN, "Cipher negotiation is disabled since neither "
+ "P2MP client nor server mode is enabled");
+
+ /* If the cipher is not set, use the old default ofo BF-CBC. We will
+ * warn that this is deprecated on cipher initialisation, no need
+ * to warn here as well */
+ if (!o->ciphername)
+ {
+ o->ciphername = "BF-CBC";
+ }
+ return;
+ }
+
+ /* pull or P2MP mode */
+ if (!o->ciphername)
+ {
+ if (!o->ncp_enabled)
+ {
+ msg(M_USAGE, "--ncp-disable needs an explicit --cipher or "
+ "--data-ciphers-fallback config option");
+ }
+
+ msg(M_WARN, "--cipher is not set. Previous OpenVPN version defaulted to
"
+ "BF-CBC as fallback when cipher negotiation failed in this case. "
+ "If you need this fallback please add '--data-ciphers-fallback "
+ "BF-CBC' to your configuration and/or add BF-CBC to "
+ "--data-ciphers.");
+
+ /* We still need to set the ciphername to BF-CBC since various other
+ * parts of OpenVPN assert that the ciphername is set */
+ o->ciphername = "BF-CBC";
+ }
+ else if (!o->enable_ncp_fallback
+ && !tls_item_in_cipher_list(o->ciphername, o->ncp_ciphers))
+ {
+ msg(M_WARN, "DEPRECATED OPTION: --cipher set to '%s' but missing in"
+ " --data-ciphers (%s). Future OpenVPN version will "
+ "ignore --cipher for cipher negotiations. "
+ "Add '%s' to --data-ciphers or change --cipher '%s' to "
+ "--data-ciphers-fallback '%s' to silence this warning.",
+ o->ciphername, o->ncp_ciphers, o->ciphername,
+ o->ciphername, o->ciphername);
+ o->enable_ncp_fallback = true;
+
+ /* Append the --cipher to ncp_ciphers to allow it in NCP */
+ size_t newlen = strlen(o->ncp_ciphers) + 1 + strlen(o->ciphername) +1;
Missing space after the last + .
+ char *ncp_ciphers = gc_malloc(newlen, false, &o->gc);
+
+ ASSERT(openvpn_snprintf(ncp_ciphers, newlen, "%s:%s", o->ncp_ciphers,
+ o->ciphername));
+ o->ncp_ciphers = ncp_ciphers;
+ }
+}
+
static void
options_postprocess_mutate(struct options *o)
{
@@ -3058,6 +3118,7 @@ options_postprocess_mutate(struct options *o)
helper_keepalive(o);
helper_tcp_nodelay(o);
+ options_postprocess_cipher(o);
options_postprocess_mutate_invariant(o);
if (o->ncp_enabled)
@@ -3118,16 +3179,6 @@ options_postprocess_mutate(struct options *o)
"include this in your server configuration");
o->dh_file = NULL;
}
-
- /* cipher negotiation (NCP) currently assumes --pull or --mode server */
- if (o->ncp_enabled
- && !(o->pull || o->mode == MODE_SERVER) )
- {
- msg( M_WARN, "disabling NCP mode (--ncp-disable) because not "
- "in P2MP client or server mode" );
- o->ncp_enabled = false;
- }
-
#if ENABLE_MANAGEMENT
if (o->http_proxy_override)
{
@@ -3663,14 +3714,21 @@ 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 %u", (unsigned int)
calc_options_string_link_mtu(o, frame));
+ /* the link-mtu that we send has only a meaning if have a fixed
+ * cipher (p2p) or have a fallback cipher configured for older non
+ * ncp clients. But not sending it, will make even 2.4 complain
+ * about it missing. So still send it. */
+ buf_printf(&out, ",link-mtu %u",
+ (unsigned int) 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));
+ bool p2p_nopull = o->mode == MODE_POINT_TO_POINT && !PULL_DEFINED(o);
/* send tun_ipv6 only in peer2peer mode - in client/server mode, it
* is usually pushed by the server, triggering a non-helpful warning
*/
- if (o->ifconfig_ipv6_local && o->mode == MODE_POINT_TO_POINT &&
!PULL_DEFINED(o))
+ if (o->ifconfig_ipv6_local && p2p_nopull)
{
buf_printf(&out, ",tun-ipv6");
}
@@ -3700,7 +3758,7 @@ options_string(const struct options *o,
}
}
- if (tt && o->mode == MODE_POINT_TO_POINT && !PULL_DEFINED(o))
+ if (tt && p2p_nopull)
{
const char *ios = ifconfig_options_string(tt, remote,
o->ifconfig_nowarn, gc);
if (ios && strlen(ios))
@@ -3756,8 +3814,14 @@ options_string(const struct options *o,
init_key_type(&kt, o->ciphername, o->authname, o->keysize, true,
false);
-
- buf_printf(&out, ",cipher %s", cipher_kt_name(kt.cipher));
+ /* Only announce the cipher to our peer if we are willing to
+ * support it */
+ const char *ciphername = cipher_kt_name(kt.cipher);
+ if (p2p_nopull || !o->ncp_enabled
+ || tls_item_in_cipher_list(ciphername, o->ncp_ciphers))
+ {
+ buf_printf(&out, ",cipher %s", ciphername);
+ }
buf_printf(&out, ",auth %s", md_kt_name(kt.digest));
buf_printf(&out, ",keysize %d", kt.cipher_length * 8);
if (o->shared_secret_file)
@@ -3875,7 +3939,8 @@ options_warning_safe_scan2(const int msglevel,
|| strprefix(p1, "keydir ")
|| strprefix(p1, "proto ")
|| strprefix(p1, "tls-auth ")
- || strprefix(p1, "tun-ipv6"))
+ || strprefix(p1, "tun-ipv6")
+ || strprefix(p1, "cipher "))
{
return;
}
@@ -7863,14 +7928,20 @@ add_option(struct options *options,
VERIFY_PERMISSION(OPT_P_NCP|OPT_P_INSTANCE);
options->ciphername = p[1];
}
+ else if (streq(p[0], "data-ciphers-fallback") && p[1] && !p[2])
+ {
+ VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
+ options->ciphername = p[1];
+ options->enable_ncp_fallback = true;
+ }
else if ((streq(p[0], "data-ciphers") || streq(p[0], "ncp-ciphers"))
- && p[1] && !p[2])
+ && p[1] && !p[2])
{
VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
if (streq(p[0], "ncp-ciphers"))
{
msg(M_INFO, "Note: Treating option '--ncp-ciphers' as "
- " '--data-ciphers' (renamed in OpenVPN 2.5).");
+ " '--data-ciphers' (renamed in OpenVPN 2.5).");
}
options->ncp_ciphers = p[1];
}
@@ -7878,9 +7949,9 @@ add_option(struct options *options,
{
VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
options->ncp_enabled = false;
- msg(M_WARN, "DEPRECATED OPTION: ncp-disable. Disabling dynamic "
- "cipher negotiation is a deprecated debug feature that "
- "will be removed in OpenVPN 2.6");
+ msg(M_WARN, "DEPRECATED OPTION: ncp-disable. Disabling "
+ "cipher negotiation is a deprecated debug feature that "
+ "will be removed in OpenVPN 2.6");
}
else if (streq(p[0], "prng") && p[1] && !p[3])
{
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index c5df2d18..877e9396 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -503,6 +503,8 @@ struct options
bool shared_secret_file_inline;
int key_direction;
const char *ciphername;
+ bool enable_ncp_fallback; /**< If defined fall back to
+ * ciphername if NCP fails */
bool ncp_enabled;
const char *ncp_ciphers;
const char *authname;
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 91ab3bf6..06dc9f8f 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1932,13 +1932,14 @@ tls_session_update_crypto_params(struct tls_session
*session,
return true;
}
- if (!session->opt->server
- && 0 != strcmp(options->ciphername, session->opt->config_ciphername)
+ bool cipher_allowed_as_fallback = options->enable_ncp_fallback
+ && streq(options->ciphername,
session->opt->config_ciphername);
+
+ if (!session->opt->server && !cipher_allowed_as_fallback
&& !tls_item_in_cipher_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);
+ msg(D_TLS_ERRORS, "Error: pushed cipher not allowed - %s not in %s",
+ options->ciphername, options->ncp_ciphers);
/* undo cipher push, abort connection setup */
options->ciphername = session->opt->config_ciphername;
return false;
@@ -1956,9 +1957,9 @@ tls_session_update_crypto_params(struct tls_session
*session,
}
else
{
- /* Very hacky workaround and quick fix for frame calculation
- * different when adjusting frame size when the original and new cipher
- * are identical to avoid a regression with client without NCP */
+ /* Very hacky workaround and quick fix for frame calculation
+ * different when adjusting frame size when the original and new cipher
+ * are identical to avoid a regression with client without NCP */
return tls_session_generate_data_channel_keys(session);
}
diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c
index 8e432fb7..2d3983c2 100644
--- a/src/openvpn/ssl_ncp.c
+++ b/src/openvpn/ssl_ncp.c
@@ -48,6 +48,7 @@
#include "common.h"
#include "ssl_ncp.h"
+#include "openvpn.h"
/**
* Return the Negotiable Crypto Parameters version advertised in the peer info
@@ -211,9 +212,8 @@ tls_peer_ncp_list(const char *peer_info, struct gc_arena
*gc)
}
char *
-ncp_get_best_cipher(const char *server_list, const char *server_cipher,
- const char *peer_info, const char *remote_cipher,
- struct gc_arena *gc)
+ncp_get_best_cipher(const char *server_list, const char *peer_info,
+ const char *remote_cipher, struct gc_arena *gc)
{
/*
* The gc of the parameter is tied to the VPN session, create a
@@ -226,7 +226,9 @@ ncp_get_best_cipher(const char *server_list, const char
*server_cipher,
const char *peer_ncp_list = tls_peer_ncp_list(peer_info, &gc_tmp);
/* non-NCP client without OCC? "assume nothing" */
- if (remote_cipher == NULL)
+ /* For client doing the newer version of NCP (that send IV_CIPHER)
+ * we cannot assume that they will accept remote_cipher */
+ if (remote_cipher == NULL || strstr(peer_info, "IV_CIPHERS="))
Just noting the missing NULL check that Gert found with testing. Can you
add a regression test while at it?
{
remote_cipher = "";
}
@@ -242,15 +244,6 @@ ncp_get_best_cipher(const char *server_list, const char
*server_cipher,
break;
}
}
- /* We have not found a common cipher, as a last resort check if the
- * server cipher can be used
- */
- if (token == NULL
- && (tls_item_in_cipher_list(server_cipher, peer_ncp_list)
- || streq(server_cipher, remote_cipher)))
- {
- token = server_cipher;
- }
char *ret = NULL;
if (token != NULL)
@@ -262,16 +255,75 @@ ncp_get_best_cipher(const char *server_list, const char
*server_cipher,
return ret;
}
-void
+/**
+ * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.
+ * Allows non-NCP peers to upgrade their cipher individually.
+ *
+ * Returns true if we switched to the peer's cipher
+ *
+ * Make sure to call tls_session_update_crypto_params() after calling this
+ * function.
+ */
+static bool
tls_poor_mans_ncp(struct options *o, const char *remote_ciphername)
{
- if (o->ncp_enabled && remote_ciphername
+ if (remote_ciphername
&& 0 != strcmp(o->ciphername, remote_ciphername))
{
if (tls_item_in_cipher_list(remote_ciphername, o->ncp_ciphers))
{
o->ciphername = string_alloc(remote_ciphername, &o->gc);
msg(D_TLS_DEBUG_LOW, "Using peer cipher '%s'", o->ciphername);
+ return true;
}
}
+ return false;
}
+
+bool
+check_pull_client_ncp(struct context *c, const int found)
+{
+ if (found & OPT_P_NCP)
+ {
+ msg(D_PUSH, "OPTIONS IMPORT: data channel crypto options modified");
+ return true;
+ }
+
+ if (!c->options.ncp_enabled)
+ {
+ return true;
+ }
+ /* If the server did not push a --cipher, we will switch to the
+ * remote cipher if it is in our ncp-ciphers list */
+ bool useremotecipher = tls_poor_mans_ncp(&c->options,
+
c->c2.tls_multi->remote_ciphername);
+
+
+ /* We could not figure out the peer's cipher but we have fallback
+ * enable */
enableD.
+ if (!useremotecipher && c->options.enable_ncp_fallback)
+ {
+ return true;
+ }
+
+ /* We failed negotiation, give appropiate error message */
+ if (c->c2.tls_multi->remote_ciphername)
+ {
+ msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to negotiate "
+ "cipher with server. Add the server's "
+ "cipher ('%s') to --data-ciphers (currently '%s') if "
+ "you want to connect to this server.",
+ c->c2.tls_multi->remote_ciphername,
+ c->options.ncp_ciphers);
+ return false;
+
+ }
+ else
+ {
+ msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to negotiate "
+ "cipher with server. Configure "
+ "--data-ciphers-fallback if you want to connect "
+ "to this server.");
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/openvpn/ssl_ncp.h b/src/openvpn/ssl_ncp.h
index d00c222d..39158a56 100644
--- a/src/openvpn/ssl_ncp.h
+++ b/src/openvpn/ssl_ncp.h
@@ -40,14 +40,17 @@
bool
tls_peer_supports_ncp(const char *peer_info);
+/* forward declaration to break include dependency loop */
+struct context;
+
/**
- * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.
- * Allows non-NCP peers to upgrade their cipher individually.
+ * Checks whether the cipher negotiation is in an acceptable state
+ * and we continue to connect or should abort.
*
- * Make sure to call tls_session_update_crypto_params() after calling this
- * function.
+ * @return Wether the client NCP process suceeded or failed
*/
-void tls_poor_mans_ncp(struct options *o, const char *remote_ciphername);
+bool
+check_pull_client_ncp(struct context *c, int found);
/**
* Iterates through the ciphers in server_list and return the first
@@ -67,9 +70,8 @@ void tls_poor_mans_ncp(struct options *o, const char
*remote_ciphername);
* cipher
*/
char *
-ncp_get_best_cipher(const char *server_list, const char *server_cipher,
- const char *peer_info, const char *remote_cipher,
- struct gc_arena *gc);
+ncp_get_best_cipher(const char *server_list, const char *peer_info,
+ const char *remote_cipher, struct gc_arena *gc);
/**
diff --git a/tests/unit_tests/openvpn/test_ncp.c
b/tests/unit_tests/openvpn/test_ncp.c
index 19432410..ea950030 100644
--- a/tests/unit_tests/openvpn/test_ncp.c
+++ b/tests/unit_tests/openvpn/test_ncp.c
@@ -139,21 +139,29 @@ test_poor_man(void **state)
char *best_cipher;
const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM";
+ const char *serverlistbfcbc = "CHACHA20_POLY1305:AES-128-GCM:BF-CBC";
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
+ best_cipher = ncp_get_best_cipher(serverlist,
+ "IV_YOLO=NO\nIV_BAR=7",
+ "BF-CBC", &gc);
+
+ assert_ptr_equal(best_cipher, NULL);
+
+
+ best_cipher = ncp_get_best_cipher(serverlistbfcbc,
"IV_YOLO=NO\nIV_BAR=7",
"BF-CBC", &gc);
assert_string_equal(best_cipher, "BF-CBC");
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
+
+ best_cipher = ncp_get_best_cipher(serverlist,
"IV_NCP=1\nIV_BAR=7",
"AES-128-GCM", &gc);
assert_string_equal(best_cipher, "AES-128-GCM");
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
- NULL,
+ best_cipher = ncp_get_best_cipher(serverlist, NULL,
"AES-128-GCM", &gc);
assert_string_equal(best_cipher, "AES-128-GCM");
@@ -170,29 +178,27 @@ test_ncp_best(void **state)
const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM:AES-256-GCM";
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
+ best_cipher = ncp_get_best_cipher(serverlist,
"IV_YOLO=NO\nIV_NCP=2\nIV_BAR=7",
"BF-CBC", &gc);
assert_string_equal(best_cipher, "AES-128-GCM");
/* Best cipher is in --cipher of client */
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
- "IV_NCP=2\nIV_BAR=7",
+ best_cipher = ncp_get_best_cipher(serverlist, "IV_NCP=2\nIV_BAR=7",
"CHACHA20_POLY1305", &gc);
assert_string_equal(best_cipher, "CHACHA20_POLY1305");
/* Best cipher is in --cipher of client */
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
- "IV_CIPHERS=AES-128-GCM",
+ best_cipher = ncp_get_best_cipher(serverlist, "IV_CIPHERS=AES-128-GCM",
"AES-256-CBC", &gc);
assert_string_equal(best_cipher, "AES-128-GCM");
/* IV_NCP=2 should be ignored if IV_CIPHERS is sent */
- best_cipher = ncp_get_best_cipher(serverlist, "BF-CBC",
+ best_cipher = ncp_get_best_cipher(serverlist,
"IV_FOO=7\nIV_CIPHERS=AES-256-GCM\nIV_NCP=2",
"AES-256-CBC", &gc);
I still try to find time to do more review and testing, but don't wait
for me if someone else has taken a good look and/or given this patch a
good beating.
-Steffan
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel