@@ -14,7 +14,7 @@ fast hardware. SSL/TLS authentication must be used in this mode.
Valid syntax:
::
- auth-gen-token [lifetime] [external-auth]
+ auth-gen-token [lifetime] [renewal-time] [external-auth]
After successful user/password authentication, the OpenVPN server will
with this option generate a temporary authentication token and push that
@@ -31,13 +31,17 @@ fast hardware. SSL/TLS authentication must be used in this mode.
The lifetime is defined in seconds. If lifetime is not set or it is set
to :code:`0`, the token will never expire.
+ If ``renewal-time`` is not set it defaults to ``reneg-sec``.
+
+
The token will expire either after the configured ``lifetime`` of the
token is reached or after not being renewed for more than 2 \*
- ``reneg-sec`` seconds. Clients will be sent renewed tokens on every TLS
- renogiation to keep the client's token updated. This is done to
- invalidate a token if a client is disconnected for a sufficiently long
- time, while at the same time permitting much longer token lifetimes for
- active clients.
+ ``renewal-time`` seconds. Clients will be sent renewed tokens on every TLS
+ renegotiation. If ``renewal-time`` is lower than ``reneg-sec`` the server
+ will push an updated temporary authentication token every ``reneweal-time``
+ seconds. This is done to invalidate a token if a client is disconnected for a
+ sufficiently long time, while at the same time permitting much longer token
+ lifetimes for active clients.
This feature is useful for environments which are configured to use One
Time Passwords (OTP) as part of the user/password authentications and
@@ -174,7 +174,7 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi)
if (multi->auth_token_initial)
{
- /* Just enough space to fit 8 bytes+ 1 extra to decode a non padded
+ /* Just enough space to fit 8 bytes+ 1 extra to decode a non-padded
* base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64
* bytes
*/
@@ -349,11 +349,11 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi,
/* Accept session tokens only if their timestamp is in the acceptable range
* for renegotiations */
bool in_renegotiation_time = now >= timestamp
- && now < timestamp + 2 * session->opt->renegotiate_seconds;
+ && now < timestamp + 2 * session->opt->auth_token_renewal;
if (!in_renegotiation_time)
{
- msg(M_WARN, "Timestamp (%" PRIu64 ") of auth-token is out of the renegotiation window",
+ msg(M_WARN, "Timestamp (%" PRIu64 ") of auth-token is out of the renewal window",
timestamp);
ret |= AUTH_TOKEN_EXPIRED;
}
@@ -416,6 +416,44 @@ wipe_auth_token(struct tls_multi *multi)
}
}
+void
+check_send_auth_token(struct context *c)
+{
+ struct tls_multi *multi = c->c2.tls_multi;
+ struct tls_session *session = &multi->session[TM_ACTIVE];
+
+ if (get_primary_key(multi)->state < S_GENERATED_KEYS
+ || get_primary_key(multi)->authenticated != KS_AUTH_TRUE)
+ {
+ /* the currently active session is still in renegotiation or another
+ * not fully authorized state. We are either very close to a
+ * renegotiation or have deauthorized the client. In both cases
+ * we just ignore the request to send another token
+ */
+ return;
+ }
+
+ if (!multi->auth_token_initial)
+ {
+ msg(D_SHOW_KEYS, "initial auth-token not generated yet, skipping "
+ "auth-token renewal.");
+ return;
+ }
+
+ if (!multi->locked_username)
+ {
+ msg(D_SHOW_KEYS, "username not locked, skipping auth-token renewal.");
+ return;
+ }
+
+ struct user_pass up;
+ strncpynt(up.username, multi->locked_username, sizeof(up.username));
+
+ generate_auth_token(&up, multi);
+
+ resend_auth_token_renegotiation(multi, session);
+}
+
void
resend_auth_token_renegotiation(struct tls_multi *multi, struct tls_session *session)
{
@@ -424,12 +462,10 @@ resend_auth_token_renegotiation(struct tls_multi *multi, struct tls_session *ses
* 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"
+ * Otherwise, the auth-token get pushed out as part of the "normal"
* push-reply
*/
- bool is_renegotiation = session->key[KS_PRIMARY].key_id != 0;
-
- if (multi->auth_token_initial && is_renegotiation)
+ if (multi->auth_token_initial)
{
/*
* We do not explicitly reschedule the sending of the
@@ -46,11 +46,11 @@
* (2 * renogiation timeout)
*
* The session id is a random string of 12 byte (or 16 in base64) that is not
- * used by OpenVPN itself but kept intact so that external logging/managment
- * can track the session multiple reconnects/servers. It is delibrately chosen
+ * used by OpenVPN itself but kept intact so that external logging/management
+ * can track the session multiple reconnects/servers. It is deliberately chosen
* be a multiple of 3 bytes to have a base64 encoding without padding.
*
- * The hmac is calculated over the username contactinated with the
+ * The hmac is calculated over the username concatenated with the
* raw auth-token bytes to include authentication of the username in the token
*
* We encode the auth-token with base64 and then prepend "SESS_ID_" before
@@ -138,4 +138,12 @@ is_auth_token(const char *password)
void
resend_auth_token_renegotiation(struct tls_multi *multi, struct tls_session *session);
+
+/**
+ * Checks if the timer to resend the auth-token has expired and if a new
+ * auth-token should be send to the client and triggers the resending
+ */
+void
+check_send_auth_token(struct context *c);
+
#endif /* AUTH_TOKEN_H */
@@ -42,6 +42,7 @@
#include "common.h"
#include "ssl_verify.h"
#include "dco.h"
+#include "auth_token.h"
#include "memdbg.h"
@@ -684,6 +685,12 @@ process_coarse_timers(struct context *c)
check_add_routes(c);
}
+ /* check if we want to refresh the auth-token */
+ if (event_timeout_trigger(&c->c2.auth_token_renewal_interval, &c->c2.timeval, ETT_DEFAULT))
+ {
+ check_send_auth_token(c);
+ }
+
/* possibly exit due to --inactive */
if (c->options.inactivity_timeout
&& event_timeout_trigger(&c->c2.inactivity_interval, &c->c2.timeval, ETT_DEFAULT))
@@ -1352,6 +1352,18 @@ do_init_timers(struct context *c, bool deferred)
}
}
+ /* If the auth-token renewal interval is shorter than reneg-sec, arm
+ * "auth-token renewal" timer to send additional auth-token to update the
+ * token on the client more often. If not, this happens automatically
+ * at renegotiation time, without needing an extra event.
+ */
+ if (c->options.auth_token_generate
+ && c->options.auth_token_renewal < c->options.renegotiate_seconds)
+ {
+ event_timeout_init(&c->c2.auth_token_renewal_interval,
+ c->options.auth_token_renewal, now);
+ }
+
if (!deferred)
{
/* initialize connection establishment timer */
@@ -3088,6 +3100,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
to.auth_user_pass_file_inline = options->auth_user_pass_file_inline;
to.auth_token_generate = options->auth_token_generate;
to.auth_token_lifetime = options->auth_token_lifetime;
+ to.auth_token_renewal = options->auth_token_renewal;
to.auth_token_call_auth = options->auth_token_call_auth;
to.auth_token_key = c->c1.ks.auth_token_key;
@@ -290,6 +290,9 @@ struct context_2
struct event_timeout session_interval;
+ /* auth token renewal timer */
+ struct event_timeout auth_token_renewal_interval;
+
/* the option strings must match across peers */
char *options_string_local;
char *options_string_remote;
@@ -2632,6 +2632,14 @@ options_postprocess_verify_ce(const struct options *options,
msg(M_USAGE, "--auth-gen-token needs a non-infinite "
"--renegotiate_seconds setting");
}
+ if (options->auth_token_generate && options->auth_token_renewal
+ && options->auth_token_renewal < 2 * options->handshake_window)
+ {
+ msg(M_USAGE, "--auth-gen-token reneweal time needs to be at least "
+ " two times --hand-window (%d).",
+ options->handshake_window);
+
+ }
{
const bool ccnr = (options->auth_user_pass_verify_script
|| PLUGIN_OPTION_LIST(options)
@@ -3745,6 +3753,10 @@ options_postprocess_mutate(struct options *o, struct env_set *es)
foreign_options_copy_dns(o, es);
#endif
}
+ if (o->auth_token_generate && !o->auth_token_renewal)
+ {
+ o->auth_token_renewal = o->renegotiate_seconds;
+ }
pre_connect_save(o);
}
@@ -7469,15 +7481,21 @@ add_option(struct options *options,
VERIFY_PERMISSION(OPT_P_GENERAL);
options->auth_token_generate = true;
options->auth_token_lifetime = p[1] ? positive_atoi(p[1]) : 0;
- if (p[2])
+
+ for (int i = 2; i < MAX_PARMS && p[i] != NULL; i++)
{
- if (streq(p[2], "external-auth"))
+ /* the second parameter can be the reneweal time */
+ if (i == 2 && positive_atoi(p[i]))
+ {
+ options->auth_token_renewal = positive_atoi(p[i]);
+ }
+ else if (streq(p[i], "external-auth"))
{
options->auth_token_call_auth = true;
}
else
{
- msg(msglevel, "Invalid argument to auth-gen-token: %s", p[2]);
+ msg(msglevel, "Invalid argument to auth-gen-token: %s (%d)", p[i], i);
}
}
@@ -515,9 +515,9 @@ struct options
const char *auth_user_pass_verify_script;
bool auth_user_pass_verify_script_via_file;
bool auth_token_generate;
- bool auth_token_gen_secret_file;
bool auth_token_call_auth;
int auth_token_lifetime;
+ int auth_token_renewal;
const char *auth_token_secret_file;
bool auth_token_secret_file_inline;
@@ -3150,8 +3150,12 @@ tls_multi_process(struct tls_multi *multi,
ks->state = S_ERROR;
}
- /* Update auth token on the client if needed */
- resend_auth_token_renegotiation(multi, session);
+ /* Update auth token on the client if needed on renegotiation
+ * (key id !=0) */
+ if (session->key[KS_PRIMARY].key_id != 0)
+ {
+ resend_auth_token_renegotiation(multi, session);
+ }
}
}
}
@@ -196,7 +196,7 @@ struct key_state
{
int state;
/** The state of the auth-token sent from the client */
- int auth_token_state_flags;
+ unsigned int auth_token_state_flags;
/**
* Key id for this key_state, inherited from struct tls_session.
@@ -374,6 +374,7 @@ struct tls_options
* options->auth_token_generate. */
bool auth_token_call_auth; /**< always call normal authentication */
unsigned int auth_token_lifetime;
+ unsigned int auth_token_renewal;
struct key_ctx auth_token_key;
@@ -102,7 +102,8 @@ setup(void **state)
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->renegotiate_seconds = 240;
+ ctx->session->opt->auth_token_renewal = 120;
ctx->session->opt->auth_token_lifetime = 3000;
strcpy(ctx->up.username, "test user name");
@@ -187,8 +188,13 @@ auth_token_test_timeout(void **state)
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 */
+ /* Token no valid for renegotiate_seconds but still for renewal_time */
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);
+
+
+ now = 100000 + 2*ctx->session->opt->auth_token_renewal - 20;
assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),
AUTH_TOKEN_HMAC_OK);
@@ -213,7 +219,7 @@ auth_token_test_timeout(void **state)
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->auth_token_renewal;
}
Currently the life time of the auth-token is tied to the renegotiation time. While this is fine for many setups, some setups prefer a user to be no longer authenticated when the user disconnects from the VPN for a certain amount of time. This commit allows to shorten the renewal time of the auth-token and ensures that the server resends the auth-token often enough over the existing control channel. This way of updating the auth token is a lot more lightweight than the alternative (frequent renegotiations). Patch v2: fix grammar mistakes (thanks Gert), fix unit tests Signed-off-by: Arne Schwabe <arne@rfc2549.org> --- doc/man-sections/server-options.rst | 16 ++++--- src/openvpn/auth_token.c | 50 +++++++++++++++++++--- src/openvpn/auth_token.h | 14 ++++-- src/openvpn/forward.c | 7 +++ src/openvpn/init.c | 13 ++++++ src/openvpn/openvpn.h | 3 ++ src/openvpn/options.c | 24 +++++++++-- src/openvpn/options.h | 2 +- src/openvpn/ssl.c | 8 +++- src/openvpn/ssl_common.h | 3 +- tests/unit_tests/openvpn/test_auth_token.c | 12 ++++-- 11 files changed, 126 insertions(+), 26 deletions(-)