Allows non-NCP clients (<= 2.3, or 2.4+ with --ncp-disable) to specify a --cipher that is different from the one in the server config, as long as the new cipher value is allowed (i.e. in --ncp-ciphers at the server side).
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 parameter from options_string_extract_option() prototype. src/openvpn/options.c | 32 ++++++++++++++++++++++++++++++++ src/openvpn/options.h | 14 ++++++++++++++ src/openvpn/ssl.c | 24 +++++++++++++++++++++--- src/openvpn/ssl.h | 7 +++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/openvpn/options.c b/src/openvpn/options.c index a74de24..94cd3d4 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -3431,6 +3431,38 @@ options_string_version (const char* s, struct gc_arena *gc) return BSTR (&out); } +char * +options_string_extract_option (const char *options_string, + size_t options_string_len, const char *opt_name, struct gc_arena *gc) +{ + char *ret = NULL; + + const char *p = options_string; + while (p) + { + if (p == strstr(p, opt_name)) + { + const size_t opt_name_len = strlen(opt_name); + if (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; +} + #endif /* ENABLE_OCC */ static void diff --git a/src/openvpn/options.h b/src/openvpn/options.h index a028556..a66fd84 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, + size_t options_string_len, const char *opt_name, struct gc_arena *gc); + + #endif void options_postprocess (struct options *options); diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 1ae1547..4080ffb 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1723,8 +1723,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; @@ -1752,7 +1752,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, @@ -2318,6 +2318,24 @@ key_method_2_read (struct buffer *buf, struct context *c, struct tls_session *se /* Peer does not support NCP */ session->opt->ncp_enabled = false; } + + /* "Poor man's NCP": Use client-cipher if it is an allowed (NCP) cipher. + * Allows non-NCP client to upgrade their cipher individually. */ + char *options_string_remote_cipher = options_string_extract_option (options, + TLS_OPTIONS_LEN, "cipher", &gc); + if (!session->opt->ncp_enabled && options_string_remote_cipher && + 0 != strcmp(c->options.ciphername, options_string_remote_cipher)) + { + if (tls_item_in_cipher_list(options_string_remote_cipher, + c->options.ncp_ciphers)) + { + c->options.ciphername = string_alloc(options_string_remote_cipher, + &c->options.gc); + session->opt->ncp_enabled = true; + msg (D_TLS_DEBUG_LOW, "Using client cipher '%s'", c->options.ciphername); + } + } + #endif if (tls_session_user_pass_enabled(session)) diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 990d8da..8f588c6 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -508,6 +508,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 */ -- 2.7.4 ------------------------------------------------------------------------------ _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel