From patchwork Thu Aug 18 23:51:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 2701 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.27.255.58]) by backend30.mail.ord1d.rsapps.net with LMTP id wPELAXRd/2LoRQAAIUCqbw (envelope-from ) for ; Fri, 19 Aug 2022 05:52:52 -0400 Received: from proxy10.mail.iad3a.rsapps.net ([172.27.255.58]) by director7.mail.ord1d.rsapps.net with LMTP id wOHkAHRd/2K4YAAAovjBpQ (envelope-from ) for ; Fri, 19 Aug 2022 05:52:52 -0400 Received: from smtp35.gate.iad3a ([172.27.255.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy10.mail.iad3a.rsapps.net with LMTPS id 4KTMNXNd/2IEWQAAnQ/bqA (envelope-from ) for ; Fri, 19 Aug 2022 05:52:51 -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: smtp35.gate.iad3a.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: aff86dd2-1fa4-11ed-85cb-52540083445f-1-1 Received: from [216.105.38.7] ([216.105.38.7:43620] helo=lists.sourceforge.net) by smtp35.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 08/90-27731-37D5FF26; Fri, 19 Aug 2022 05:52:51 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1oOyfI-00035L-Ug; Fri, 19 Aug 2022 09:51:44 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1oOyfI-00035E-4F for openvpn-devel@lists.sourceforge.net; Fri, 19 Aug 2022 09:51:44 +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=msIFUnZt+b8efPop8rqiyg9sNURBUZfptp+Nk/2IU90=; b=VQz/euejLszK7LHxSgvm06LYOB 4EwqGH9DBV0zhwVYgB6rL0bIpPyKXjT8xshNOmdOvPyHFSdMLVvHhbv4vfxMrJ9PPJH4IbVolkvb2 Aw4FYEVgbltz8lkNclylDawgjQFYjh0QZm3PXRUHxkWzMKvx2ra66Eq6Ms81GGdc7ZjU=; 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=msIFUnZt+b8efPop8rqiyg9sNURBUZfptp+Nk/2IU90=; b=hq399sNUzo9EScZ2GTw8FkDxus E4SjB26NdSZi0+1mhHx3dZAj9aSVHQ9U5dDlI5IP/6N4ESCkKIBmMgaSpozl6Iezl9XXIfQsVaQwG ikykwUoVLkn6y63DQiNB5XVz5qBWC5Q2OUg2AZ6KvfHc05He02zYjdmBDWnSGaFuEMB0=; 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 1oOyfE-0003bM-76 for openvpn-devel@lists.sourceforge.net; Fri, 19 Aug 2022 09:51:44 +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 1oOyf6-000ANg-Uo for openvpn-devel@lists.sourceforge.net; Fri, 19 Aug 2022 11:51:32 +0200 Received: (nullmailer pid 94919 invoked by uid 10006); Fri, 19 Aug 2022 09:51:32 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 19 Aug 2022 11:51:32 +0200 Message-Id: <20220819095132.94873-1-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210125125628.30364-11-arne@rfc2549.org> References: <20210125125628.30364-11-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-1.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: This is allows scripts and pluginsto parse/react to a CR_RESPONSE message Patch V2: doc fixes, do not put script under ENABLE_PLUGIN Patch V3: rebase Patch V4: fix else branch of the verify_crresponse_script function Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record X-Headers-End: 1oOyfE-0003bM-76 Subject: [Openvpn-devel] [PATCH v4] Implement --client-crresponse script options and plugin interface 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 This is allows scripts and pluginsto parse/react to a CR_RESPONSE message Patch V2: doc fixes, do not put script under ENABLE_PLUGIN Patch V3: rebase Patch V4: fix else branch of the verify_crresponse_script function Signed-off-by: Arne Schwabe --- doc/man-sections/script-options.rst | 28 +++++++++++- include/openvpn-plugin.h.in | 7 ++- src/openvpn/init.c | 1 + src/openvpn/options.c | 15 +++++++ src/openvpn/options.h | 1 + src/openvpn/plugin.c | 5 ++- src/openvpn/push.c | 4 ++ src/openvpn/ssl_common.h | 1 + src/openvpn/ssl_verify.c | 67 +++++++++++++++++++++++++++++ src/openvpn/ssl_verify.h | 23 ++++++++++ 10 files changed, 148 insertions(+), 4 deletions(-) diff --git a/doc/man-sections/script-options.rst b/doc/man-sections/script-options.rst index 6be0686d7..74c6a1fc6 100644 --- a/doc/man-sections/script-options.rst +++ b/doc/man-sections/script-options.rst @@ -52,6 +52,11 @@ Script Order of Execution Executed in ``--mode server`` mode on new client connections, when the client is still untrusted. +#. ``--client-crresponse`` + + Execute in ``--mode server`` whenever a client sends a + :code:`CR_RESPONSE` message + SCRIPT HOOKS ------------ @@ -72,7 +77,7 @@ SCRIPT HOOKS double-quoted and/or escaped using a backslash, and should be separated by one or more spaces. - If ``method`` is set to :code:`via-env`, OpenVPN will call ``script`` + If ``method`` is set to :code:`via-env`, OpenVPN will call ``cmd`` with the environmental variables :code:`username` and :code:`password` set to the username/password strings provided by the client. *Beware* that this method is insecure on some platforms which make the environment @@ -80,7 +85,7 @@ SCRIPT HOOKS If ``method`` is set to :code:`via-file`, OpenVPN will write the username and password to the first two lines of a temporary file. The filename - will be passed as an argument to ``script``, and the file will be + will be passed as an argument to ``cmd``, and the file will be automatically deleted by OpenVPN after the script returns. The location of the temporary file is controlled by the ``--tmp-dir`` option, and will default to the current directory if unspecified. For security, @@ -123,6 +128,25 @@ SCRIPT HOOKS For a sample script that performs PAM authentication, see :code:`sample-scripts/auth-pam.pl` in the OpenVPN source distribution. +--client-crresponse + Executed when the client sends a text based challenge response. + + Valid syntax: + :: + + client-crresponse cmd + + OpenVPN will write the response of the client into a temporary file. + The filename will be passed as an argument to ``cmd``, and the file will be + automatically deleted by OpenVPN after the script returns. + + The response is passed as is from the client. The script needs to check + itself if the input is valid, e.g. if the input is valid base64 encoding. + + The script can either directly write the result of the verification to + :code:`auth_control_file or further defer it. See ``--auth-user-pass-verify`` + for details. + --client-connect cmd Run command ``cmd`` on client connection. diff --git a/include/openvpn-plugin.h.in b/include/openvpn-plugin.h.in index dc7c5306f..e498f94db 100644 --- a/include/openvpn-plugin.h.in +++ b/include/openvpn-plugin.h.in @@ -83,6 +83,10 @@ extern "C" { * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_CONNECT_V2 * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_LEARN_ADDRESS * + * The OPENVPN_PLUGIN_CLIENT_CRRESPONSE function is called when the client sends + * the CR_RESPONSE message, this is *typically* after OPENVPN_PLUGIN_TLS_FINAL + * but may also occur much later. + * * [Client session ensues] * * For each "TLS soft reset", according to reneg-sec option (or similar): @@ -128,7 +132,8 @@ extern "C" { #define OPENVPN_PLUGIN_ROUTE_PREDOWN 12 #define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER 13 #define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 14 -#define OPENVPN_PLUGIN_N 15 +#define OPENVPN_PLUGIN_CLIENT_CRRESPONSE 15 +#define OPENVPN_PLUGIN_N 16 /* * Build a mask out of a set of plug-in types. diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 05c2ee9bc..b06406b95 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -3029,6 +3029,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) to.auth_user_pass_verify_script = options->auth_user_pass_verify_script; to.auth_user_pass_verify_script_via_file = options->auth_user_pass_verify_script_via_file; + to.client_crresponse_script = options->client_crresponse_script; to.tmp_dir = options->tmp_dir; if (options->ccd_exclusive) { diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 2415c1a84..5d181f6b8 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1525,6 +1525,7 @@ show_p2mp_parms(const struct options *o) SHOW_STR(client_connect_script); SHOW_STR(learn_address_script); SHOW_STR(client_disconnect_script); + SHOW_STR(client_crresponse_script); SHOW_STR(client_config_dir); SHOW_BOOL(ccd_exclusive); SHOW_STR(tmp_dir); @@ -2684,6 +2685,10 @@ options_postprocess_verify_ce(const struct options *options, { msg(M_USAGE, "--client-connect requires --mode server"); } + if (options->client_crresponse_script) + { + msg(M_USAGE, "--client-crresponse requires --mode server"); + } if (options->client_disconnect_script) { msg(M_USAGE, "--client-disconnect requires --mode server"); @@ -7441,6 +7446,16 @@ add_option(struct options *options, set_user_script(options, &options->client_connect_script, p[1], "client-connect", true); } + else if (streq(p[0], "client-crresponse") && p[1]) + { + VERIFY_PERMISSION(OPT_P_SCRIPT); + if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT)) + { + goto err; + } + set_user_script(options, &options->client_crresponse_script, + p[1], "client-crresponse", true); + } else if (streq(p[0], "client-disconnect") && p[1]) { VERIFY_PERMISSION(OPT_P_SCRIPT); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 83c97ded1..b4f4dfc17 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -473,6 +473,7 @@ struct options const char *client_connect_script; const char *client_disconnect_script; const char *learn_address_script; + const char *client_crresponse_script; const char *client_config_dir; bool ccd_exclusive; bool disable; diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c index 51136feae..4c2fec068 100644 --- a/src/openvpn/plugin.c +++ b/src/openvpn/plugin.c @@ -102,7 +102,7 @@ plugin_type_name(const int type) return "PLUGIN_CLIENT_CONNECT"; case OPENVPN_PLUGIN_CLIENT_CONNECT_V2: - return "PLUGIN_CLIENT_CONNECT"; + return "PLUGIN_CLIENT_CONNECT_V2"; case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER: return "PLUGIN_CLIENT_CONNECT_DEFER"; @@ -122,6 +122,9 @@ plugin_type_name(const int type) case OPENVPN_PLUGIN_ROUTE_PREDOWN: return "PLUGIN_ROUTE_PREDOWN"; + case OPENVPN_PLUGIN_CLIENT_CRRESPONSE: + return "PLUGIN_CRRESPONSE"; + default: return "PLUGIN_???"; } diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 121ea691a..4cbc89e4c 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -229,6 +229,10 @@ receive_cr_response(struct context *c, const struct buffer *buffer) management_notify_client_cr_response(key_id, mda, es, m); #endif +#if ENABLE_PLUGIN + verify_crresponse_plugin(c->c2.tls_multi, m); +#endif + verify_crresponse_script(c->c2.tls_multi, m); msg(D_PUSH, "CR response was sent by client ('%s')", m); } diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index c565d78c1..bf3ac67a5 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -362,6 +362,7 @@ struct tls_options /* used for username/password authentication */ const char *auth_user_pass_verify_script; + const char *client_crresponse_script; bool auth_user_pass_verify_script_via_file; const char *tmp_dir; const char *auth_user_pass_file; diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index c01841fa9..5152eb4f3 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -1352,6 +1352,73 @@ done: return retval; } +#ifdef ENABLE_PLUGIN +void +verify_crresponse_plugin(struct tls_multi *multi, const char *cr_response) +{ + struct tls_session *session = &multi->session[TM_ACTIVE]; + setenv_str(session->opt->es, "crresponse", cr_response); + + plugin_call(session->opt->plugins, OPENVPN_PLUGIN_CLIENT_CRRESPONSE, NULL, + NULL, session->opt->es); + + setenv_del(session->opt->es, "crresponse"); +} +#endif + +void +verify_crresponse_script(struct tls_multi *multi, const char *cr_response) +{ + + struct tls_session *session = &multi->session[TM_ACTIVE]; + + if (!session->opt->client_crresponse_script) + { + return; + } + struct argv argv = argv_new(); + struct gc_arena gc = gc_new(); + + setenv_str(session->opt->es, "script_type", "client-crresponse"); + + /* Since cr response might be sensitive, like a stupid way to query + * a password via 2FA, we pass it via file instead environment */ + const char *tmp_file = platform_create_temp_file(session->opt->tmp_dir, "cr", &gc); + if (tmp_file) + { + struct status_output *so = status_open(tmp_file, 0, -1, NULL, + STATUS_OUTPUT_WRITE); + status_printf(so, "%s", cr_response); + if (!status_close(so)) + { + msg(D_TLS_ERRORS, "TLS CR Response Error: could not write cr" + "responsed to file: %s", + tmp_file); + tls_deauthenticate(multi); + goto done; + } + } + else + { + msg(D_TLS_ERRORS, "TLS Auth Error: could not write " + "username/password to cr response temp file"); + tls_deauthenticate(multi); + goto done; + } + + argv_parse_cmd(&argv, session->opt->client_crresponse_script); + argv_printf_cat(&argv, "%s", tmp_file); + + + if (!openvpn_run_script(&argv, session->opt->es, 0, "--client-crresponse")) + { + tls_deauthenticate(multi); + } +done: + argv_free(&argv); + gc_free(&gc); +} + /* * Verify the username and password using a plugin */ diff --git a/src/openvpn/ssl_verify.h b/src/openvpn/ssl_verify.h index 15ef0b40d..30dfc9bc3 100644 --- a/src/openvpn/ssl_verify.h +++ b/src/openvpn/ssl_verify.h @@ -177,6 +177,29 @@ bool cert_hash_compare(const struct cert_hash_set *chs1, const struct cert_hash_ void verify_user_pass(struct user_pass *up, struct tls_multi *multi, struct tls_session *session); + + +/** + * Runs the --client-crresponse script if one is defined. + * + * As with the management interface the script is stateless in the sense that + * it does not directly participate in the authentication but rather should set + * the files for the deferred auth like the management commands. + * + */ +void +verify_crresponse_script(struct tls_multi *multi, const char *cr_response); + +/** + * Call the plugin OPENVPN_PLUGIN_CLIENT_CRRESPONSE. + * + * As with the management interface calling the plugin is stateless in the sense + * that it does not directly participate in the authentication but rather + * should set the files for the deferred auth like the management commands. + */ +void +verify_crresponse_plugin(struct tls_multi *multi, const char *cr_response); + /** * Perform final authentication checks, including locking of the cn, the allowed * certificate hashes, and whether a client config entry exists in the