From: Arne Schwabe Date: Wed, 10 Oct 2018 14:30:51 +0000 (+0200) Subject: Fallback to password authentication when auth-token fails X-Git-Tag: v2.4.7~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=004f13b60d87fe7815f4feee9aded22ae4eacbaf;p=thirdparty%2Fopenvpn.git Fallback to password authentication when auth-token fails Under some circumstances, the auth-token stored by a client may not be valid anymore. For example, if the server is restarted, the client will try to reconnect and resend the old token during authentication. Unfortunately this attempt will fail, because the server does not keep track of tokens used during previous runs. With the current behaviour, depending on how auth-retry was configured, the client will either just quit, prompt the user for username and password as the original values are overwritten by the token, or endelessly fail authentication by sending the old token over and over. This patch changes the behaviour of the client so that, upon failed authentication using a token, it will drop the token, perform a soft restart (USR1) and attempt re-authenticating with the original password provided by the user if auth-nocache was not specified. Patch V2: properly formatted commit message, fix openvpn3 detection Patch V3: remove all server changes, include only minimal non intrusive client changes that only improve error recovery but don't change overall behaviour. Patch V4: forget add push.c to git index, now also included Patch V5: is fixing overlong lines and one minor style problem. Signed-off-by: Arne Schwabe Acked-by: Antonio Quartulli Message-Id: <20181010143051.27163-1-arne@rfc2549.org> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg17718.html Signed-off-by: Gert Doering (cherry picked from commit e61b401ac50d2a9cfabf0289811ad14cf3bd2751) --- diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 2d088e5f5..78017010f 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5338,6 +5338,12 @@ into the file/buffer for dynamic configuration data. This will then make the OpenVPN server to push this value to the client, which replaces the local password with the UNIQUE_TOKEN_VALUE. + +Newer clients (2.4.7+) will fall back to the original password method +after a failed auth. Older clients will keep using the token value +and react acording to +.B \-\-auth-retry +. .\"********************************************************* .TP .B \-\-tls\-verify cmd diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index 77bb67135..581a8908e 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -1261,7 +1261,7 @@ purge_user_pass(struct user_pass *up, const bool force) * don't show warning if the pass has been replaced by a token: this is an * artificial "auth-nocache" */ - else if (!warn_shown && (!up->tokenized)) + else if (!warn_shown) { msg(M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this"); warn_shown = true; @@ -1269,14 +1269,18 @@ purge_user_pass(struct user_pass *up, const bool force) } void -set_auth_token(struct user_pass *up, const char *token) +set_auth_token(struct user_pass *up, struct user_pass *tk, const char *token) { - if (token && strlen(token) && up && up->defined && !up->nocache) + + if (token && strlen(token) && up && up->defined) { - CLEAR(up->password); - strncpynt(up->password, token, USER_PASS_LEN); - up->tokenized = true; + strncpynt(tk->password, token, USER_PASS_LEN); + strncpynt(tk->username, up->username, USER_PASS_LEN); + tk->defined = true; } + + /* Cleans user/pass for nocache */ + purge_user_pass(up, false); } /* diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 9f358ae47..a64ddcc71 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -173,7 +173,6 @@ struct user_pass { bool defined; bool nocache; - bool tokenized; /* true if password has been substituted by a token */ bool wait_for_push; /* true if this object is waiting for a push-reply */ /* max length of username/password */ @@ -255,7 +254,8 @@ void fail_user_pass(const char *prefix, void purge_user_pass(struct user_pass *up, const bool force); -void set_auth_token(struct user_pass *up, const char *token); +void set_auth_token(struct user_pass *up, struct user_pass *tk, + const char *token); /* * Process string received by untrusted peer before diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 6a30e4792..dd5bd4163 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -55,8 +55,20 @@ receive_auth_failed(struct context *c, const struct buffer *buffer) if (c->options.pull) { - switch (auth_retry_get()) + /* Before checking how to react on AUTH_FAILED, first check if the + * failed auth might be the result of an expired auth-token. + * Note that a server restart will trigger a generic AUTH_FAILED + * instead an AUTH_FAILED,SESSION so handle all AUTH_FAILED message + * identical for this scenario */ + if (ssl_clean_auth_token()) { + c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- Auth failure error */ + c->sig->signal_text = "auth-failure (auth-token)"; + } + else + { + switch (auth_retry_get()) + { case AR_NONE: c->sig->signal_received = SIGTERM; /* SOFT-SIGTERM -- Auth failure error */ break; @@ -70,8 +82,9 @@ receive_auth_failed(struct context *c, const struct buffer *buffer) default: ASSERT(0); + } + c->sig->signal_text = "auth-failure"; } - c->sig->signal_text = "auth-failure"; #ifdef ENABLE_MANAGEMENT if (management) { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index ca326182e..9696e9bab 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -400,6 +400,7 @@ pem_password_callback(char *buf, int size, int rwflag, void *u) static bool auth_user_pass_enabled; /* GLOBAL */ static struct user_pass auth_user_pass; /* GLOBAL */ +static struct user_pass auth_token; /* GLOBAL */ #ifdef ENABLE_CLIENT_CR static char *auth_challenge; /* GLOBAL */ @@ -409,7 +410,7 @@ void auth_user_pass_setup(const char *auth_file, const struct static_challenge_info *sci) { auth_user_pass_enabled = true; - if (!auth_user_pass.defined) + if (!auth_user_pass.defined && !auth_token.defined) { #if AUTO_USERID get_user_pass_auto_userid(&auth_user_pass, auth_file); @@ -451,7 +452,7 @@ ssl_set_auth_nocache(void) { passbuf.nocache = true; auth_user_pass.nocache = true; - /* wait for push-reply, because auth-token may invert nocache */ + /* wait for push-reply, because auth-token may still need the username */ auth_user_pass.wait_for_push = true; } @@ -461,15 +462,18 @@ ssl_set_auth_nocache(void) void ssl_set_auth_token(const char *token) { - if (auth_user_pass.nocache) - { - msg(M_INFO, - "auth-token received, disabling auth-nocache for the " - "authentication token"); - auth_user_pass.nocache = false; - } + set_auth_token(&auth_user_pass, &auth_token, token); +} - set_auth_token(&auth_user_pass, token); +/* + * Cleans an auth token and checks if it was active + */ +bool +ssl_clean_auth_token (void) +{ + bool wasdefined = auth_token.defined; + purge_user_pass(&auth_token, true); + return wasdefined; } /* @@ -2382,19 +2386,26 @@ key_method_2_write(struct buffer *buf, struct tls_session *session) #else auth_user_pass_setup(session->opt->auth_user_pass_file, NULL); #endif - if (!write_string(buf, auth_user_pass.username, -1)) + struct user_pass *up = &auth_user_pass; + + /* + * If we have a valid auth-token, send that instead of real + * username/password + */ + if (auth_token.defined) + up = &auth_token; + + if (!write_string(buf, up->username, -1)) { goto error; } - if (!write_string(buf, auth_user_pass.password, -1)) + else if (!write_string(buf, up->password, -1)) { goto error; } /* if auth-nocache was specified, the auth_user_pass object reaches * a "complete" state only after having received the push-reply * message. - * This is the case because auth-token statement in a push-reply would - * invert its nocache. * * For this reason, skip the purge operation here if no push-reply * message has been received yet. diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index a724419fc..8066789b6 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -438,6 +438,8 @@ void ssl_set_auth_token(const char *token); */ void ssl_purge_auth_challenge(void); +bool ssl_clean_auth_token(void); + void ssl_put_auth_challenge(const char *cr_str); #endif