summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernhard Schmidt <berni@debian.org>2021-04-28 14:38:07 +0200
committerBernhard Schmidt <berni@debian.org>2021-04-28 15:12:01 +0200
commita398f557fd1320096e140f8ca297481ae75e12b3 (patch)
tree120765e28976d039124f6962e2d2e7ee554e1b3c
parenta8b5c8b8223889ccbb3f415ba206027a4f1b3b67 (diff)
CVE-2020-15078: Authentication bypass with deferred authentication
Overview OpenVPN 2.5.1 and earlier versions allows a remote attackers to bypass authentication and access control channel data on servers configured with deferred authentication, which can be used to potentially trigger further information leaks. Detailed description 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 a user-specific token auth solution it can be possible to get access to a VPN with an otherwise-invalid account. Pre-Dependency: CVE-2020-15078-0.patch: https://github.com/OpenVPN/openvpn/commit/14511010 CVE-Fix: CVE-2020-15078-1.patch: https://github.com/OpenVPN/openvpn/commit/3aca477a CVE-2020-15078-2.patch: https://github.com/OpenVPN/openvpn/commit/3d18e308 CVE-2020-15078-3.patch: https://github.com/OpenVPN/openvpn/commit/f7b3bf06 Closes: #987380
-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