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. To achieve this cleanly, tls_session_update_crypto_parameters() had to be split into 'update' and 'generate keys' parts. I used the opportunity to introduce a simpler API that works for the other places where we used 'generate_key_expansion()' too. 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 src/openvpn/init.c | 4 ++- src/openvpn/options.c | 32 ++++++++++++++++++ src/openvpn/options.h | 14 ++++++++ src/openvpn/push.c | 7 ++-- src/openvpn/ssl.c | 94 ++++++++++++++++++++++++++++----------------------- src/openvpn/ssl.h | 19 +++++++++-- 6 files changed, 122 insertions(+), 48 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 2ed633c..ba19ac4 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1928,7 +1928,9 @@ do_deferred_options (struct context *c, const unsigned int found) msg (D_PUSH, "OPTIONS IMPORT: data channel crypto options modified"); /* Do not regenerate keys if server sends an extra push request */ if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized && - !tls_session_update_crypto_params(session, &c->options, &c->c2.frame)) + (!tls_session_update_crypto_params (session, &c->options, + &c->c2.frame) || + !tls_session_generate_data_channel_keys(session))) { msg (D_TLS_ERRORS, "OPTIONS ERROR: failed to import crypto options"); return false; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index a74de24..778fcbd 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,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..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..01bc63c 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -259,10 +259,11 @@ incoming_push_message (struct context *c, const struct buffer *buffer) struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE]; /* Do not regenerate keys if client send a second push request */ if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized && - !tls_session_update_crypto_params (session, &c->options, - &c->c2.frame)) + (!tls_session_update_crypto_params (session, &c->options, + &c->c2.frame) || + !tls_session_generate_data_channel_keys(session))) { - msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed"); + msg (D_TLS_ERRORS, "TLS Error: initializing data channel failed"); goto error; } } diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 8bb3f96..ca3f45b 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1725,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; @@ -1744,17 +1744,40 @@ item_in_list(const char *item, const char *list) } bool -tls_session_update_crypto_params(struct tls_session *session, - const struct options *options, struct frame *frame) +tls_session_generate_data_channel_keys(struct tls_session *session) { bool ret = false; struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */ + const struct session_id *client_sid = session->opt->server ? + &ks->session_id_remote : &session->session_id; + const struct session_id *server_sid = !session->opt->server ? + &ks->session_id_remote : &session->session_id; ASSERT (ks->authenticated); + if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi, + &session->opt->key_type, ks->key_src, client_sid, server_sid, + session->opt->server)) + { + msg (D_TLS_ERRORS, "TLS Error: generate_key_expansion failed"); + goto cleanup; + } + tls_limit_reneg_bytes (session->opt->key_type.cipher, + &session->opt->renegotiate_bytes); + + ret = true; +cleanup: + CLEAR (*ks->key_src); + return ret; +} + +bool +tls_session_update_crypto_params(struct tls_session *session, + const struct options *options, struct frame *frame) +{ 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, @@ -1779,23 +1802,7 @@ tls_session_update_crypto_params(struct tls_session *session, frame_init_mssfix(frame, options); frame_print (frame, D_MTU_INFO, "Data Channel MTU parms"); - const struct session_id *client_sid = session->opt->server ? - &ks->session_id_remote : &session->session_id; - const struct session_id *server_sid = !session->opt->server ? - &ks->session_id_remote : &session->session_id; - if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi, - &session->opt->key_type, ks->key_src, client_sid, server_sid, - session->opt->server)) - { - msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed"); - goto cleanup; - } - tls_limit_reneg_bytes (session->opt->key_type.cipher, - &session->opt->renegotiate_bytes); - ret = true; -cleanup: - CLEAR (*ks->key_src); - return ret; + return true; } static bool @@ -2163,21 +2170,12 @@ key_method_2_write (struct buffer *buf, struct tls_session *session) { if (ks->authenticated) { - if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi, - &session->opt->key_type, - ks->key_src, - &ks->session_id_remote, - &session->session_id, - true)) + if (!tls_session_generate_data_channel_keys (session)) { msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed"); goto error; } } - - CLEAR (*ks->key_src); - tls_limit_reneg_bytes (session->opt->key_type.cipher, - &session->opt->renegotiate_bytes); } return true; @@ -2320,6 +2318,27 @@ 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 *remote_cipher = options_string_extract_option (options, "cipher", &gc); + if (c->options.ncp_enabled && !session->opt->ncp_enabled && remote_cipher && + 0 != strcmp(c->options.ciphername, remote_cipher)) + { + if (tls_item_in_cipher_list(remote_cipher, c->options.ncp_ciphers)) + { + c->options.ciphername = string_alloc(remote_cipher, &c->options.gc); + msg (D_TLS_DEBUG_LOW, "Using client cipher '%s'", + c->options.ciphername); + if (!tls_session_update_crypto_params (session, &c->options, + &c->c2.frame)) + { + msg (D_TLS_ERRORS, "TLS Error: failed to update crypto params"); + goto error; + } + } + } + #endif if (tls_session_user_pass_enabled(session)) @@ -2395,20 +2414,11 @@ key_method_2_read (struct buffer *buf, struct context *c, struct tls_session *se */ if (!session->opt->server && (!session->opt->pull || ks->key_id > 0)) { - if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi, - &session->opt->key_type, - ks->key_src, - &session->session_id, - &ks->session_id_remote, - false)) + if (!tls_session_generate_data_channel_keys (session)) { msg (D_TLS_ERRORS, "TLS Error: client generate_key_expansion failed"); goto error; } - - CLEAR (*ks->key_src); - tls_limit_reneg_bytes (session->opt->key_type.cipher, - &session->opt->renegotiate_bytes); } gc_free (&gc); diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 990d8da..f9bec37 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -472,8 +472,16 @@ void tls_update_remote_addr (struct tls_multi *multi, const struct link_socket_actual *addr); /** - * Update TLS session crypto parameters (cipher and auth) and derive data - * channel keys based on the supplied options. + * Generate data channel keys for the supplied TLS session. + * + * This erases the source material used to generate the data channel keys, and + * can thus be called only once per session. + */ +bool tls_session_generate_data_channel_keys(struct tls_session *session); + +/** + * Update TLS session crypto parameters (cipher and auth) and recalculate + * resulting frame sizes. * * @param session The TLS session to update. * @param options The options to use when updating session. @@ -508,6 +516,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