From patchwork Mon Oct 17 09:51:45 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 2820 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.30.191.6]) by backend30.mail.ord1d.rsapps.net with LMTP id GItgLt4lTWPsUAAAIUCqbw (envelope-from ) for ; Mon, 17 Oct 2022 05:52:30 -0400 Received: from proxy19.mail.ord1d.rsapps.net ([172.30.191.6]) by director7.mail.ord1d.rsapps.net with LMTP id kEk+Lt4lTWNsIgAAovjBpQ (envelope-from ) for ; Mon, 17 Oct 2022 05:52:30 -0400 Received: from smtp4.gate.ord1d ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.ord1d.rsapps.net with LMTPS id cM4GLt4lTWP/VAAAyH2SIw (envelope-from ) for ; Mon, 17 Oct 2022 05:52:30 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp4.gate.ord1d.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 69d52c4c-4e01-11ed-9043-525400760ffc-1-1 Received: from [216.105.38.7] ([216.105.38.7:34470] helo=lists.sourceforge.net) by smtp4.gate.ord1d.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 90/B3-31125-ED52D436; Mon, 17 Oct 2022 05:52:30 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1okMmx-0003VM-9k; Mon, 17 Oct 2022 09:52:03 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1okMmr-0003Ut-N7 for openvpn-devel@lists.sourceforge.net; Mon, 17 Oct 2022 09:51:57 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=lvJ5zgJXQJ+bgavGjyh4stp0W7xKfYwkUsd2laigK30=; b=iPNEuTI3NNIUwjpFSDrsl+6hby S1Sj8l8x4ZP7LPSJ26PWShNNUyIPgR3yzbElDapPRhCI8ibRe8XOEXcCrzbt1JhQqoM85g2ATs+N4 hKvVM2rcIhWslBZZD7LGQkGt8GLjpBFrSfOlq8CdxujQ2IIzil+X9swl1FLDgCI+KAr0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=lvJ5zgJXQJ+bgavGjyh4stp0W7xKfYwkUsd2laigK30=; b=OgqrDeaY0MKwA3lq0toXT7j/Kw oDKMi0Z8CztwdH0lVsGTXm0JN0VTqqqHf01zufIp3clkJPCMt3P2Rg0Uf2LAdCLl17uuMo+JH8GQg eHQkQxsTskrXkXfIq2+JkDaBEdsMX4ezERiICcSKX6mkbwuqOvbRj6D9gsxzBVkLyD0Y=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1okMmp-00012E-DZ for openvpn-devel@lists.sourceforge.net; Mon, 17 Oct 2022 09:51:57 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1okMmf-0004Mn-Ia for openvpn-devel@lists.sourceforge.net; Mon, 17 Oct 2022 11:51:45 +0200 Received: (nullmailer pid 2580232 invoked by uid 10006); Mon, 17 Oct 2022 09:51:45 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 17 Oct 2022 11:51:45 +0200 Message-Id: <20221017095145.2580186-1-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221007153823.1334940-1-arne@rfc2549.org> References: <20221007153823.1334940-1-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: 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 [...] Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 SPF_NONE SPF: sender does not publish an SPF Record X-Headers-End: 1okMmp-00012E-DZ Subject: [Openvpn-devel] [PATCH v2] Allow Authtoken lifetime to be short than renegotiation time X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox 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 Acked-by: Gert Doering --- 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(-) diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index 9d0c73b69..99263fff3 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -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 diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c index b5f9f6dd7..7b963a9c5 100644 --- a/src/openvpn/auth_token.c +++ b/src/openvpn/auth_token.c @@ -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 diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h index 50b90cfa4..5e23d8c44 100644 --- a/src/openvpn/auth_token.h +++ b/src/openvpn/auth_token.h @@ -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 */ diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index bee24f0d4..20d9a7598 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -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)) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 5141a35c2..ed2ccb815 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -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; diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index 1b699b93a..c543cbf60 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -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; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 52b861abc..60fec147f 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -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); } } diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 3d1d37d05..55322baa0 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -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; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 5ed71f0c5..66aa9da3d 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -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); + } } } } diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 9aa28f1e5..6135556a9 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -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; diff --git a/tests/unit_tests/openvpn/test_auth_token.c b/tests/unit_tests/openvpn/test_auth_token.c index 5299c3643..57e98f541 100644 --- a/tests/unit_tests/openvpn/test_auth_token.c +++ b/tests/unit_tests/openvpn/test_auth_token.c @@ -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; }