This allows scripts and plugins to 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
Patch V5: unify message when unable to create/write crresponse file
Signed-off-by: Arne Schwabe <arne@rfc2549.org>
Acked-by: Heiko Hund <heiko@ist.eigentlich.net>
Message-Id: <20220824110930.73009-1-arne@rfc2549.org>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg25089.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
| ... | ... |
@@ -52,6 +52,11 @@ Script Order of Execution |
| 52 | 52 |
Executed in ``--mode server`` mode on new client connections, when the |
| 53 | 53 |
client is still untrusted. |
| 54 | 54 |
|
| 55 |
+#. ``--client-crresponse`` |
|
| 56 |
+ |
|
| 57 |
+ Execute in ``--mode server`` whenever a client sends a |
|
| 58 |
+ :code:`CR_RESPONSE` message |
|
| 59 |
+ |
|
| 55 | 60 |
SCRIPT HOOKS |
| 56 | 61 |
------------ |
| 57 | 62 |
|
| ... | ... |
@@ -72,7 +77,7 @@ SCRIPT HOOKS |
| 72 | 72 |
double-quoted and/or escaped using a backslash, and should be separated |
| 73 | 73 |
by one or more spaces. |
| 74 | 74 |
|
| 75 |
- If ``method`` is set to :code:`via-env`, OpenVPN will call ``script`` |
|
| 75 |
+ If ``method`` is set to :code:`via-env`, OpenVPN will call ``cmd`` |
|
| 76 | 76 |
with the environmental variables :code:`username` and :code:`password` |
| 77 | 77 |
set to the username/password strings provided by the client. *Beware* |
| 78 | 78 |
that this method is insecure on some platforms which make the environment |
| ... | ... |
@@ -80,7 +85,7 @@ SCRIPT HOOKS |
| 80 | 80 |
|
| 81 | 81 |
If ``method`` is set to :code:`via-file`, OpenVPN will write the username |
| 82 | 82 |
and password to the first two lines of a temporary file. The filename |
| 83 |
- will be passed as an argument to ``script``, and the file will be |
|
| 83 |
+ will be passed as an argument to ``cmd``, and the file will be |
|
| 84 | 84 |
automatically deleted by OpenVPN after the script returns. The location |
| 85 | 85 |
of the temporary file is controlled by the ``--tmp-dir`` option, and |
| 86 | 86 |
will default to the current directory if unspecified. For security, |
| ... | ... |
@@ -123,6 +128,25 @@ SCRIPT HOOKS |
| 123 | 123 |
For a sample script that performs PAM authentication, see |
| 124 | 124 |
:code:`sample-scripts/auth-pam.pl` in the OpenVPN source distribution. |
| 125 | 125 |
|
| 126 |
+--client-crresponse |
|
| 127 |
+ Executed when the client sends a text based challenge response. |
|
| 128 |
+ |
|
| 129 |
+ Valid syntax: |
|
| 130 |
+ :: |
|
| 131 |
+ |
|
| 132 |
+ client-crresponse cmd |
|
| 133 |
+ |
|
| 134 |
+ OpenVPN will write the response of the client into a temporary file. |
|
| 135 |
+ The filename will be passed as an argument to ``cmd``, and the file will be |
|
| 136 |
+ automatically deleted by OpenVPN after the script returns. |
|
| 137 |
+ |
|
| 138 |
+ The response is passed as is from the client. The script needs to check |
|
| 139 |
+ itself if the input is valid, e.g. if the input is valid base64 encoding. |
|
| 140 |
+ |
|
| 141 |
+ The script can either directly write the result of the verification to |
|
| 142 |
+ :code:`auth_control_file or further defer it. See ``--auth-user-pass-verify`` |
|
| 143 |
+ for details. |
|
| 144 |
+ |
|
| 126 | 145 |
--client-connect cmd |
| 127 | 146 |
Run command ``cmd`` on client connection. |
| 128 | 147 |
|
| ... | ... |
@@ -83,6 +83,10 @@ extern "C" {
|
| 83 | 83 |
* FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_CONNECT_V2 |
| 84 | 84 |
* FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_LEARN_ADDRESS |
| 85 | 85 |
* |
| 86 |
+ * The OPENVPN_PLUGIN_CLIENT_CRRESPONSE function is called when the client sends |
|
| 87 |
+ * the CR_RESPONSE message, this is *typically* after OPENVPN_PLUGIN_TLS_FINAL |
|
| 88 |
+ * but may also occur much later. |
|
| 89 |
+ * |
|
| 86 | 90 |
* [Client session ensues] |
| 87 | 91 |
* |
| 88 | 92 |
* For each "TLS soft reset", according to reneg-sec option (or similar): |
| ... | ... |
@@ -128,7 +132,8 @@ extern "C" {
|
| 128 | 128 |
#define OPENVPN_PLUGIN_ROUTE_PREDOWN 12 |
| 129 | 129 |
#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER 13 |
| 130 | 130 |
#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 14 |
| 131 |
-#define OPENVPN_PLUGIN_N 15 |
|
| 131 |
+#define OPENVPN_PLUGIN_CLIENT_CRRESPONSE 15 |
|
| 132 |
+#define OPENVPN_PLUGIN_N 16 |
|
| 132 | 133 |
|
| 133 | 134 |
/* |
| 134 | 135 |
* Build a mask out of a set of plug-in types. |
| ... | ... |
@@ -3046,6 +3046,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) |
| 3046 | 3046 |
|
| 3047 | 3047 |
to.auth_user_pass_verify_script = options->auth_user_pass_verify_script; |
| 3048 | 3048 |
to.auth_user_pass_verify_script_via_file = options->auth_user_pass_verify_script_via_file; |
| 3049 |
+ to.client_crresponse_script = options->client_crresponse_script; |
|
| 3049 | 3050 |
to.tmp_dir = options->tmp_dir; |
| 3050 | 3051 |
if (options->ccd_exclusive) |
| 3051 | 3052 |
{
|
| ... | ... |
@@ -1525,6 +1525,7 @@ show_p2mp_parms(const struct options *o) |
| 1525 | 1525 |
SHOW_STR(client_connect_script); |
| 1526 | 1526 |
SHOW_STR(learn_address_script); |
| 1527 | 1527 |
SHOW_STR(client_disconnect_script); |
| 1528 |
+ SHOW_STR(client_crresponse_script); |
|
| 1528 | 1529 |
SHOW_STR(client_config_dir); |
| 1529 | 1530 |
SHOW_BOOL(ccd_exclusive); |
| 1530 | 1531 |
SHOW_STR(tmp_dir); |
| ... | ... |
@@ -2686,6 +2687,10 @@ options_postprocess_verify_ce(const struct options *options, |
| 2686 | 2686 |
{
|
| 2687 | 2687 |
msg(M_USAGE, "--client-connect requires --mode server"); |
| 2688 | 2688 |
} |
| 2689 |
+ if (options->client_crresponse_script) |
|
| 2690 |
+ {
|
|
| 2691 |
+ msg(M_USAGE, "--client-crresponse requires --mode server"); |
|
| 2692 |
+ } |
|
| 2689 | 2693 |
if (options->client_disconnect_script) |
| 2690 | 2694 |
{
|
| 2691 | 2695 |
msg(M_USAGE, "--client-disconnect requires --mode server"); |
| ... | ... |
@@ -7468,6 +7473,16 @@ add_option(struct options *options, |
| 7468 | 7468 |
set_user_script(options, &options->client_connect_script, |
| 7469 | 7469 |
p[1], "client-connect", true); |
| 7470 | 7470 |
} |
| 7471 |
+ else if (streq(p[0], "client-crresponse") && p[1]) |
|
| 7472 |
+ {
|
|
| 7473 |
+ VERIFY_PERMISSION(OPT_P_SCRIPT); |
|
| 7474 |
+ if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT)) |
|
| 7475 |
+ {
|
|
| 7476 |
+ goto err; |
|
| 7477 |
+ } |
|
| 7478 |
+ set_user_script(options, &options->client_crresponse_script, |
|
| 7479 |
+ p[1], "client-crresponse", true); |
|
| 7480 |
+ } |
|
| 7471 | 7481 |
else if (streq(p[0], "client-disconnect") && p[1]) |
| 7472 | 7482 |
{
|
| 7473 | 7483 |
VERIFY_PERMISSION(OPT_P_SCRIPT); |
| ... | ... |
@@ -473,6 +473,7 @@ struct options |
| 473 | 473 |
const char *client_connect_script; |
| 474 | 474 |
const char *client_disconnect_script; |
| 475 | 475 |
const char *learn_address_script; |
| 476 |
+ const char *client_crresponse_script; |
|
| 476 | 477 |
const char *client_config_dir; |
| 477 | 478 |
bool ccd_exclusive; |
| 478 | 479 |
bool disable; |
| ... | ... |
@@ -102,7 +102,7 @@ plugin_type_name(const int type) |
| 102 | 102 |
return "PLUGIN_CLIENT_CONNECT"; |
| 103 | 103 |
|
| 104 | 104 |
case OPENVPN_PLUGIN_CLIENT_CONNECT_V2: |
| 105 |
- return "PLUGIN_CLIENT_CONNECT"; |
|
| 105 |
+ return "PLUGIN_CLIENT_CONNECT_V2"; |
|
| 106 | 106 |
|
| 107 | 107 |
case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER: |
| 108 | 108 |
return "PLUGIN_CLIENT_CONNECT_DEFER"; |
| ... | ... |
@@ -122,6 +122,9 @@ plugin_type_name(const int type) |
| 122 | 122 |
case OPENVPN_PLUGIN_ROUTE_PREDOWN: |
| 123 | 123 |
return "PLUGIN_ROUTE_PREDOWN"; |
| 124 | 124 |
|
| 125 |
+ case OPENVPN_PLUGIN_CLIENT_CRRESPONSE: |
|
| 126 |
+ return "PLUGIN_CRRESPONSE"; |
|
| 127 |
+ |
|
| 125 | 128 |
default: |
| 126 | 129 |
return "PLUGIN_???"; |
| 127 | 130 |
} |
| ... | ... |
@@ -229,6 +229,10 @@ receive_cr_response(struct context *c, const struct buffer *buffer) |
| 229 | 229 |
|
| 230 | 230 |
management_notify_client_cr_response(key_id, mda, es, m); |
| 231 | 231 |
#endif |
| 232 |
+#if ENABLE_PLUGIN |
|
| 233 |
+ verify_crresponse_plugin(c->c2.tls_multi, m); |
|
| 234 |
+#endif |
|
| 235 |
+ verify_crresponse_script(c->c2.tls_multi, m); |
|
| 232 | 236 |
msg(D_PUSH, "CR response was sent by client ('%s')", m);
|
| 233 | 237 |
} |
| 234 | 238 |
|
| ... | ... |
@@ -362,6 +362,7 @@ struct tls_options |
| 362 | 362 |
|
| 363 | 363 |
/* used for username/password authentication */ |
| 364 | 364 |
const char *auth_user_pass_verify_script; |
| 365 |
+ const char *client_crresponse_script; |
|
| 365 | 366 |
bool auth_user_pass_verify_script_via_file; |
| 366 | 367 |
const char *tmp_dir; |
| 367 | 368 |
const char *auth_user_pass_file; |
| ... | ... |
@@ -1352,6 +1352,73 @@ done: |
| 1352 | 1352 |
return retval; |
| 1353 | 1353 |
} |
| 1354 | 1354 |
|
| 1355 |
+#ifdef ENABLE_PLUGIN |
|
| 1356 |
+void |
|
| 1357 |
+verify_crresponse_plugin(struct tls_multi *multi, const char *cr_response) |
|
| 1358 |
+{
|
|
| 1359 |
+ struct tls_session *session = &multi->session[TM_ACTIVE]; |
|
| 1360 |
+ setenv_str(session->opt->es, "crresponse", cr_response); |
|
| 1361 |
+ |
|
| 1362 |
+ plugin_call(session->opt->plugins, OPENVPN_PLUGIN_CLIENT_CRRESPONSE, NULL, |
|
| 1363 |
+ NULL, session->opt->es); |
|
| 1364 |
+ |
|
| 1365 |
+ setenv_del(session->opt->es, "crresponse"); |
|
| 1366 |
+} |
|
| 1367 |
+#endif |
|
| 1368 |
+ |
|
| 1369 |
+void |
|
| 1370 |
+verify_crresponse_script(struct tls_multi *multi, const char *cr_response) |
|
| 1371 |
+{
|
|
| 1372 |
+ |
|
| 1373 |
+ struct tls_session *session = &multi->session[TM_ACTIVE]; |
|
| 1374 |
+ |
|
| 1375 |
+ if (!session->opt->client_crresponse_script) |
|
| 1376 |
+ {
|
|
| 1377 |
+ return; |
|
| 1378 |
+ } |
|
| 1379 |
+ struct argv argv = argv_new(); |
|
| 1380 |
+ struct gc_arena gc = gc_new(); |
|
| 1381 |
+ |
|
| 1382 |
+ setenv_str(session->opt->es, "script_type", "client-crresponse"); |
|
| 1383 |
+ |
|
| 1384 |
+ /* Since cr response might be sensitive, like a stupid way to query |
|
| 1385 |
+ * a password via 2FA, we pass it via file instead environment */ |
|
| 1386 |
+ const char *tmp_file = platform_create_temp_file(session->opt->tmp_dir, "cr", &gc); |
|
| 1387 |
+ static const char *openerrmsg = "TLS CR Response Error: could not write " |
|
| 1388 |
+ "crtext challenge response to file: %s"; |
|
| 1389 |
+ |
|
| 1390 |
+ if (tmp_file) |
|
| 1391 |
+ {
|
|
| 1392 |
+ struct status_output *so = status_open(tmp_file, 0, -1, NULL, |
|
| 1393 |
+ STATUS_OUTPUT_WRITE); |
|
| 1394 |
+ status_printf(so, "%s", cr_response); |
|
| 1395 |
+ if (!status_close(so)) |
|
| 1396 |
+ {
|
|
| 1397 |
+ msg(D_TLS_ERRORS, openerrmsg, tmp_file); |
|
| 1398 |
+ tls_deauthenticate(multi); |
|
| 1399 |
+ goto done; |
|
| 1400 |
+ } |
|
| 1401 |
+ } |
|
| 1402 |
+ else |
|
| 1403 |
+ {
|
|
| 1404 |
+ msg(D_TLS_ERRORS, openerrmsg, "creating file failed"); |
|
| 1405 |
+ tls_deauthenticate(multi); |
|
| 1406 |
+ goto done; |
|
| 1407 |
+ } |
|
| 1408 |
+ |
|
| 1409 |
+ argv_parse_cmd(&argv, session->opt->client_crresponse_script); |
|
| 1410 |
+ argv_printf_cat(&argv, "%s", tmp_file); |
|
| 1411 |
+ |
|
| 1412 |
+ |
|
| 1413 |
+ if (!openvpn_run_script(&argv, session->opt->es, 0, "--client-crresponse")) |
|
| 1414 |
+ {
|
|
| 1415 |
+ tls_deauthenticate(multi); |
|
| 1416 |
+ } |
|
| 1417 |
+done: |
|
| 1418 |
+ argv_free(&argv); |
|
| 1419 |
+ gc_free(&gc); |
|
| 1420 |
+} |
|
| 1421 |
+ |
|
| 1355 | 1422 |
/* |
| 1356 | 1423 |
* Verify the username and password using a plugin |
| 1357 | 1424 |
*/ |
| ... | ... |
@@ -177,6 +177,29 @@ bool cert_hash_compare(const struct cert_hash_set *chs1, const struct cert_hash_ |
| 177 | 177 |
void verify_user_pass(struct user_pass *up, struct tls_multi *multi, |
| 178 | 178 |
struct tls_session *session); |
| 179 | 179 |
|
| 180 |
+ |
|
| 181 |
+ |
|
| 182 |
+/** |
|
| 183 |
+ * Runs the --client-crresponse script if one is defined. |
|
| 184 |
+ * |
|
| 185 |
+ * As with the management interface the script is stateless in the sense that |
|
| 186 |
+ * it does not directly participate in the authentication but rather should set |
|
| 187 |
+ * the files for the deferred auth like the management commands. |
|
| 188 |
+ * |
|
| 189 |
+ */ |
|
| 190 |
+void |
|
| 191 |
+verify_crresponse_script(struct tls_multi *multi, const char *cr_response); |
|
| 192 |
+ |
|
| 193 |
+/** |
|
| 194 |
+ * Call the plugin OPENVPN_PLUGIN_CLIENT_CRRESPONSE. |
|
| 195 |
+ * |
|
| 196 |
+ * As with the management interface calling the plugin is stateless in the sense |
|
| 197 |
+ * that it does not directly participate in the authentication but rather |
|
| 198 |
+ * should set the files for the deferred auth like the management commands. |
|
| 199 |
+ */ |
|
| 200 |
+void |
|
| 201 |
+verify_crresponse_plugin(struct tls_multi *multi, const char *cr_response); |
|
| 202 |
+ |
|
| 180 | 203 |
/** |
| 181 | 204 |
* Perform final authentication checks, including locking of the cn, the allowed |
| 182 | 205 |
* certificate hashes, and whether a client config entry exists in the |