Hi,

On 18/05/2021 14:26, Arne Schwabe wrote:
> 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 <a...@rfc2549.org>
> ---
>  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``

this fix seems unrelated

>    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

this fix seems unrelated

>    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";

This seems unrelated, isn't it?

>  
>          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
> 


The code looks reasonable and I Can't spot anything wrong, other than
what I noted above.

On v2 David already conducted tests and ACK'd this patch, therefore I'd
just consider his reply still valid, since this is just a rebase.

Best Regards,

-- 
Antonio Quartulli


_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to