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>
... | ... |
@@ -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. |