@@ -22,6 +22,11 @@
For more details see [lwipovpn on Gihtub](https://github.com/OpenVPN/lwipovpn).
+Default ciphers in ``--data-ciphers``
+ Ciphers in ``--data-ciphers`` can contain the string DEFAULT that is
+ replaced by the default ciphers used by OpenVPN, making it easier to
+ add an allowed cipher without having to spell out the default ciphers.
+
Deprecated features
-------------------
``secret`` support has been removed by default.
@@ -178,6 +178,12 @@
Chacha20-Poly1305 if the underlying SSL library (and its configuration)
supports it.
+ Starting with OpenVPN 2.7 the special keyword DEFAULT can be used in the
+ string and is replaced by the default ciphers. This can be used add
+ an additional allowed cipher to the allowed ciphers, e.g.
+ :code:`DEFAULT:AES-192-CBC` to use the default ciphers but also allow
+ :code:`AES-192-CBC`.
+
Cipher negotiation is enabled in client-server mode only. I.e. if
``--mode`` is set to `server` (server-side, implied by setting
``--server`` ), or if ``--pull`` is specified (client-side, implied by
@@ -1896,14 +1896,15 @@
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);
+ "Server data-ciphers: '%s%s', client supported ciphers '%s'",
+ o->ncp_ciphers_conf, ncp_expanded_ciphers(o, &gc), 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);
+ "Server data-ciphers: '%s'%s, client supports cipher '%s'",
+ o->ncp_ciphers_conf, ncp_expanded_ciphers(o, &gc),
+ tls_multi->remote_ciphername);
}
else
{
@@ -3486,40 +3486,6 @@
}
}
-
-/**
- * Checks for availibility of Chacha20-Poly1305 and sets
- * the ncp_cipher to either AES-256-GCM:AES-128-GCM or
- * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305.
- */
-static void
-options_postprocess_setdefault_ncpciphers(struct options *o)
-{
- if (o->ncp_ciphers)
- {
- /* custom --data-ciphers set, keep list */
- return;
- }
-
- /* check if crypto library supports chacha */
- bool can_do_chacha = cipher_valid("CHACHA20-POLY1305");
-
- if (can_do_chacha && dco_enabled(o))
- {
- /* also make sure that dco supports chacha */
- can_do_chacha = tls_item_in_cipher_list("CHACHA20-POLY1305", dco_get_supported_ciphers());
- }
-
- if (can_do_chacha)
- {
- o->ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
- }
- else
- {
- o->ncp_ciphers = "AES-256-GCM:AES-128-GCM";
- }
-}
-
static void
options_postprocess_cipher(struct options *o)
{
@@ -3550,7 +3516,8 @@
"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.");
+ "and/or add BF-CBC to --data-ciphers. E.g. "
+ "--data-ciphers %s:BF-CBC", o->ncp_ciphers_conf);
}
else if (!o->enable_ncp_fallback
&& !tls_item_in_cipher_list(o->ciphername, o->ncp_ciphers))
@@ -3558,7 +3525,7 @@
msg(M_WARN, "DEPRECATED OPTION: --cipher set to '%s' but missing in "
"--data-ciphers (%s). OpenVPN ignores --cipher for cipher "
"negotiations. ",
- o->ciphername, o->ncp_ciphers);
+ o->ciphername, o->ncp_ciphers_conf);
}
}
@@ -559,6 +559,8 @@
const char *ciphername;
bool enable_ncp_fallback; /**< If defined fall back to
* ciphername if NCP fails */
+ /** The original ncp_ciphers specified by the user in the configuration*/
+ const char *ncp_ciphers_conf;
const char *ncp_ciphers;
const char *authname;
const char *engine;
@@ -39,6 +39,8 @@
#include "config.h"
#endif
+#include <string.h>
+
#include "syshead.h"
#include "win32.h"
@@ -222,7 +224,6 @@
return token != NULL;
}
-
const char *
tls_peer_ncp_list(const char *peer_info, struct gc_arena *gc)
{
@@ -334,15 +335,16 @@
return true;
}
- /* We failed negotiation, give appropiate error message */
+ /* We failed negotiation, give appropriate 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.",
+ "cipher ('%s') to --data-ciphers (currently '%s'), e.g."
+ "--data-ciphers %s:%s if you want to connect to this server.",
c->c2.tls_multi->remote_ciphername,
- c->options.ncp_ciphers);
+ c->options.ncp_ciphers_conf, c->options.ncp_ciphers_conf,
+ c->c2.tls_multi->remote_ciphername);
return false;
}
@@ -516,10 +518,13 @@
if (!session->opt->server && !cipher_allowed_as_fallback
&& !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))
{
- msg(D_TLS_ERRORS, "Error: negotiated cipher not allowed - %s not in %s",
- options->ciphername, options->ncp_ciphers);
+ struct gc_arena gc = gc_new();
+ msg(D_TLS_ERRORS, "Error: negotiated cipher not allowed - %s not in %s%s",
+ options->ciphername, options->ncp_ciphers_conf,
+ ncp_expanded_ciphers(options, &gc));
/* undo cipher push, abort connection setup */
options->ciphername = session->opt->config_ciphername;
+ gc_free(&gc);
return false;
}
else
@@ -527,3 +532,99 @@
return true;
}
}
+
+/**
+ * Replaces the string DEFAULT with the string \param replace. The
+ * @param o Options struct to modify and to use the gc from
+ * @param replace string used to replace the DEFAULT string
+ */
+static void
+replace_default_in_ncp_ciphers_option(struct options *o, const char *replace)
+{
+ const char *search = "DEFAULT";
+ const int ncp_ciphers_len = strlen(o->ncp_ciphers) + strlen(replace) - strlen(search) + 1;
+
+ uint8_t *ncp_ciphers = gc_malloc(ncp_ciphers_len, true, &o->gc);
+
+ struct buffer ncp_ciphers_buf;
+ buf_set_write(&ncp_ciphers_buf, ncp_ciphers, ncp_ciphers_len);
+
+ const char *def = strstr(o->ncp_ciphers, search);
+
+ /* Copy everything before the DEFAULT string */
+ buf_write(&ncp_ciphers_buf, o->ncp_ciphers, def - o->ncp_ciphers);
+
+ /* copy the default string. */
+ buf_write(&ncp_ciphers_buf, replace, strlen(replace));
+
+ /* copy the rest of the ncp cipher string */
+ const char *after_default = def + strlen(search);
+ buf_write(&ncp_ciphers_buf, after_default, strlen(after_default));
+
+ o->ncp_ciphers = (char *) ncp_ciphers;
+}
+
+/**
+ * Checks for availibility of Chacha20-Poly1305 and sets
+ * the ncp_cipher to either AES-256-GCM:AES-128-GCM or
+ * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305.
+ */
+void
+options_postprocess_setdefault_ncpciphers(struct options *o)
+{
+ bool default_in_cipher_list = o->ncp_ciphers
+ && tls_item_in_cipher_list("DEFAULT", o->ncp_ciphers);
+
+ /* preserve the values that the user put into the configuration */
+ o->ncp_ciphers_conf = o->ncp_ciphers;
+
+ /* check if crypto library supports chacha */
+ bool can_do_chacha = cipher_valid("CHACHA20-POLY1305");
+
+ if (can_do_chacha && dco_enabled(o))
+ {
+ /* also make sure that dco supports chacha */
+ can_do_chacha = tls_item_in_cipher_list("CHACHA20-POLY1305", dco_get_supported_ciphers());
+ }
+
+ const char *default_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
+
+ if (!can_do_chacha)
+ {
+ default_ciphers = "AES-256-GCM:AES-128-GCM";
+ }
+
+ /* want to rather print DEFAULT instead of a manually set default list */
+ if (!o->ncp_ciphers_conf || !strcmp(default_ciphers, o->ncp_ciphers_conf))
+ {
+ o->ncp_ciphers = default_ciphers;
+ o->ncp_ciphers_conf = "DEFAULT";
+ }
+ else if (!default_in_cipher_list)
+ {
+ /* custom cipher list without DEFAULT string in it,
+ * nothing to replace/mutate */
+ return;
+ }
+ else
+ {
+ replace_default_in_ncp_ciphers_option(o, default_ciphers);
+ }
+}
+
+const char *
+ncp_expanded_ciphers(struct options *o, struct gc_arena *gc)
+{
+ if (!strcmp(o->ncp_ciphers, o->ncp_ciphers_conf))
+ {
+ /* expanded ciphers and user set ciphers are identical, no need to
+ * add an expanded version */
+ return "";
+ }
+
+ /* two extra brackets, one space, NUL byte */
+ struct buffer expanded_ciphers_buf = alloc_buf_gc(strlen(o->ncp_ciphers) + 4, gc);
+
+ buf_printf(&expanded_ciphers_buf, " (%s)", o->ncp_ciphers);
+ return BSTR(&expanded_ciphers_buf);
+}
@@ -156,4 +156,24 @@
bool
check_session_cipher(struct tls_session *session, struct options *options);
+/**
+ * Checks for availability of Chacha20-Poly1305 and sets
+ * the ncp_cipher to either AES-256-GCM:AES-128-GCM or
+ * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 if not set.
+ *
+ * If DEFAULT is in the ncp_cipher string, it will be replaced
+ * by the default cipher string as defined above.
+ */
+void
+options_postprocess_setdefault_ncpciphers(struct options *o);
+
+/** returns the o->ncp_ciphers in brackets, e.g.
+ * (AES-256-GCM:CHACHA20-POLY1305) if o->ncp_ciphers_conf
+ * and o->ncp_ciphers differ, otherwise an empty string
+ *
+ * The returned string will be allocated in the passed \param gc
+ *
+ */
+const char *
+ncp_expanded_ciphers(struct options *o, struct gc_arena *gc);
#endif /* ifndef OPENVPN_SSL_NCP_H */
@@ -54,6 +54,17 @@
ASSERT(0);
}
+
+/* Define a dummy dco cipher option to avoid linking against all the DCO
+ * units */
+#if defined(ENABLE_DCO)
+const char *
+dco_get_supported_ciphers(void)
+{
+ return "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
+}
+#endif
+
static void
test_check_ncp_ciphers_list(void **state)
{
@@ -260,13 +271,128 @@
gc_free(&gc);
}
+static void
+test_ncp_default(void **state)
+{
+ bool have_chacha = cipher_valid("CHACHA20-POLY1305");
+
+ struct options o = { 0 };
+
+ o.gc = gc_new();
+
+ /* no user specified string */
+ o.ncp_ciphers = NULL;
+ options_postprocess_setdefault_ncpciphers(&o);
+
+ if (have_chacha)
+ {
+ assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
+ }
+ else
+ {
+ assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM");
+ }
+ assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
+
+ /* check that a default string is replaced with DEFAULT */
+ if (have_chacha)
+ {
+ o.ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
+ }
+ else
+ {
+ o.ncp_ciphers = "AES-256-GCM:AES-128-GCM";
+ }
+
+ options_postprocess_setdefault_ncpciphers(&o);
+ assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
+
+ /* test default in the middle of the string */
+ o.ncp_ciphers = "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC";
+ options_postprocess_setdefault_ncpciphers(&o);
+
+ if (have_chacha)
+ {
+ assert_string_equal(o.ncp_ciphers, "BF-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-256-CBC");
+ }
+ else
+ {
+ assert_string_equal(o.ncp_ciphers, "BF-CBC:AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-256-CBC");
+ }
+ assert_string_equal(o.ncp_ciphers_conf, "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC");
+
+ /* string at the beginning */
+ o.ncp_ciphers = "DEFAULT:AES-128-CBC:AES-192-CBC";
+ options_postprocess_setdefault_ncpciphers(&o);
+
+ if (have_chacha)
+ {
+ assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-192-CBC");
+ }
+ else
+ {
+ assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-192-CBC");
+ }
+ assert_string_equal(o.ncp_ciphers_conf, "DEFAULT:AES-128-CBC:AES-192-CBC");
+
+ /* DEFAULT at the end */
+ o.ncp_ciphers = "AES-192-GCM:AES-128-CBC:DEFAULT";
+ options_postprocess_setdefault_ncpciphers(&o);
+
+ if (have_chacha)
+ {
+ assert_string_equal(o.ncp_ciphers, "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
+ }
+ else
+ {
+ assert_string_equal(o.ncp_ciphers, "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM");
+ }
+ assert_string_equal(o.ncp_ciphers_conf, "AES-192-GCM:AES-128-CBC:DEFAULT");
+
+ gc_free(&o.gc);
+}
+
+static void
+test_ncp_expand(void **state)
+{
+ bool have_chacha = cipher_valid("CHACHA20-POLY1305");
+ struct options o = {0};
+
+ o.gc = gc_new();
+ struct gc_arena gc = gc_new();
+
+ /* no user specified string */
+ o.ncp_ciphers = NULL;
+ options_postprocess_setdefault_ncpciphers(&o);
+
+ const char *expanded = ncp_expanded_ciphers(&o, &gc);
+
+ if (have_chacha)
+ {
+ assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
+ }
+ else
+ {
+ assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM)");
+ }
+
+ o.ncp_ciphers = "AES-192-GCM:BF-CBC";
+ options_postprocess_setdefault_ncpciphers(&o);
+
+ assert_string_equal(ncp_expanded_ciphers(&o, &gc), "");
+
+ gc_free(&o.gc);
+ gc_free(&gc);
+}
const struct CMUnitTest ncp_tests[] = {
cmocka_unit_test(test_check_ncp_ciphers_list),
cmocka_unit_test(test_extract_client_ciphers),
cmocka_unit_test(test_poor_man),
- cmocka_unit_test(test_ncp_best)
+ cmocka_unit_test(test_ncp_best),
+ cmocka_unit_test(test_ncp_default),
+ cmocka_unit_test(test_ncp_expand),
};