Hi, On 20/05/2021 17:11, Arne Schwabe wrote: > Since generating data channel keys does not happen when we have reach the > S_ACTIVE/S_GOT_KEY state anymore like it used to be before NCP, the > state that data channel keys deserves its own state in the TLS session > state machine. > > The changes done by this commit are rather intrusive since they > move the key generation to a completely different place and also > rely on the state machine to decide if keys should be > generated rather than on the complicated conditions that were > implemented in the key_method_2_write/read methods. > > A (intended) side effect of this change is that sessions that > are still in deferred state (ks->authenticated == KS_DEFERRED) > will not have data channel keys generated. This avoids corner > cases where a not fully authenticated sessions might leak data. > > Signed-off-by: Arne Schwabe <a...@rfc2549.org> > > Patch v2: rebased > > Signed-off-by: Arne Schwabe <a...@rfc2549.org> > --- > src/openvpn/forward.h | 2 +- > src/openvpn/init.c | 1 + > src/openvpn/ssl.c | 89 +++++++++++++++++----------------------- > src/openvpn/ssl.h | 10 +++++ > src/openvpn/ssl_common.h | 9 +++- > 5 files changed, 57 insertions(+), 54 deletions(-) > > diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h > index 3461e6422..b8760099e 100644 > --- a/src/openvpn/forward.h > +++ b/src/openvpn/forward.h > @@ -450,7 +450,7 @@ connection_established(struct context *c) > { > if (c->c2.tls_multi) > { > - return c->c2.tls_multi->multi_state >= CAS_CONNECT_DONE; > + return c->c2.tls_multi->multi_state >= CAS_WAITING_OPTIONS_IMPORT; > } > else > { > diff --git a/src/openvpn/init.c b/src/openvpn/init.c > index 49c742928..4335d4870 100644 > --- a/src/openvpn/init.c > +++ b/src/openvpn/init.c > @@ -2202,6 +2202,7 @@ do_up(struct context *c, bool pulled_options, unsigned > int option_types_found) > } > > c->c2.do_up_ran = true; > + c->c2.tls_multi->multi_state = CAS_CONNECT_DONE; > } > return true; > } > diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c > index fd64b8d4e..b28d8edf8 100644 > --- a/src/openvpn/ssl.c > +++ b/src/openvpn/ssl.c > @@ -788,6 +788,9 @@ state_name(int state) > case S_ERROR: > return "S_ERROR"; > > + case S_GENERATED_KEYS: > + return "S_GENERATED_KEYS"; > + > default: > return "S_???"; > } > @@ -1840,13 +1843,13 @@ key_ctx_update_implicit_iv(struct key_ctx *ctx, > uint8_t *key, size_t key_len) > * This erases the source material used to generate the data channel keys, > and > * can thus be called only once per session. > */ > -static bool > +bool > tls_session_generate_data_channel_keys(struct tls_session *session) > { > bool ret = false; > struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */ > > - if (ks->authenticated == KS_AUTH_FALSE) > + if (ks->authenticated <= KS_AUTH_FALSE) > { > msg(D_TLS_ERRORS, "TLS Error: key_state not authenticated"); > goto cleanup; > @@ -1862,6 +1865,9 @@ tls_session_generate_data_channel_keys(struct > tls_session *session) > tls_limit_reneg_bytes(session->opt->key_type.cipher, > &session->opt->renegotiate_bytes); > > + /* set the state of the keys for the session to generated */ > + ks->state = S_GENERATED_KEYS; > + > ret = true; > cleanup: > secure_memzero(ks->key_src, sizeof(*ks->key_src)); > @@ -2375,31 +2381,6 @@ key_method_2_write(struct buffer *buf, struct > tls_multi *multi, struct tls_sessi > goto error; > } > > - /* > - * Generate tunnel keys if we're a TLS server. > - * > - * If we're a p2mp server to allow NCP, the first key > - * generation is postponed until after the connect script finished and > the > - * NCP options can be processed. Since that always happens at after > connect > - * script options are available the CAS_CONNECT_DONE status is identical > to > - * NCP options are processed and do not wait for NCP being finished. > - */ > - if (ks->authenticated > KS_AUTH_FALSE && session->opt->server > - && ((session->opt->mode == MODE_SERVER && multi->multi_state >= > CAS_CONNECT_DONE) > - || (session->opt->mode == MODE_POINT_TO_POINT && > !session->opt->pull))) > - { > - /* if key_id >= 1, is a renegotiation, so we use the already > established > - * parameters and do not need to delay anything. */ > - > - /* key-id == 0 and multi_state >= CAS_CONNECT_DONE is a special case > of > - * the server reusing the session of a reconnecting client. */ > - if (!tls_session_generate_data_channel_keys(session)) > - { > - msg(D_TLS_ERRORS, "TLS Error: server generate_key_expansion > failed"); > - goto error; > - } > - } > - > return true; > > error: > @@ -2599,21 +2580,7 @@ key_method_2_read(struct buffer *buf, struct tls_multi > *multi, struct tls_sessio > > setenv_del(session->opt->es, "exported_keying_material"); > } > - > - /* > - * Generate tunnel keys if we're a client. > - * If --pull is enabled, the first key generation is postponed until > after the > - * pull/push, so we can process pushed cipher directives. > - */ > - if (!session->opt->server && (!session->opt->pull || ks->key_id > 0)) > - { > - if (!tls_session_generate_data_channel_keys(session)) > - { > - msg(D_TLS_ERRORS, "TLS Error: client generate_key_expansion > failed"); > - goto error; > - } > - } > - > +
^^ trailing spaces > gc_free(&gc); > return true; > > @@ -2815,7 +2782,7 @@ tls_process(struct tls_multi *multi, > else > { > /* Skip the connect script related states */ > - multi->multi_state = CAS_CONNECT_DONE; > + multi->multi_state = CAS_WAITING_OPTIONS_IMPORT; > } > } > > @@ -3138,6 +3105,27 @@ tls_multi_process(struct tls_multi *multi, > > /* If we have successfully authenticated and are still waiting for the > authentication to finish > * move the state machine for the multi context forward */ > + > + if (multi->multi_state >= CAS_CONNECT_DONE) > + { > + for (int i = 0; i < TM_SIZE; ++i) > + { > + struct tls_session *session = &multi->session[i]; > + struct key_state *ks = &session->key[KS_PRIMARY]; > + > + if (ks->state == S_ACTIVE && ks->authenticated == KS_AUTH_TRUE) > + { > + /* This will ks->state from S_ACTIVE to S_GENERATED_KEYS */ word missing: "switch" ? > + if (!tls_session_generate_data_channel_keys(session)) > + { > + msg(D_TLS_ERRORS, "TLS Error: generate_key_expansion > failed"); > + ks->authenticated = KS_AUTH_FALSE; > + ks->state = S_ERROR; > + } > + } > + } > + } > + > if (multi->multi_state == CAS_WAITING_AUTH && tas == > TLS_AUTHENTICATION_SUCCEEDED) > { > multi->multi_state = CAS_PENDING; > @@ -3246,11 +3234,10 @@ handle_data_channel_packet(struct tls_multi *multi, > * passive side is the server which only listens for the > connections, the > * active side is the client which initiates connections). > */ > - if (TLS_AUTHENTICATED(multi, ks) > - && key_id == ks->key_id > - && (ks->authenticated == KS_AUTH_TRUE) > + if (ks->state >= S_GENERATED_KEYS && key_id == ks->key_id extra space after S_GENERATED_KEYS > && (floated || link_socket_actual_match(from, &ks->remote_addr))) > { > + ASSERT(ks->authenticated == KS_AUTH_TRUE); > if (!ks->crypto_options.key_ctx_bi.initialized) > { > msg(D_MULTI_DROPPED, > @@ -3572,8 +3559,7 @@ tls_pre_decrypt(struct tls_multi *multi, > /* > * Remote is requesting a key renegotiation > */ > - if (op == P_CONTROL_SOFT_RESET_V1 > - && TLS_AUTHENTICATED(multi, ks)) > + if (op == P_CONTROL_SOFT_RESET_V1 && TLS_AUTHENTICATED(multi, ks)) > { > if (!read_control_auth(buf, &session->tls_wrap, from, > session->opt)) > @@ -3834,10 +3820,11 @@ struct key_state *tls_select_encryption_key(struct > tls_multi *multi) > for (int i = 0; i < KEY_SCAN_SIZE; ++i) > { > struct key_state *ks = get_key_scan(multi, i); > - if (ks->state >= S_ACTIVE > - && ks->authenticated == KS_AUTH_TRUE > - && ks->crypto_options.key_ctx_bi.initialized) > + if (ks->state >= S_GENERATED_KEYS) > { > + ASSERT(ks->authenticated == KS_AUTH_TRUE); > + ASSERT(ks->crypto_options.key_ctx_bi.initialized); > + > if (!ks_select) > { > ks_select = ks; > diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h > index 81cc22131..baf3560d2 100644 > --- a/src/openvpn/ssl.h > +++ b/src/openvpn/ssl.h > @@ -612,4 +612,14 @@ show_available_tls_ciphers(const char *cipher_list, > const char *cipher_list_tls13, > const char *tls_cert_profile); > > + > +/** > + * 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); > + > #endif /* ifndef OPENVPN_SSL_H */ > diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h > index 66700bf68..928e80929 100644 > --- a/src/openvpn/ssl_common.h > +++ b/src/openvpn/ssl_common.h > @@ -64,7 +64,8 @@ > * material. > * -# \c S_GOT_KEY, have received remote part of \c key_source2 random > * material. > - * -# \c S_ACTIVE, normal operation > + * -# \c S_ACTIVE, control channel successfully established > + * -# \c S_GENERATED_KEYS, the > * > * Servers follow the same order, except for \c S_SENT_KEY and \c > * S_GOT_KEY being reversed, because the server first receives the > @@ -92,7 +93,10 @@ > #define S_ACTIVE 6 /**< Operational \c key_state state > * immediately after negotiation has > * completed while still within the > - * handshake window. */ > + * handshake window, deferred auth, client > + * connect and can still typ0: "and can" -> "can" > + * be pending. */ > +#define S_GENERATED_KEYS 7 /**< The data channel keys have been > generated */ > /* Note that earlier versions also had a S_OP_NORMAL state that was > * virtually identical with S_ACTIVE and the code still assumes everything > * >= S_ACTIVE to be fully operational */ > @@ -516,6 +520,7 @@ enum multi_status { > CAS_PENDING_DEFERRED, > CAS_PENDING_DEFERRED_PARTIAL, /**< at least handler succeeded, no > result yet*/ > CAS_FAILED, > + CAS_WAITING_OPTIONS_IMPORT, /**< client with pull or p2p waiting for > first time options import */ do we wait for "options" in p2p mode? I thought in client/server we could have "pull" enabled, no? > CAS_CONNECT_DONE, > }; > > Due to your previous simplifications, this change is easier to digest. However, I wonder if it was easier to split the introduction of CAS_WAITING_OPTIONS_IMPORT in a different patch? This said, keeping everything in one patch is still reasonable - mostly matter of taste I think. To me this looks like yet another nice simplification which helps making the whole flow easier to understand. I tried to break the key generation in various ways, but I was unable to do so. So far it worked fine with deferred auth, without it, with client/server and p2p mode as well. Even though this patch is intrusive I find it very reasonable. Acked-by: Antonio Quartulli <anto...@openvpn.net> -- Antonio Quartulli _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel