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 <stef...@karger.me> --- v2: remove unused argument from options_string_extract_option() v3: don't do poor-man's NCP if NCP is disabled v4: use strncmp instead of strstr to match prefix in options_string_extract_option, add man page text and make clear that this feature works both client-to-server and server-to-client. v5: move poor-mans-ncp logic from key_method_2_read() to the place where we do regular NCP too. This makes that we no longer need access to the struct context in key_method_2_read, and is cleaner in general, and makes the patch a bit smaller. v6: don't break p2mp-to-no-pull-client setups doc/openvpn.8 | 14 ++++++++++++-- src/openvpn/init.c | 10 ++++++++-- src/openvpn/options.c | 32 ++++++++++++++++++++++++++++++++ src/openvpn/options.h | 14 ++++++++++++++ src/openvpn/push.c | 6 +++++- src/openvpn/ssl.c | 36 ++++++++++++++++++++++++++++++------ src/openvpn/ssl.h | 16 ++++++++++++++++ src/openvpn/ssl_common.h | 2 ++ 8 files changed, 119 insertions(+), 11 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 171bd72..4356597 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -4147,9 +4147,9 @@ to disable encryption. As of OpenVPN 2.4, cipher negotiation (NCP) can override the cipher specified by .B \-\-cipher\fR. See -.B \-\-ncp-ciphers +.B \-\-ncp\-ciphers and -.B \-\-ncp-disable +.B \-\-ncp\-disable for more on NCP. .\"********************************************************* @@ -4177,6 +4177,16 @@ If both peers support and do not disable NCP, the negotiated cipher will override the cipher specified by .B \-\-cipher\fR. +Additionally, to allow for more smooth transition, if NCP is enabled, OpenVPN +will inherit the cipher of the peer if that cipher is different from the local +.B \-\-cipher +setting, but the peer cipher is one of the ciphers specified in +.B \-\-ncp\-ciphers\fR. +E.g. a non-NCP client (<=2.3, or with \-\-ncp\-disabled set) connecting to a +NCP server (2.4+) with "\-\-cipher BF-CBC" and "\-\-ncp-ciphers +AES-256-GCM:AES-256-CBC" set can either specify "\-\-cipher BF-CBC" or +"\-\-cipher AES-256-CBC" and both will work. + .\"********************************************************* .TP .B \-\-ncp\-disable diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 2ed633c..03302a6 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1925,8 +1925,14 @@ do_deferred_options (struct context *c, const unsigned int found) { 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"); - /* Do not regenerate keys if server sends an extra push request */ + { + msg (D_PUSH, "OPTIONS IMPORT: data channel crypto options modified"); + } + else if (c->options.ncp_enabled) + { + tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername); + } + /* Do not regenerate keys if server sends an extra push reply */ if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized && !tls_session_update_crypto_params(session, &c->options, &c->c2.frame)) { diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 102b4d0..7d042a8 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1643,6 +1643,8 @@ show_settings (const struct options *o) SHOW_STR (shared_secret_file); SHOW_INT (key_direction); SHOW_STR (ciphername); + SHOW_BOOL (ncp_enabled); + SHOW_STR (ncp_ciphers); SHOW_STR (authname); SHOW_STR (prng_hash); SHOW_INT (prng_nonce_secret_len); @@ -3434,6 +3436,36 @@ options_string_version (const char* s, struct gc_arena *gc) #endif /* ENABLE_OCC */ +char * +options_string_extract_option (const char *options_string,const char *opt_name, + struct gc_arena *gc) +{ + char *ret = NULL; + const size_t opt_name_len = strlen(opt_name); + + const char *p = options_string; + while (p) + { + if (0 == strncmp(p, opt_name, opt_name_len) && + strlen(p) > (opt_name_len+1) && p[opt_name_len] == ' ') + { + /* option found, extract value */ + const char *start = &p[opt_name_len+1]; + const char *end = strchr (p, ','); + size_t val_len = end ? end - start : strlen (start); + ret = gc_malloc (val_len+1, true, gc); + memcpy (ret, start, val_len); + break; + } + p = strchr (p, ','); + if (p) + { + p++; /* skip delimiter */ + } + } + return ret; +} + static void foreign_option (struct options *o, char *argv[], int len, struct env_set *es) { diff --git a/src/openvpn/options.h b/src/openvpn/options.h index a028556..913bfde 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -724,6 +724,20 @@ void options_warning_safe (char *actual, const char *expected, size_t actual_n); bool options_cmp_equal (char *actual, const char *expected); void options_warning (char *actual, const char *expected); +/** + * Given an OpenVPN options string, extract the value of an option. + * + * @param options_string Zero-terminated, comma-separated options string + * @param opt_name The name of the option to extract + * @param gc The gc to allocate the return value + * + * @return gc-allocated value of option with name opt_name if option was found, + * or NULL otherwise. + */ +char *options_string_extract_option (const char *options_string, + const char *opt_name, struct gc_arena *gc); + + #endif void options_postprocess (struct options *options); diff --git a/src/openvpn/push.c b/src/openvpn/push.c index b7539e6..9953079 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -262,7 +262,7 @@ incoming_push_message (struct context *c, const struct buffer *buffer) !tls_session_update_crypto_params (session, &c->options, &c->c2.frame)) { - msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed"); + msg (D_TLS_ERRORS, "TLS Error: initializing data channel failed"); goto error; } } @@ -370,6 +370,10 @@ prepare_push_reply (struct context *c, struct gc_arena *gc, push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername); } } + else if (o->ncp_enabled) + { + tls_poor_mans_ncp (o, tls_multi->remote_ciphername); + } /* If server uses --auth-gen-token and we have an auth token * to send to the client diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 16b9cb7..ff7e837 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1216,6 +1216,8 @@ tls_multi_free (struct tls_multi *multi, bool clear) free (multi->auth_token); } + free (multi->remote_ciphername); + for (i = 0; i < TM_SIZE; ++i) tls_session_free (&multi->session[i], false); @@ -1723,8 +1725,8 @@ 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) +bool +tls_item_in_cipher_list(const char *item, const char *list) { char *tmp_ciphers = string_alloc (list, NULL); char *tmp_ciphers_orig = tmp_ciphers; @@ -1741,6 +1743,20 @@ item_in_list(const char *item, const char *list) return token != NULL; } +void +tls_poor_mans_ncp(struct options *o, const char *remote_ciphername) +{ + if (o->ncp_enabled && 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); + } + } +} + bool tls_session_update_crypto_params(struct tls_session *session, const struct options *options, struct frame *frame) @@ -1752,7 +1768,7 @@ tls_session_update_crypto_params(struct tls_session *session, if (!session->opt->server && 0 != strcmp(options->ciphername, session->opt->config_ciphername) && - !item_in_list(options->ciphername, options->ncp_ciphers)) + !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, @@ -2311,13 +2327,21 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi multi->peer_info = read_string_alloc (buf); if ( multi->peer_info ) output_peer_info_env (session->opt->es, multi->peer_info); +#endif + + free (multi->remote_ciphername); + multi->remote_ciphername = + options_string_extract_option (options, "cipher", NULL); if (tls_peer_info_ncp_ver (multi->peer_info) < 2) { - /* Peer does not support NCP */ - session->opt->ncp_enabled = false; + /* Peer does not support NCP, but leave NCP enabled if the local and + * remote cipher do not match to attempt 'poor-man's NCP'. */ + if (0 == strcmp(multi->remote_ciphername, multi->opt.config_ciphername)) + { + session->opt->ncp_enabled = false; + } } -#endif if (tls_session_user_pass_enabled(session)) { diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 777b621..047eb24 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -489,6 +489,15 @@ void tls_update_remote_addr (struct tls_multi *multi, bool tls_session_update_crypto_params(struct tls_session *session, const struct options *options, struct frame *frame); +/** + * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher. + * Allows non-NCP peers to upgrade their cipher individually. + * + * Make sure to call tls_session_update_crypto_params() after calling this + * function. + */ +void tls_poor_mans_ncp(struct options *o, const char *remote_ciphername); + #ifdef MANAGEMENT_DEF_AUTH static inline char * tls_get_peer_info(const struct tls_multi *multi) @@ -512,6 +521,13 @@ int tls_peer_info_ncp_ver(const char *peer_info); */ bool tls_check_ncp_cipher_list(const char *list); +/** + * Return true iff item is present in the colon-separated zero-terminated + * cipher list. + */ +bool tls_item_in_cipher_list(const char *item, const char *list); + + /* * inline functions */ diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 28702af..7938f41 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -540,6 +540,8 @@ struct tls_multi uint32_t peer_id; bool use_peer_id; + char *remote_ciphername; /**< cipher specified in peer's config file */ + char *auth_token; /**< If server sends a generated auth-token, * this is the token to use for future * user/pass authentications in this session. -- 2.7.4 ------------------------------------------------------------------------------ _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel