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 <[email protected]>
>
> Patch v2: rebased
>
> Signed-off-by: Arne Schwabe <[email protected]>
> ---
> 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 <[email protected]>
--
Antonio Quartulli
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel