summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debian/patches/CVE-2020-15078-0.patch276
-rw-r--r--debian/patches/CVE-2020-15078-1.patch390
-rw-r--r--debian/patches/CVE-2020-15078-2.patch125
-rw-r--r--debian/patches/CVE-2020-15078-3.patch51
-rw-r--r--debian/patches/series4
5 files changed, 846 insertions, 0 deletions
diff --git a/debian/patches/CVE-2020-15078-0.patch b/debian/patches/CVE-2020-15078-0.patch
new file mode 100644
index 0000000..d048994
--- /dev/null
+++ b/debian/patches/CVE-2020-15078-0.patch
@@ -0,0 +1,276 @@
+From 145110101b70599cb9adcf4ed071856daac9c8af Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <arne@rfc2549.org>
+Date: Sun, 28 Mar 2021 14:02:40 +0200
+Subject: [PATCH] Move context_auth from context_2 to tls_multi and name it
+ multi_state
+
+context_2 and tls_multi have the same life cycle for TLS connections
+but so this move does not affect behaviour of the variable.
+
+OpenVPN TLS multi code has a grown a lot more complex and code that
+handles multi objects needs to know the state that the object is in.
+Since not all code has access to the context_2 struct, the code that
+does not have access is often not checking the state directly but
+checks other parts of multi that have been affected from a state
+change.
+
+This patch also renames it to multi_state as this variable represents
+the multi state machine status rather than just the state of the connect
+authentication (more upcoming patches will move other states
+into this variable).
+
+Patch V2: also rename context_auth to multi_state, explain a bit why this
+ change is done.
+Patch V3: Add comments for c2->multi NULL check forwarding. Fix compile
+ with ENABLE_ASYNC_PUSH.
+
+Signed-off-by: Arne Schwabe <arne@rfc2549.org>
+Acked-by: Antonio Quartulli <antonio@openvpn.net>
+Acked-by: Gert Doering <gert@greenie.muc.de>
+Message-Id: <20210418160111.1494779-1-arne@rfc2549.org>
+URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg22155.html
+Signed-off-by: Gert Doering <gert@greenie.muc.de>
+(backported from commit 0767d5b447044e4cdcfd198058aef1f85f63bbe6)
+---
+ src/openvpn/forward.c | 10 ++++++----
+ src/openvpn/multi.c | 28 ++++++++++++++--------------
+ src/openvpn/openvpn.h | 14 --------------
+ src/openvpn/push.c | 5 +++--
+ src/openvpn/ssl_common.h | 14 ++++++++++++++
+ 5 files changed, 37 insertions(+), 34 deletions(-)
+
+diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
+index 7ed8d0d75..fd7412f73 100644
+--- a/src/openvpn/forward.c
++++ b/src/openvpn/forward.c
+@@ -526,9 +526,10 @@ encrypt_sign(struct context *c, bool comp_frag)
+
+ /*
+ * Drop non-TLS outgoing packet if client-connect script/plugin
+- * has not yet succeeded.
++ * has not yet succeeded. In non-TLS mode tls_multi is not defined
++ * and we always pass packets.
+ */
+- if (c->c2.context_auth != CAS_SUCCEEDED)
++ if (c->c2.tls_multi && c->c2.tls_multi->multi_state != CAS_SUCCEEDED)
+ {
+ c->c2.buf.len = 0;
+ }
+@@ -973,9 +974,10 @@ process_incoming_link_part1(struct context *c, struct link_socket_info *lsi, boo
+
+ /*
+ * Drop non-TLS packet if client-connect script/plugin and cipher selection
+- * has not yet succeeded.
++ * has not yet succeeded. In non-TLS mode tls_multi is not defined
++ * and we always pass packets.
+ */
+- if (c->c2.context_auth != CAS_SUCCEEDED)
++ if (c->c2.tls_multi && c->c2.tls_multi->multi_state != CAS_SUCCEEDED)
+ {
+ c->c2.buf.len = 0;
+ }
+diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
+index 137381805..599ffd86d 100644
+--- a/src/openvpn/multi.c
++++ b/src/openvpn/multi.c
+@@ -678,7 +678,7 @@ multi_close_instance(struct multi_context *m,
+ #ifdef MANAGEMENT_DEF_AUTH
+ set_cc_config(mi, NULL);
+ #endif
+- if (mi->context.c2.context_auth == CAS_SUCCEEDED)
++ if (mi->context.c2.tls_multi->multi_state == CAS_SUCCEEDED)
+ {
+ multi_client_disconnect_script(mi);
+ }
+@@ -788,7 +788,7 @@ multi_create_instance(struct multi_context *m, const struct mroute_addr *real)
+ goto err;
+ }
+
+- mi->context.c2.context_auth = CAS_PENDING;
++ mi->context.c2.tls_multi->multi_state = CAS_PENDING;
+
+ if (hash_n_elements(m->hash) >= m->max_clients)
+ {
+@@ -2436,18 +2436,18 @@ multi_client_connect_late_setup(struct multi_context *m,
+ mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
+
+ /* set context-level authentication flag */
+- mi->context.c2.context_auth = CAS_SUCCEEDED;
++ mi->context.c2.tls_multi->multi_state = CAS_SUCCEEDED;
+
+ /* authentication complete, calculate dynamic client specific options */
+ if (!multi_client_set_protocol_options(&mi->context))
+ {
+- mi->context.c2.context_auth = CAS_FAILED;
++ mi->context.c2.tls_multi->multi_state = CAS_FAILED;
+ }
+ /* Generate data channel keys only if setting protocol options
+ * has not failed */
+ else if (!multi_client_generate_tls_keys(&mi->context))
+ {
+- mi->context.c2.context_auth = CAS_FAILED;
++ mi->context.c2.tls_multi->multi_state = CAS_FAILED;
+ }
+
+ /* send push reply if ready */
+@@ -2595,7 +2595,7 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
+
+ /* We are only called for the CAS_PENDING_x states, so we
+ * can ignore other states here */
+- bool from_deferred = (mi->context.c2.context_auth != CAS_PENDING);
++ bool from_deferred = (mi->context.c2.tls_multi->multi_state != CAS_PENDING);
+
+ int *cur_handler_index = &mi->client_connect_defer_state.cur_handler_index;
+ unsigned int *option_types_found =
+@@ -2607,7 +2607,7 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
+ *cur_handler_index = 0;
+ *option_types_found = 0;
+ /* Initially we have no handler that has returned a result */
+- mi->context.c2.context_auth = CAS_PENDING_DEFERRED;
++ mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED;
+
+ multi_client_connect_early_setup(m, mi);
+ }
+@@ -2630,7 +2630,7 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
+ * Remember that we already had at least one handler
+ * returning a result should we go to into deferred state
+ */
+- mi->context.c2.context_auth = CAS_PENDING_DEFERRED_PARTIAL;
++ mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED_PARTIAL;
+ break;
+
+ case CC_RET_SKIPPED:
+@@ -2682,12 +2682,12 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
+ {
+ /* run the disconnect script if we had a connect script that
+ * did not fail */
+- if (mi->context.c2.context_auth == CAS_PENDING_DEFERRED_PARTIAL)
++ if (mi->context.c2.tls_multi->multi_state == CAS_PENDING_DEFERRED_PARTIAL)
+ {
+ multi_client_disconnect_script(mi);
+ }
+
+- mi->context.c2.context_auth = CAS_FAILED;
++ mi->context.c2.tls_multi->multi_state = CAS_FAILED;
+ }
+
+ /* increment number of current authenticated clients */
+@@ -2990,13 +2990,13 @@ multi_process_post(struct multi_context *m, struct multi_instance *mi, const uns
+ {
+ /* connection is "established" when SSL/TLS key negotiation succeeds
+ * and (if specified) auth user/pass succeeds */
+- if (is_cas_pending(mi->context.c2.context_auth)
++ if (is_cas_pending(mi->context.c2.tls_multi->multi_state)
+ && CONNECTION_ESTABLISHED(&mi->context))
+ {
+ multi_connection_established(m, mi);
+ }
+ #if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
+- if (is_cas_pending(mi->context.c2.context_auth)
++ if (is_cas_pending(mi->context.c2.tls_multi->multi_state)
+ && mi->client_connect_defer_state.deferred_ret_file)
+ {
+ add_inotify_file_watch(m, mi, m->top.c2.inotify_fd,
+@@ -3953,7 +3953,7 @@ management_client_auth(void *arg,
+ {
+ if (auth)
+ {
+- if (is_cas_pending(mi->context.c2.context_auth))
++ if (is_cas_pending(mi->context.c2.tls_multi->multi_state))
+ {
+ set_cc_config(mi, cc_config);
+ cc_config_owned = false;
+@@ -3965,7 +3965,7 @@ management_client_auth(void *arg,
+ {
+ msg(D_MULTI_LOW, "MULTI: connection rejected: %s, CLI:%s", reason, np(client_reason));
+ }
+- if (!is_cas_pending(mi->context.c2.context_auth))
++ if (!is_cas_pending(mi->context.c2.tls_multi->multi_state))
+ {
+ send_auth_failed(&mi->context, client_reason); /* mid-session reauth failed */
+ multi_schedule_context_wakeup(m, mi);
+diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
+index a7b597749..d131ac59e 100644
+--- a/src/openvpn/openvpn.h
++++ b/src/openvpn/openvpn.h
+@@ -211,17 +211,6 @@ struct context_1
+ };
+
+
+-/* client authentication state, CAS_SUCCEEDED must be 0 since
+- * non multi code path still checks this variable but does not initialise it
+- * so the code depends on zero initialisation */
+-enum client_connect_status {
+- CAS_SUCCEEDED=0,
+- CAS_PENDING,
+- CAS_PENDING_DEFERRED,
+- CAS_PENDING_DEFERRED_PARTIAL, /**< at least handler succeeded, no result yet*/
+- CAS_FAILED,
+-};
+-
+ static inline bool
+ is_cas_pending(enum client_connect_status cas)
+ {
+@@ -458,9 +447,6 @@ struct context_2
+ int push_ifconfig_ipv6_netbits;
+ struct in6_addr push_ifconfig_ipv6_remote;
+
+-
+- enum client_connect_status context_auth;
+-
+ struct event_timeout push_request_interval;
+ int n_sent_push_requests;
+ bool did_pre_pull_restore;
+diff --git a/src/openvpn/push.c b/src/openvpn/push.c
+index e0d2eeaf2..c47f4c8b6 100644
+--- a/src/openvpn/push.c
++++ b/src/openvpn/push.c
+@@ -733,13 +733,14 @@ process_incoming_push_request(struct context *c)
+ {
+ int ret = PUSH_MSG_ERROR;
+
+- if (tls_authentication_status(c->c2.tls_multi, 0) == TLS_AUTHENTICATION_FAILED || c->c2.context_auth == CAS_FAILED)
++ if (tls_authentication_status(c->c2.tls_multi, 0) == TLS_AUTHENTICATION_FAILED
++ || c->c2.tls_multi->multi_state == CAS_FAILED)
+ {
+ const char *client_reason = tls_client_reason(c->c2.tls_multi);
+ send_auth_failed(c, client_reason);
+ ret = PUSH_MSG_AUTH_FAILURE;
+ }
+- else if (c->c2.context_auth == CAS_SUCCEEDED)
++ else if (c->c2.tls_multi->multi_state == CAS_SUCCEEDED)
+ {
+ time_t now;
+
+diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
+index a703f97cd..06c32ac1d 100644
+--- a/src/openvpn/ssl_common.h
++++ b/src/openvpn/ssl_common.h
+@@ -479,6 +479,19 @@ struct tls_session
+ */
+ #define KEY_SCAN_SIZE 3
+
++
++/* client authentication state, CAS_SUCCEEDED must be 0 since
++ * non multi code path still checks this variable but does not initialise it
++ * so the code depends on zero initialisation */
++enum client_connect_status {
++ CAS_SUCCEEDED=0,
++ CAS_PENDING,
++ CAS_PENDING_DEFERRED,
++ CAS_PENDING_DEFERRED_PARTIAL, /**< at least handler succeeded, no result yet*/
++ CAS_FAILED,
++};
++
++
+ /**
+ * Security parameter state for a single VPN tunnel.
+ * @ingroup control_processor
+@@ -519,6 +532,7 @@ struct tls_multi
+
+ int n_sessions; /**< Number of sessions negotiated thus
+ * far. */
++ enum client_connect_status multi_state;
+
+ /*
+ * Number of errors.
diff --git a/debian/patches/CVE-2020-15078-1.patch b/debian/patches/CVE-2020-15078-1.patch
new file mode 100644
index 0000000..9acfc27
--- /dev/null
+++ b/debian/patches/CVE-2020-15078-1.patch
@@ -0,0 +1,390 @@
+From 3aca477a1b58714754fea3a26d0892fffc51db6b Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <arne@rfc2549.org>
+Date: Sat, 27 Mar 2021 18:47:24 +0100
+Subject: [PATCH] Move auth_token_state from multi to key_state
+
+The auth-token check is tied to the username/password that is coming
+via a specific SSL session, so keep the state also in the key_state
+structure.
+
+This also ensures the auth_token_state is always set to 0 on a new
+session since we clear the key_state object at the start of a new
+SSL session.
+
+This is a prerequisite patch to fix 2020-15078 in the following two
+commits.
+
+2nd patch, squashed into the first one:
+
+This also applies the changes to the auth_token_test.c. The change of
+tls_session to a pointer is necessary since before that we had tls_session
+not tied to the multi and had two tls_session used in the test. One
+implicitly in tls_multi and one explicit one. Merge these to one.
+
+CVE: 2020-15078
+Signed-off-by: Arne Schwabe <arne@rfc2549.org>
+Acked-by: Antonio Quartulli <antonio@openvpn.net>
+Message-Id: <d25ec73f-2ab0-31df-8cb6-7778000f4822@openvpn.net>
+URL: non-public, embargoed
+Signed-off-by: Gert Doering <gert@greenie.muc.de>
+---
+ doc/man-sections/example-fingerprint.rst | 0
+ src/openvpn/auth_token.c | 12 +--
+ src/openvpn/ssl_common.h | 4 +-
+ src/openvpn/ssl_verify.c | 8 +-
+ tests/unit_tests/openvpn/test_auth_token.c | 91 +++++++++++-----------
+ 5 files changed, 60 insertions(+), 55 deletions(-)
+ create mode 100644 doc/man-sections/example-fingerprint.rst
+
+diff --git a/doc/man-sections/example-fingerprint.rst b/doc/man-sections/example-fingerprint.rst
+new file mode 100644
+index 000000000..e69de29bb
+diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c
+index cc70c06c3..0ea6d1832 100644
+--- a/src/openvpn/auth_token.c
++++ b/src/openvpn/auth_token.c
+@@ -57,6 +57,7 @@ add_session_token_env(struct tls_session *session, struct tls_multi *multi,
+ return;
+ }
+
++ int auth_token_state_flags = session->key[KS_PRIMARY].auth_token_state_flags;
+
+ const char *state;
+
+@@ -64,9 +65,9 @@ add_session_token_env(struct tls_session *session, struct tls_multi *multi,
+ {
+ state = "Initial";
+ }
+- else if (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
++ else if (auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
+ {
+- switch (multi->auth_token_state_flags & (AUTH_TOKEN_VALID_EMPTYUSER|AUTH_TOKEN_EXPIRED))
++ switch (auth_token_state_flags & (AUTH_TOKEN_VALID_EMPTYUSER|AUTH_TOKEN_EXPIRED))
+ {
+ case 0:
+ state = "Authenticated";
+@@ -98,8 +99,8 @@ add_session_token_env(struct tls_session *session, struct tls_multi *multi,
+
+ /* We had a valid session id before */
+ const char *session_id_source;
+- if (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK
+- &!(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED))
++ if (auth_token_state_flags & AUTH_TOKEN_HMAC_OK
++ && !(auth_token_state_flags & AUTH_TOKEN_EXPIRED))
+ {
+ session_id_source = up->password;
+ }
+@@ -236,7 +237,8 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi)
+ * a new token with the empty username since we do not want to loose
+ * the information that the username cannot be trusted
+ */
+- if (multi->auth_token_state_flags & AUTH_TOKEN_VALID_EMPTYUSER)
++ struct key_state *ks = &multi->session[TM_ACTIVE].key[KS_PRIMARY];
++ if (ks->auth_token_state_flags & AUTH_TOKEN_VALID_EMPTYUSER)
+ {
+ hmac_ctx_update(ctx, (const uint8_t *) "", 0);
+ }
+diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
+index 06c32ac1d..d6fd50bd3 100644
+--- a/src/openvpn/ssl_common.h
++++ b/src/openvpn/ssl_common.h
+@@ -166,6 +166,8 @@ enum ks_auth_state {
+ struct key_state
+ {
+ int state;
++ /** The state of the auth-token sent from the client */
++ int auth_token_state_flags;
+
+ /**
+ * Key id for this key_state, inherited from struct tls_session.
+@@ -582,8 +584,6 @@ struct tls_multi
+ * OpenVPN 3 clients sometimes wipes or replaces the username with a
+ * username hint from their config.
+ */
+- int auth_token_state_flags;
+- /**< The state of the auth-token sent from the client last time */
+
+ /* For P_DATA_V2 */
+ uint32_t peer_id;
+diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c
+index 33115eb6c..6fd51505e 100644
+--- a/src/openvpn/ssl_verify.c
++++ b/src/openvpn/ssl_verify.c
+@@ -1269,7 +1269,7 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
+ */
+ if (session->opt->auth_token_generate && is_auth_token(up->password))
+ {
+- multi->auth_token_state_flags = verify_auth_token(up, multi, session);
++ ks->auth_token_state_flags = verify_auth_token(up, multi, session);
+ if (session->opt->auth_token_call_auth)
+ {
+ /*
+@@ -1278,7 +1278,7 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
+ * decide what to do with the result
+ */
+ }
+- else if (multi->auth_token_state_flags == AUTH_TOKEN_HMAC_OK)
++ else if (ks->auth_token_state_flags == AUTH_TOKEN_HMAC_OK)
+ {
+ /*
+ * We do not want the EXPIRED or EMPTY USER flags here so check
+@@ -1373,8 +1373,8 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
+ * the initial timestamp and session id can be extracted from it
+ */
+ if (!multi->auth_token
+- && (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
+- && !(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED))
++ && (ks->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
++ && !(ks->auth_token_state_flags & AUTH_TOKEN_EXPIRED))
+ {
+ multi->auth_token = strdup(up->password);
+ }
+diff --git a/tests/unit_tests/openvpn/test_auth_token.c b/tests/unit_tests/openvpn/test_auth_token.c
+index dbde86318..69fc1f8c9 100644
+--- a/tests/unit_tests/openvpn/test_auth_token.c
++++ b/tests/unit_tests/openvpn/test_auth_token.c
+@@ -45,7 +45,7 @@ struct test_context {
+ struct tls_multi multi;
+ struct key_type kt;
+ struct user_pass up;
+- struct tls_session session;
++ struct tls_session *session;
+ };
+
+ /* Dummy functions that do nothing to mock the functionality */
+@@ -100,10 +100,11 @@ setup(void **state)
+ }
+ ctx->multi.opt.auth_token_generate = true;
+ ctx->multi.opt.auth_token_lifetime = 3000;
++ ctx->session = &ctx->multi.session[TM_ACTIVE];
+
+- ctx->session.opt = calloc(1, sizeof(struct tls_options));
+- ctx->session.opt->renegotiate_seconds = 120;
+- ctx->session.opt->auth_token_lifetime = 3000;
++ ctx->session->opt = calloc(1, sizeof(struct tls_options));
++ ctx->session->opt->renegotiate_seconds = 120;
++ ctx->session->opt->auth_token_lifetime = 3000;
+
+ strcpy(ctx->up.username, "test user name");
+ strcpy(ctx->up.password, "ignored");
+@@ -122,7 +123,7 @@ teardown(void **state)
+ free_key_ctx(&ctx->multi.opt.auth_token_key);
+ wipe_auth_token(&ctx->multi);
+
+- free(ctx->session.opt);
++ free(ctx->session->opt);
+ free(ctx);
+
+ return 0;
+@@ -135,7 +136,7 @@ auth_token_basic_test(void **state)
+
+ generate_auth_token(&ctx->up, &ctx->multi);
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+ }
+
+@@ -146,7 +147,7 @@ auth_token_fail_invalid_key(void **state)
+
+ generate_auth_token(&ctx->up, &ctx->multi);
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+
+ /* Change auth-token key */
+@@ -155,13 +156,13 @@ auth_token_fail_invalid_key(void **state)
+ free_key_ctx(&ctx->multi.opt.auth_token_key);
+ init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST");
+
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), 0);
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), 0);
+
+ /* Load original test key again */
+ memset(&key, 0, sizeof(key));
+ free_key_ctx(&ctx->multi.opt.auth_token_key);
+ init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST");
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+
+ }
+@@ -176,32 +177,32 @@ auth_token_test_timeout(void **state)
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+
+ /* No time has passed */
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+
+ /* Token before validity, should be rejected */
+ now = 100000 - 100;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED);
+
+ /* Token still in validity, should be accepted */
+- now = 100000 + 2*ctx->session.opt->renegotiate_seconds - 20;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ now = 100000 + 2*ctx->session->opt->renegotiate_seconds - 20;
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+
+ /* Token past validity, should be rejected */
+- now = 100000 + 2*ctx->session.opt->renegotiate_seconds + 20;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ now = 100000 + 2*ctx->session->opt->renegotiate_seconds + 20;
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED);
+
+ /* Check if the mode for a client that never updates its token works */
+ ctx->multi.auth_token_initial = strdup(ctx->up.password);
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+
+ /* But not when we reached our timeout */
+- now = 100000 + ctx->session.opt->auth_token_lifetime + 1;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ now = 100000 + ctx->session->opt->auth_token_lifetime + 1;
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED);
+
+ free(ctx->multi.auth_token_initial);
+@@ -209,22 +210,22 @@ auth_token_test_timeout(void **state)
+
+ /* regenerate the token util it hits the expiry */
+ now = 100000;
+- while (now < 100000 + ctx->session.opt->auth_token_lifetime + 1)
++ while (now < 100000 + ctx->session->opt->auth_token_lifetime + 1)
+ {
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+ generate_auth_token(&ctx->up, &ctx->multi);
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+- now += ctx->session.opt->renegotiate_seconds;
++ now += ctx->session->opt->renegotiate_seconds;
+ }
+
+
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED);
+ ctx->multi.opt.auth_token_lifetime = 0;
+
+ /* Non expiring token should be fine */
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+ }
+
+@@ -253,7 +254,7 @@ auth_token_test_known_keys(void **state)
+ assert_string_equal(now0key0, ctx->multi.auth_token);
+
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+ }
+
+@@ -277,25 +278,25 @@ auth_token_test_empty_user(void **state)
+
+ generate_auth_token(&ctx->up, &ctx->multi);
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK);
+
+ now = 100000;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED);
+ strcpy(ctx->up.username, "test user name");
+
+ now = 0;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_VALID_EMPTYUSER);
+
+ strcpy(ctx->up.username, "test user name");
+ now = 100000;
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED|AUTH_TOKEN_VALID_EMPTYUSER);
+
+ zerohmac(ctx->up.password);
+- assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session),
++ assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
+ 0);
+ }
+
+@@ -304,30 +305,32 @@ auth_token_test_env(void **state)
+ {
+ struct test_context *ctx = (struct test_context *) *state;
+
+- ctx->multi.auth_token_state_flags = 0;
++ struct key_state *ks = &ctx->multi.session[TM_ACTIVE].key[KS_PRIMARY];
++
++ ks->auth_token_state_flags = 0;
+ ctx->multi.auth_token = NULL;
+- add_session_token_env(&ctx->session, &ctx->multi, &ctx->up);
++ add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
+ assert_string_equal(lastsesion_statevalue, "Initial");
+
+- ctx->multi.auth_token_state_flags = 0;
++ ks->auth_token_state_flags = 0;
+ strcpy(ctx->up.password, now0key0);
+- add_session_token_env(&ctx->session, &ctx->multi, &ctx->up);
++ add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
+ assert_string_equal(lastsesion_statevalue, "Invalid");
+
+- ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK;
+- add_session_token_env(&ctx->session, &ctx->multi, &ctx->up);
++ ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK;
++ add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
+ assert_string_equal(lastsesion_statevalue, "Authenticated");
+
+- ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED;
+- add_session_token_env(&ctx->session, &ctx->multi, &ctx->up);
++ ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED;
++ add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
+ assert_string_equal(lastsesion_statevalue, "Expired");
+
+- ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_VALID_EMPTYUSER;
+- add_session_token_env(&ctx->session, &ctx->multi, &ctx->up);
++ ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_VALID_EMPTYUSER;
++ add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
+ assert_string_equal(lastsesion_statevalue, "AuthenticatedEmptyUser");
+
+- ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED|AUTH_TOKEN_VALID_EMPTYUSER;
+- add_session_token_env(&ctx->session, &ctx->multi, &ctx->up);
++ ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED|AUTH_TOKEN_VALID_EMPTYUSER;
++ add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
+ assert_string_equal(lastsesion_statevalue, "ExpiredEmptyUser");
+ }
+
+@@ -351,7 +354,7 @@ auth_token_test_random_keys(void **state)
+ assert_string_equal(random_token, ctx->multi.auth_token);
+
+ strcpy(ctx->up.password, ctx->multi.auth_token);
+- assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session));
++ assert_true(verify_auth_token(&ctx->up, &ctx->multi, ctx->session));
+ }
+
+
+@@ -363,11 +366,11 @@ auth_token_test_key_load(void **state)
+ free_key_ctx(&ctx->multi.opt.auth_token_key);
+ auth_token_init_secret(&ctx->multi.opt.auth_token_key, zeroinline, true);
+ strcpy(ctx->up.password, now0key0);
+- assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session));
++ assert_true(verify_auth_token(&ctx->up, &ctx->multi, ctx->session));
+
+ free_key_ctx(&ctx->multi.opt.auth_token_key);
+ auth_token_init_secret(&ctx->multi.opt.auth_token_key, allx01inline, true);
+- assert_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session));
++ assert_false(verify_auth_token(&ctx->up, &ctx->multi, ctx->session));
+ }
+
+ int
diff --git a/debian/patches/CVE-2020-15078-2.patch b/debian/patches/CVE-2020-15078-2.patch
new file mode 100644
index 0000000..4bd7df4
--- /dev/null
+++ b/debian/patches/CVE-2020-15078-2.patch
@@ -0,0 +1,125 @@
+From 3d18e308c4e7e6f7ab7c2826c70d2d07b031c18a Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <arne@rfc2549.org>
+Date: Sat, 27 Mar 2021 19:35:44 +0100
+Subject: [PATCH] Ensure auth-token is only sent on a fully authenticated
+ session
+
+This fixes the problem that if client authentication is deferred, we
+send an updated token before the authentication fully finished.
+
+Calling the new ssl_session_fully_authenticated from the two places
+that do the state transition to KS_AUTH_TRUE is a bit suboptimal but
+a cleaner solution requires more refactoring of the involved methods
+and state machines.
+
+This bug allows - under very specific circumstances - to trick a
+server using delayed authentication (plugin or management) *and*
+"--auth-gen-token" into returning a PUSH_REPLY before the AUTH_FAILED
+message, which can possibly be used to gather information about a
+VPN setup or even get access to a VPN with an otherwise-invalid account.
+
+CVE-2020-15078 has been assigned to acknowledge this risk.
+
+CVE: 2020-15078
+Signed-off-by: Arne Schwabe <arne@rfc2549.org>
+Acked-by: Antonio Quartulli <antonio@openvpn.net>
+Message-Id: <d25ec73f-2ab0-31df-8cb6-7778000f4822@openvpn.net>
+URL: non-public, embargoed
+Signed-off-by: Gert Doering <gert@greenie.muc.de>
+---
+ src/openvpn/ssl_verify.c | 64 +++++++++++++++++++++++++++-------------
+ 1 file changed, 43 insertions(+), 21 deletions(-)
+
+diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c
+index 6fd51505e..55e7fedc0 100644
+--- a/src/openvpn/ssl_verify.c
++++ b/src/openvpn/ssl_verify.c
+@@ -906,6 +906,39 @@ key_state_test_auth_control_file(struct key_state *ks)
+
+ #endif /* ifdef PLUGIN_DEF_AUTH */
+
++/* This function is called when a session's primary key state first becomes KS_TRUE */
++void ssl_session_fully_authenticated(struct tls_multi *multi, struct tls_session* session)
++{
++ struct key_state *ks = &session->key[KS_PRIMARY];
++ if (ks->key_id == 0)
++ {
++ /* A key id of 0 indicates a new session and the client will
++ * get the auth-token as part of the initial push reply */
++ return;
++ }
++
++ /*
++ * Auth token already sent to client, update auth-token on client.
++ * The initial auth-token is sent as part of the push message, for this
++ * update we need to schedule an extra push message.
++ *
++ * Otherwise the auth-token get pushed out as part of the "normal"
++ * push-reply
++ */
++ if (multi->auth_token_initial)
++ {
++ /*
++ * We do not explicitly schedule the sending of the
++ * control message here but control message are only
++ * postponed when the control channel is not yet fully
++ * established and furthermore since this is called in
++ * the middle of authentication, there are other messages
++ * (new data channel keys) that are sent anyway and will
++ * trigger scheduling
++ */
++ send_push_reply_auth_token(multi);
++ }
++}
+ /*
+ * Return current session authentication state. Return
+ * value is TLS_AUTHENTICATION_x.
+@@ -975,6 +1008,12 @@ tls_authentication_status(struct tls_multi *multi, const int latency)
+ case ACF_SUCCEEDED:
+ case ACF_DISABLED:
+ success = true;
++ /* i=0 is the TM_ACTIVE/KS_PRIMARY session */
++ if (i == 0 && ks->authenticated == KS_AUTH_DEFERRED)
++ {
++ ssl_session_fully_authenticated(multi,
++ &multi->session[TM_ACTIVE]);
++ }
+ ks->authenticated = KS_AUTH_TRUE;
+ break;
+
+@@ -1385,31 +1424,14 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
+ */
+ generate_auth_token(up, multi);
+ }
+- /*
+- * Auth token already sent to client, update auth-token on client.
+- * The initial auth-token is sent as part of the push message, for this
+- * update we need to schedule an extra push message.
+- *
+- * Otherwise the auth-token get pushed out as part of the "normal"
+- * push-reply
+- */
+- if (multi->auth_token_initial)
+- {
+- /*
+- * We do not explicitly schedule the sending of the
+- * control message here but control message are only
+- * postponed when the control channel is not yet fully
+- * established and furthermore since this is called in
+- * the middle of authentication, there are other messages
+- * (new data channel keys) that are sent anyway and will
+- * trigger schedueling
+- */
+- send_push_reply_auth_token(multi);
+- }
+ msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
+ (ks->authenticated == KS_AUTH_DEFERRED) ? "deferred" : "succeeded",
+ up->username,
+ (session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
++ if (ks->authenticated == KS_AUTH_TRUE)
++ {
++ ssl_session_fully_authenticated(multi, session);
++ }
+ }
+ else
+ {
diff --git a/debian/patches/CVE-2020-15078-3.patch b/debian/patches/CVE-2020-15078-3.patch
new file mode 100644
index 0000000..6d4414a
--- /dev/null
+++ b/debian/patches/CVE-2020-15078-3.patch
@@ -0,0 +1,51 @@
+From f7b3bf067ffce72e7de49a4174fd17a3a83f0573 Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <arne@rfc2549.org>
+Date: Tue, 6 Apr 2021 00:14:47 +0200
+Subject: [PATCH] Ensure key state is authenticated before sending push reply
+
+This ensures that the key state is authenticated when sending
+a push reply.
+
+This bug allows - under very specific circumstances - to trick a
+server using delayed authentication (plugin or management) into
+returning a PUSH_REPLY before the AUTH_FAILED message, which can
+possibly be used to gather information about a VPN setup.
+
+In combination with "--auth-gen-token" or user-specific token auth
+solutions it can be possible to get access to a VPN with an
+otherwise-invalid account.
+
+CVE-2020-15078 has been assigned to acknowledge this risk.
+
+CVE: 2020-15078
+Signed-off-by: Arne Schwabe <arne@rfc2549.org>
+Acked-by: Gert Doering <gert@greenie.muc.de>
+Message-Id: <d25ec73f-2ab0-31df-8cb6-7778000f4822@openvpn.net>
+URL: non-public, embargoed
+Signed-off-by: Gert Doering <gert@greenie.muc.de>
+---
+ src/openvpn/push.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/src/openvpn/push.c b/src/openvpn/push.c
+index c47f4c8b6..2147aca0c 100644
+--- a/src/openvpn/push.c
++++ b/src/openvpn/push.c
+@@ -732,6 +732,7 @@ int
+ process_incoming_push_request(struct context *c)
+ {
+ int ret = PUSH_MSG_ERROR;
++ struct key_state *ks = &c->c2.tls_multi->session[TM_ACTIVE].key[KS_PRIMARY];
+
+ if (tls_authentication_status(c->c2.tls_multi, 0) == TLS_AUTHENTICATION_FAILED
+ || c->c2.tls_multi->multi_state == CAS_FAILED)
+@@ -740,7 +741,8 @@ process_incoming_push_request(struct context *c)
+ send_auth_failed(c, client_reason);
+ ret = PUSH_MSG_AUTH_FAILURE;
+ }
+- else if (c->c2.tls_multi->multi_state == CAS_SUCCEEDED)
++ else if (c->c2.tls_multi->multi_state == CAS_SUCCEEDED
++ && ks->authenticated == KS_AUTH_TRUE)
+ {
+ time_t now;
+
diff --git a/debian/patches/series b/debian/patches/series
index 55bae8e..692c800 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -5,3 +5,7 @@ openvpn-pkcs11warn.patch
#kfreebsd_support.patch
match-manpage-and-command-help.patch
systemd.patch
+CVE-2020-15078-0.patch
+CVE-2020-15078-1.patch
+CVE-2020-15078-2.patch
+CVE-2020-15078-3.patch