Package: release.debian.org
Severity: normal
User: release.debian....@packages.debian.org
Usertags: unblock

Please unblock package openvpn

[ Reason ]
Fix CVE-2020-15078, an authentication bypass on servers configured
with deferred-authentication, i.e. with 2FA plugins. It was marked
no-dsa by the security team.

[ Impact ]
Security issue to be fixed later with a stable update.

[ Tests ]
Test-suite is run during build and has been adapted for this security fix

autopkgtest has been added a while ago but is still broken

- not executed (never passed) on debci because the test is tagged
  isolation-machine which debci does not support
- if executed manually with qemu there is an issue with the CA test that still
  needs to be debugged, see #983662

My local installation has been upgraded and worked fine.

[ Risks ]
Targeted security bugfix

[ Checklist ]
  [X] all changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in testing

[ Other info ]
I would also consider upgrading to 2.5.2, which does not have that many
fixes over 2.5.1 + cherry-picked patches. 

---
Arne Schwabe (10):
      Avoid generating unecessary mbed debug messages
      Restore also ping related options on a reconnect
      Cleanup print_details and add signature/ED certificate print
      Always disable TLS renegotiations
      Also restore/save route-gateway options on SIGUSR1 reconnects
      Move context_auth from context_2 to tls_multi and name it multi_state   
<--- cherry-picked in 2.5.1-2
      Fix condition to generate session keys
      Move auth_token_state from multi to key_state                           
<--- cherry-picked in 2.5.1-2
      Ensure auth-token is only sent on a fully authenticated session         
<--- cherry-picked in 2.5.1-2
      Ensure key state is authenticated before sending push reply             
<--- cherry-picked in 2.5.1-2

Gert Doering (2):
      Fix potential NULL ptr crash if compiled with DMALLOC
      Preparing release 2.5.2

Max Fillinger (2):
      In init_ssl, open the correct CRL path pre-chroot
      Abort if CRL file can't be stat-ed in ssl_init

Richard Bonhomme (1):
      Do not print Diffie Hellman parameters file to log file

Simon Rozman (1):
      openvpnserv: Cache last error before it is overridden

Vladislav Grishenko (1):
      Fix IPv4 default gateway with multiple route tables
---

unblock openvpn/2.5.1-2
diff -Nru openvpn-2.5.1/debian/changelog openvpn-2.5.1/debian/changelog
--- openvpn-2.5.1/debian/changelog      2021-02-24 19:54:34.000000000 +0100
+++ openvpn-2.5.1/debian/changelog      2021-04-28 14:41:58.000000000 +0200
@@ -1,3 +1,11 @@
+openvpn (2.5.1-2) unstable; urgency=high
+
+  * Cherry-Pick 3 (+ 1 predependency) patches from upstream to fix
+    authentication bypass with deferred authentication
+    (CVE-2020-15078) (Closes: #987380)
+
+ -- Bernhard Schmidt <be...@debian.org>  Wed, 28 Apr 2021 14:41:58 +0200
+
 openvpn (2.5.1-1) unstable; urgency=medium
 
   * New upstream version 2.5.1 (bugfix release)
diff -Nru openvpn-2.5.1/debian/patches/CVE-2020-15078-0.patch 
openvpn-2.5.1/debian/patches/CVE-2020-15078-0.patch
--- openvpn-2.5.1/debian/patches/CVE-2020-15078-0.patch 1970-01-01 
01:00:00.000000000 +0100
+++ openvpn-2.5.1/debian/patches/CVE-2020-15078-0.patch 2021-04-28 
14:41:58.000000000 +0200
@@ -0,0 +1,276 @@
+From 145110101b70599cb9adcf4ed071856daac9c8af Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <a...@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 <a...@rfc2549.org>
+Acked-by: Antonio Quartulli <anto...@openvpn.net>
+Acked-by: Gert Doering <g...@greenie.muc.de>
+Message-Id: <20210418160111.1494779-1-a...@rfc2549.org>
+URL: 
https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg22155.html
+Signed-off-by: Gert Doering <g...@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 -Nru openvpn-2.5.1/debian/patches/CVE-2020-15078-1.patch 
openvpn-2.5.1/debian/patches/CVE-2020-15078-1.patch
--- openvpn-2.5.1/debian/patches/CVE-2020-15078-1.patch 1970-01-01 
01:00:00.000000000 +0100
+++ openvpn-2.5.1/debian/patches/CVE-2020-15078-1.patch 2021-04-28 
14:41:58.000000000 +0200
@@ -0,0 +1,390 @@
+From 3aca477a1b58714754fea3a26d0892fffc51db6b Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <a...@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 <a...@rfc2549.org>
+Acked-by: Antonio Quartulli <anto...@openvpn.net>
+Message-Id: <d25ec73f-2ab0-31df-8cb6-7778000f4...@openvpn.net>
+URL: non-public, embargoed
+Signed-off-by: Gert Doering <g...@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 -Nru openvpn-2.5.1/debian/patches/CVE-2020-15078-2.patch 
openvpn-2.5.1/debian/patches/CVE-2020-15078-2.patch
--- openvpn-2.5.1/debian/patches/CVE-2020-15078-2.patch 1970-01-01 
01:00:00.000000000 +0100
+++ openvpn-2.5.1/debian/patches/CVE-2020-15078-2.patch 2021-04-28 
14:41:58.000000000 +0200
@@ -0,0 +1,125 @@
+From 3d18e308c4e7e6f7ab7c2826c70d2d07b031c18a Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <a...@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 <a...@rfc2549.org>
+Acked-by: Antonio Quartulli <anto...@openvpn.net>
+Message-Id: <d25ec73f-2ab0-31df-8cb6-7778000f4...@openvpn.net>
+URL: non-public, embargoed
+Signed-off-by: Gert Doering <g...@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 -Nru openvpn-2.5.1/debian/patches/CVE-2020-15078-3.patch 
openvpn-2.5.1/debian/patches/CVE-2020-15078-3.patch
--- openvpn-2.5.1/debian/patches/CVE-2020-15078-3.patch 1970-01-01 
01:00:00.000000000 +0100
+++ openvpn-2.5.1/debian/patches/CVE-2020-15078-3.patch 2021-04-28 
14:41:58.000000000 +0200
@@ -0,0 +1,51 @@
+From f7b3bf067ffce72e7de49a4174fd17a3a83f0573 Mon Sep 17 00:00:00 2001
+From: Arne Schwabe <a...@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 <a...@rfc2549.org>
+Acked-by: Gert Doering <g...@greenie.muc.de>
+Message-Id: <d25ec73f-2ab0-31df-8cb6-7778000f4...@openvpn.net>
+URL: non-public, embargoed
+Signed-off-by: Gert Doering <g...@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 -Nru openvpn-2.5.1/debian/patches/series 
openvpn-2.5.1/debian/patches/series
--- openvpn-2.5.1/debian/patches/series 2021-02-24 19:54:34.000000000 +0100
+++ openvpn-2.5.1/debian/patches/series 2021-04-28 14:41:58.000000000 +0200
@@ -5,3 +5,7 @@
 #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

Reply via email to