From patchwork Tue May 18 02:26:35 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 1816 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director15.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id aMrRCrKyo2BsDAAAIUCqbw (envelope-from ) for ; Tue, 18 May 2021 08:27:30 -0400 Received: from proxy6.mail.iad3b.rsapps.net ([172.31.255.6]) by director15.mail.ord1d.rsapps.net with LMTP id 6HlXCrKyo2BxXgAAIcMcQg (envelope-from ) for ; Tue, 18 May 2021 08:27:30 -0400 Received: from smtp33.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy6.mail.iad3b.rsapps.net with LMTPS id 6AeUA7Kyo2AgawAARawThA (envelope-from ) for ; Tue, 18 May 2021 08:27: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: smtp33.gate.iad3b.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: 68331fb2-b7d4-11eb-87e0-525400fb5834-1-1 Received: from [216.105.38.7] ([216.105.38.7:52432] helo=lists.sourceforge.net) by smtp33.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 20/15-14053-FA2B3A06; Tue, 18 May 2021 08:27:28 -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.90_1) (envelope-from ) id 1liyoF-00089w-U9; Tue, 18 May 2021 12:26:51 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1liyoE-00089p-SW for openvpn-devel@lists.sourceforge.net; Tue, 18 May 2021 12:26:51 +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=EEipCUax0as04FJ/By3k57LXjYSFBrF6KRWxoCnLKTk=; b=JdaFSUKuLdLUHgrJ2g/zNWQUWO a/vUFuzLRErgc9zAhoLOQhUxuQl70Rc17W24tfBFVucnkzHJjLVo7mxikAnzc3h87suVYg3bvYXwx 72sgwaMjk4Amsragfj9oq7Ctqco0c8+9AQrHotxkBdnU8gh/bqNn4Q8wZ974hTs1ykWg=; 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=EEipCUax0as04FJ/By3k57LXjYSFBrF6KRWxoCnLKTk=; b=YyW0b/guCjZmnkBI0m+4igvmog g+6HYrb5g4roKuvbItZop716ahsTIDCnoTKVhNi+nScLCKfy49b5SGFwSH1fSYvHdsbYdsSddZp9h WubVuzNlvyC6XloI+FTsYhMbGaFMTh+psgZTP4DJHKxSFftLsvaI5FW+KJBhw72TaNJw=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.2) id 1liyo6-00CfOC-Qu for openvpn-devel@lists.sourceforge.net; Tue, 18 May 2021 12:26:52 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.94.2 (FreeBSD)) (envelope-from ) id 1liynz-000M8a-1A for openvpn-devel@lists.sourceforge.net; Tue, 18 May 2021 14:26:35 +0200 Received: (nullmailer pid 2235706 invoked by uid 10006); Tue, 18 May 2021 12:26:35 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Tue, 18 May 2021 14:26:35 +0200 Message-Id: <20210518122635.2235658-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 Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 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 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-Headers-End: 1liyo6-00CfOC-Qu Subject: [Openvpn-devel] [PATCH v3] 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 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 | 65 +++++++++++++++++++++++++++++ src/openvpn/ssl_verify.h | 23 ++++++++++ 10 files changed, 146 insertions(+), 4 deletions(-) diff --git a/doc/man-sections/script-options.rst b/doc/man-sections/script-options.rst index 22990f4f4..f48e5818d 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 1f60f8861..550ce2746 100644 --- a/include/openvpn-plugin.h.in +++ b/include/openvpn-plugin.h.in @@ -84,6 +84,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): @@ -131,7 +135,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 49c742928..ffb3c78d5 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2916,6 +2916,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 db460796a..3be0e2c35 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1327,6 +1327,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); @@ -2470,6 +2471,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"); @@ -7082,6 +7087,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 41e84f7e1..651594ae5 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -449,6 +449,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 234e92b9c..466437284 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"; @@ -125,6 +125,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 85f9488a5..88ed6b24e 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -227,6 +227,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 61cae7419..2560cffd4 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -330,6 +330,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 0b41eea2d..839119df6 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -1337,6 +1337,71 @@ 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 create write " + "username/password to temp file"); + } + + 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 88c3d540a..a5421a9e3 100644 --- a/src/openvpn/ssl_verify.h +++ b/src/openvpn/ssl_verify.h @@ -206,6 +206,29 @@ tls_common_name_hash(const struct tls_multi *multi, const char **cn, uint32_t *c 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