[Openvpn-devel] [XL] Change in openvpn[master]: Implement sending/receiving app custom control protocol
"plaisthos \(Code Review\) via Openvpn-devel" <[email protected]>
| Newsgroups | net.sourceforge.lists.openvpn-devel |
|---|---|
| Message-ID | <[email protected]> |
plaisthos has uploaded this change for review. ( http://gerrit.openvpn.net/c/openvpn/+/1858?usp=email ) Change subject: Implement sending/receiving app custom control protocol ...................................................................... Implement sending/receiving app custom control protocol Change-Id: I4a6a6ef41e15896b8b79d795b2a6de86b8ae4c84 Signed-off-by: Arne Schwabe <[email protected]> --- M CMakeLists.txt M doc/man-sections/client-options.rst M doc/management-notes.txt M src/openvpn/Makefile.am A src/openvpn/acc.c A src/openvpn/acc.h M src/openvpn/forward.c M src/openvpn/init.c M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/options.c M src/openvpn/options.h M src/openvpn/push.c M src/openvpn/push.h M src/openvpn/push_util.c A src/openvpn/push_util.h M src/openvpn/ssl.c M src/openvpn/ssl_common.h M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/mock_management.c A tests/unit_tests/openvpn/mock_management.h M tests/unit_tests/openvpn/mock_push_dependencies.c A tests/unit_tests/openvpn/test_acc.c A tests/unit_tests/openvpn/test_acc.h M tests/unit_tests/openvpn/test_misc.c M tests/unit_tests/openvpn/test_ssl.c 27 files changed, 1,317 insertions(+), 22 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/58/1858/1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6eb5954..8f52e44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -424,6 +424,8 @@ src/compat/compat-gettimeofday.c src/compat/compat-strsep.c src/compat/compat-strtok_r.c + src/openvpn/acc.c + src/openvpn/acc.h src/openvpn/argv.c src/openvpn/argv.h src/openvpn/base64.c @@ -560,8 +562,9 @@ src/openvpn/ps.c src/openvpn/ps.h src/openvpn/push.c - src/openvpn/push_util.c src/openvpn/push.h + src/openvpn/push_util.c + src/openvpn/push_util.h src/openvpn/pushlist.h src/openvpn/reflect_filter.c src/openvpn/reflect_filter.h @@ -812,7 +815,10 @@ tests/unit_tests/openvpn/mock_management.c tests/unit_tests/openvpn/mock_ssl_dependencies.c tests/unit_tests/openvpn/mock_win32_execve.c + tests/unit_tests/openvpn/test_acc.c + src/openvpn/acc.c src/openvpn/argv.c + src/openvpn/auth_token.c src/openvpn/base64.c src/openvpn/crypto_epoch.c src/openvpn/crypto_mbedtls.c @@ -826,8 +832,11 @@ src/openvpn/options_util.c src/openvpn/otime.c src/openvpn/packet_id.c + src/openvpn/reliable.c + src/openvpn/session_id.c src/openvpn/run_command.c src/openvpn/ssl_mbedtls.c + src/openvpn/ssl_ncp.c src/openvpn/ssl_openssl.c src/openvpn/ssl_util.c src/openvpn/ssl_verify_mbedtls.c @@ -844,6 +853,8 @@ target_sources(test_misc PRIVATE tests/unit_tests/openvpn/mock_get_random.c tests/unit_tests/openvpn/test_schedule.c + tests/unit_tests/openvpn/mock_management.c + src/openvpn/base64.c src/openvpn/options_util.c src/openvpn/otime.c src/openvpn/ssl_util.c diff --git a/doc/man-sections/client-options.rst b/doc/man-sections/client-options.rst index 87c138a..89ba7cf 100644 --- a/doc/man-sections/client-options.rst +++ b/doc/man-sections/client-options.rst @@ -12,6 +12,17 @@ When this option is set, OpenVPN will not drop incoming tun packets with same destination as host. +--app-custom-control protocollist + Sets the list of supported app custom control protocols. + These protocols are announced to the server when this option is used + as a client. If the option is set on a server it will negotiate the + common protocols with the client. + + The protocollist is a : separated list of protocol identifiers. + + See management-notes.txt in the OpenVPN distribution and the OpenVPN RFC + draft for a description of app custom control protocol support. + --auth-token token This is not an option to be used directly in any configuration files, but rather push this option from a ``--client-connect`` script or a diff --git a/doc/management-notes.txt b/doc/management-notes.txt index d6b3fbb..b2dcc0a 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -798,6 +798,56 @@ The client should return the response to the crtext challenge using the cr-response command. +app custom control +================== +If the special string "ACC" is passed as {EXTRA} OpenVPN will determine +the common app custom protocols between the client and the sever and insruct +the client that the server will send control messages to the client using +the ACC protocol. + +The server should then use acc-msg command to send app custom control messages +to the client to faciliate the pending authentication. + +COMMAND -- client-acc-msg (OpenVPN 2.8 or higher) +------------------------------------------ + +This commands sends an app custom control message to a client, +the format of the command is: + + client-acc-msg {CID} {KID} + {protocol} + {flags} + {message} + END + +CID,KID -- client ID and Key ID. See documentation for ">CLIENT:" +notification for more info. + +protocol -- the ACC protocol identified that this message should be + tagged with. + +flags -- a colon seperated list of the encoding and the flag if + the message is a fragment. E.g. "6:F" for a base64 encoded + message that is also a fragment and not complete yet. + +message -- The message encoded in the encoding specified in flags + + +COMMAND -- acc-msg (OpenVPN 2.8 or higher) +------------------------------------------ + +This commands sends an app custom command to the peer, the format of the +command is + + acc-msg + {protocol} + {flags} + {message} + END + +See the client-acc-msg command for more details about the format of the message. + + COMMAND -- client-deny (OpenVPN 2.1 or higher) ----------------------------------------------- @@ -1247,6 +1297,23 @@ response. Mechanisms that need multiple rounds or more complex answers should implement a different response type than CR_RESPONSE. +(6) App custom control message + + This message is an application specific payload message that is transported + over the OpenVPN control channel. + + In server mode: + + >CLIENT:ACC,{CID},{KID},{protocol},{fragment},{msg_base64} + + In client mode: + + >ACC:{protocol},{fragment},{msg_base64} + + The protocol is a short ascii string identifying the app specific custom + protocol and identifies the protocol. If fragment is set to 1, the message + is not complete and the next message should be concatenated to this message + to form a complete message. Variables: diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 7fd12b4..4fcccf1 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -42,6 +42,7 @@ sbin_PROGRAMS = openvpn openvpn_SOURCES = \ + acc.c acc.h \ argv.c argv.h \ auth_token.c auth_token.h \ base64.c base64.h \ @@ -121,7 +122,8 @@ proto.c proto.h \ proxy.c proxy.h \ ps.c ps.h \ - push.c push_util.c push.h \ + push.c push.h \ + push_util.c push_util.h \ pushlist.h \ reflect_filter.c reflect_filter.h \ reliable.c reliable.h \ diff --git a/src/openvpn/acc.c b/src/openvpn/acc.c new file mode 100644 index 0000000..2b7e65f --- /dev/null +++ b/src/openvpn/acc.c @@ -0,0 +1,390 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 OpenVPN Inc <[email protected]> + * Copyright (C) 2025 Arne Schwabe <[email protected]> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "syshead.h" + +#include <string.h> +#include "buffer.h" +#include "push_util.h" +#include "error.h" +#include "acc.h" +#include "forward.h" +#include "options_util.h" +#include "ssl_util.h" +#include "ssl_ncp.h" + +enum acc_message_flag +{ + ACC_MESSAGE_FLAGS_ASCII = 1, + ACC_MESSAGE_FLAGS_BASE64 = 2, + ACC_MESSAGE_FLAGS_FRAGMENT = 4, +}; + + +static void +log_acc_message(struct context *c, const struct buffer *buf, int payload_len, + const char *protocol, int flags) +{ + bool fragment = flags & ACC_MESSAGE_FLAGS_FRAGMENT; +#ifdef ENABLE_MANAGEMENT + struct gc_arena gc = gc_new(); + const char *payload_msg = NULL; + + /* For simplicity, we always encode payload to be base64 encoded + * if not already in base64 format */ + if (flags & ACC_MESSAGE_FLAGS_ASCII) + { + char *b64out = NULL; + ASSERT(openvpn_base64_encode(BPTR(buf), payload_len, &b64out) >= 0); + gc_addspecial(b64out, free, &gc); + payload_msg = b64out; + } + else + { + payload_msg = BSTR(buf); + } + + if (management) + { + struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE]; + + struct man_def_auth_context *mda = session->opt->mda_context; + if (c->options.mode == MODE_SERVER) + { + /* server mode. Report client context */ + unsigned int mda_key_id = get_primary_key(c->c2.tls_multi)->mda_key_id; + msg(M_CLIENT, ">CLIENT:ACC,%lu,%u,%s,%d,%s", + mda->cid, mda_key_id, protocol, fragment, payload_msg); + } + else + { + /* Client mode. Do not a report a CLIENT id */ + msg(M_CLIENT, ">ACC:%s,%d,%s", protocol, fragment, payload_msg); + } + } + gc_free(&gc); +#endif /* ifdef ENABLE_MANAGEMENT */ + msg(D_PUSH, "Received custom app control message (protocol '%s'%s)", + protocol, fragment ? ", fragment" : ""); +} + + +/** + * Parses the flags field of an app control message and moves the + * buffer past the flags string. If there is an + * error parsing the flags field the method returns -1 and + * puts the error reason in err_reason + */ +static int +parse_acc_message_flags(struct buffer *buf, const char **err_reason, struct gc_arena *gc) +{ + const char *flags = extract_field(buf, ',', gc); + + if (!flags) + { + *err_reason = "could not extract flags field"; + return -1; + } + + + int acc_flags = 0; + + for (const char *flag = flags; *flag != '\0'; flag++) + { + if (*flag == 'A') + { + acc_flags |= ACC_MESSAGE_FLAGS_ASCII; + } + else if (*flag == '6') + { + acc_flags |= ACC_MESSAGE_FLAGS_BASE64; + } + else if (*flag == 'F') + { + acc_flags |= ACC_MESSAGE_FLAGS_FRAGMENT; + } + else + { + *err_reason = "Unknown flag in flags"; + return -1; + } + } + /* The message should be encoded with exactly one encoding. Ensure that + * only one of the flags is present */ + bool asciienc = (bool)(acc_flags & ACC_MESSAGE_FLAGS_ASCII); + bool base64enc = (bool)(acc_flags & ACC_MESSAGE_FLAGS_BASE64); + + if (base64enc + asciienc != 1) + { + char *tmp = gc_malloc(512, 1, gc); + snprintf(tmp, 512, "number of encodings must be exactly one " + "(B64=%d, ASCII=%d)", + base64enc, asciienc); + *err_reason = tmp; + return -1; + } + + return acc_flags; +} + + +void +receive_acc_message(struct context *c, const struct buffer *buffer) +{ + struct gc_arena gc = gc_new(); + const char *err_reason = ""; + + /* Example message: ACC,muppets,15,A,I am Miss Piggy */ + struct buffer buf = *buffer; + + if (!buf_advance(&buf, strlen("ACC")) || buf_read_u8(&buf) != ',') + { + err_reason = "missing , after ACC"; + goto err; + } + + /* extract protocol, payload length, flags substrings */ + char *protocol = extract_field(&buf, ',', &gc); + if (!protocol) + { + err_reason = "could not extract protocol field"; + goto err; + } + + int payload_len = 0; + if (!buffer_read_int(&buf, &payload_len)) + { + err_reason = "could not extract payload length field"; + goto err; + } + + /* comma after the length */ + if (buf_read_u8(&buf) != ',') + { + err_reason = "missing , after payload len"; + goto err; + } + + int flags = parse_acc_message_flags(&buf, &err_reason, &gc); + + if (flags < 0) + { + goto err; + } + + /* We should have a final NUL byte in the control message buffer + * and thus the length of the buffer should be payload_len + 1 */ + if (buf_len(&buf) != payload_len + 1) + { + char *tmp = gc_malloc(512, 1, &gc); + snprintf(tmp, 512, "field length %d, payload length %d mismatch", + payload_len, buf_len(&buf) - 1); + err_reason = tmp; + goto err; + } + + + log_acc_message(c, &buf, payload_len, protocol, flags); + gc_free(&gc); + return; + +err: + msg(D_PUSH_ERRORS, "WARNING: Received malformed custom app control channel " + "(%s) message control message: %s", + err_reason, + format_hex(BPTR(&buf), BLEN(&buf), 80, &gc)); + gc_free(&gc); +} + + +bool +send_acc_message(struct context *c, + struct tls_multi *tls_multi, + struct tls_session *session, + const char *protocol, bool fragment, + const char *msg, bool base64) +{ + /* TODO check client capabilities */ + /* 3 for the encoding, potential F, and , 1 for the final flag, 5 for the message size itself */ + const size_t max_header_size = strlen("ACC,") + 3 + strlen(protocol) + 1 + 5; + + size_t len = max_header_size + strlen(msg); + + if (len > PUSH_BUNDLE_SIZE) + { + msg(M_CLIENT, "message length (%zu) exceeds maximum possible message length (%d)", len, PUSH_BUNDLE_SIZE); + return false; + } + + struct gc_arena gc = gc_new(); + struct buffer buf = alloc_buf_gc(len, &gc); + + /* Example message: ACC,muppets,15,A,I am Miss Piggy */ + buf_printf(&buf, "ACC,%s,%zu,%s%s,%s", protocol, + strlen(msg), + base64 ? "6" : "A", + fragment ? "F" : "", + msg); + + send_control_channel_string_dowork(session, BSTR(&buf), D_PUSH); + reschedule_multi_process(c); + return true; +} + +char * +parse_acc_parameters(const char *capabilities, + const char *delim, + int *max_acc_len, + bool server, + struct gc_arena *gc) +{ + char *tmp_message = string_alloc(capabilities, NULL); + char *tmp_message_orig = tmp_message; + char *lasts = NULL; + + const char *token = strtok_r(tmp_message, delim, &lasts); + + int p = 0; + int acclen = 0; + const char *protocols = NULL; + char *ret = NULL; + + /* In server mode we allow more capabilities than our own, in client + * mode we reject if we should use capabilities that we do not support */ + int allowed_acc_len = server ? INT_MAX : ACC_MAX_MSG_LEN; + + while (token) + { + if (p == 0 && !atoi_constrained(token, &acclen, "ACC message length", ACC_MIN_MSG_LEN, allowed_acc_len, D_PUSH_ERRORS)) + { + goto done; + } + + if (p == 1 && (!tls_item_in_cipher_list("A", token) || !tls_item_in_cipher_list("6", token))) + { + msg(D_PUSH_ERRORS, "App custom control encoding must include base64 and ascii"); + goto done; + } + + if (p == 1 && !server && strcmp(token, "A:6") && strcmp(token, "6:A")) + { + msg(D_PUSH_ERRORS, "App custom control encoding must exactly base64 and ascii"); + goto done; + } + + if (p == 2) + { + protocols = token; + } + + if (p == 3) + { + msg(D_PUSH_ERRORS, "Too many parameters for INFO ACC message/IV_SSO."); + goto done; + } + + token = strtok_r(NULL, delim, &lasts); + p++; + } + + *max_acc_len = min_int(acclen, ACC_MAX_MSG_LEN); + ret = string_alloc(protocols, gc); + +done: + free(tmp_message_orig); + return ret; +} + + +void +determine_common_acc_protocols(struct context *c) +{ + if (!c->options.acc_protocols) + { + /* No app custom protocols have been configured server-side. */ + return; + } + if (c->options.acc_negotiated_protocols) + { + /* We already have determined the common protocols. Doing this + * again would just add an extra string allocation to options->gc */ + return; + } + + struct gc_arena gc = gc_new(); + + /* example IV_ACC: IV_ACC=2048,6:A,flower:power */ + const char *peer_info = c->c2.tls_multi->peer_info; + char *iv_acc = extract_var_peer_info(peer_info, "IV_ACC=", &gc); + if (!iv_acc) + { + gc_free(&gc); + return; + } + + int max_acc_len = 0; + char *protocols = parse_acc_parameters(iv_acc, ",", &max_acc_len, true, &gc); + + if (!protocols) + { + gc_free(&gc); + msg(D_PUSH_ERRORS, "WARNING: IV_ACC peer info variable of peer is malformed"); + return; + } + + char *lasts = NULL; + const char *token = strtok_r(protocols, ":", &lasts); + + struct buffer common_protocols = alloc_buf_gc(1024, &gc); + + while (token) + { + if (tls_item_in_cipher_list(token, c->options.acc_protocols)) + { + /* protocol is in both server and client list */ + if (buf_len(&common_protocols) > 0) + { + buf_write_u8(&common_protocols, ':'); + } + buf_printf(&common_protocols, "%s", token); + } + token = strtok_r(NULL, ":", &lasts); + } + + if (buf_len(&common_protocols) == 0) + { + gc_free(&gc); + msg(D_PUSH_DEBUG, "WARNING: No common app custom control protocol."); + return; + } + + c->options.acc_negotiated_protocols = string_alloc(buf_str(&common_protocols), &c->options.gc); + c->options.app_custom_protocol_len = max_acc_len; + gc_free(&gc); +} \ No newline at end of file diff --git a/src/openvpn/acc.h b/src/openvpn/acc.h new file mode 100644 index 0000000..c86eb5d --- /dev/null +++ b/src/openvpn/acc.h @@ -0,0 +1,92 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 OpenVPN Inc <[email protected]> + * Copyright (C) 2025 Arne Schwabe <[email protected]> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef ACC_H +#define ACC_H + +#include "openvpn.h" + +#define ACC_MIN_MSG_LEN 64 +#define ACC_MAX_MSG_LEN 1280 + +/** + * This method parses an app custom control message and delivers it to the + * management interface. We leave reassembly of fragmented messages to the + * management interface. + * + * @param c The context struct + * @param buffer Buffer containing the control message with ACC + */ +void +receive_acc_message(struct context *c, const struct buffer *buffer); + + +bool +send_acc_message(struct context *c, + struct tls_multi *tls_multi, + struct tls_session *session, + const char *protocol, bool fragment, + const char *msg, bool base64); + + +/** + * Parses an info message from the server that contains app custom control info + * information prior to the PUSH_REPLY to be able to use app custom control + * protocols before sending + * + * In client mode only allow what we support ourselves. In server mode + * allow also capabilities that we not support ourselves. + * + * @param capabilities The string to parse + * @param delim The delimiter to use when parsing the string. + * IV_ACC uses "," as delimiter while the + * custom-control PUSH message uses " " as "," is + * already used to separate different push directives + * @param max_acc_len Set to the maximum ACC message length + * @param server Whether we are server or client + * @param gc The gc arena to use for allocating the resulting string + * @return The resulting protocol string or NULL on error + */ +char * +parse_acc_parameters(const char *capabilities, + const char *delim, + int *max_acc_len, + bool server, + struct gc_arena *gc); + +/** + * Parses the IV_ACC of the peer and sets c->options.acc_negotiated_protocols + * to the common protocols of server and client and + * c->options.app_custom_protocol_len to the maximum length of the ACC message + * support by both server and client. + * + * If there is no common protocol or the client does not support any app custom + * protocol c->options.acc_negotiated_protocols will be set to NULL. + * + * @param c the client context to use the set the app custom protocols. + */ +void +determine_common_acc_protocols(struct context *c); +#endif diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index 46e1a53..48a9b7c 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -42,6 +42,7 @@ #include "dco.h" #include "auth_token.h" #include "tun_afunix.h" +#include "acc.h" #include "memdbg.h" @@ -251,11 +252,11 @@ } else if (buf_string_match_head_str(buf, "INFO_PRE")) { - server_pushed_info(buf, 8); + server_pushed_info(c, buf, 8); } else if (buf_string_match_head_str(buf, "INFO")) { - server_pushed_info(buf, 4); + server_pushed_info(c, buf, 4); } else if (buf_string_match_head_str(buf, "CR_RESPONSE")) { @@ -265,6 +266,10 @@ { receive_auth_pending(c, buf); } + else if (buf_string_match_head_str(buf, "ACC")) + { + receive_acc_message(c, buf); + } else if (buf_string_match_head_str(buf, "EXIT")) { receive_exit_message(c); diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 101fa64..fc7e9b4 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2283,6 +2283,10 @@ buf_printf(&out, " aead-epoch"); } } + if (c->options.acc_negotiated_protocols) + { + buf_printf(&out, "custom-control %d,A:6,%s", c->options.app_custom_protocol_len, c->options.acc_negotiated_protocols); + } if (BLENZ(&out) > strlen(header)) { @@ -2527,6 +2531,11 @@ flags |= (OPT_P_ROUTE | OPT_P_DHCPDNS); } + if (c->options.acc_protocols) + { + flags |= OPT_P_ACC; + } + return flags; } @@ -3342,6 +3351,7 @@ to.verify_hash_depth = options->verify_hash_depth; to.verify_hash_no_ca = options->verify_hash_no_ca; memcpy(to.x509_username_field, options->x509_username_field, sizeof(to.x509_username_field)); + to.acc_protocols = options->acc_protocols; to.es = c->c2.es; to.net_ctx = &c->net_ctx; @@ -4300,6 +4310,27 @@ } #endif /* ifdef TARGET_ANDROID */ +static bool +management_acc_msg(void *arg, + struct buffer_list *input) /* ownership transferred */ +{ + struct gc_arena gc = gc_new(); + struct context *c = arg; + + bool ret = true; + if (buffer_list_defined(input)) + { + struct tls_multi *multi = c->c2.tls_multi; + struct tls_session *session = &multi->session[TM_ACTIVE]; + struct options *opt = &c->options; + + ret = management_send_acc_message(c, multi, session, opt->acc_negotiated_protocols, input); + } + + buffer_list_free(input); + gc_free(&gc); + return ret; +} #endif /* ifdef ENABLE_MANAGEMENT */ void @@ -4316,6 +4347,7 @@ cb.proxy_cmd = management_callback_proxy_cmd; cb.remote_cmd = management_callback_remote_cmd; cb.send_cc_message = management_callback_send_cc_message; + cb.acc_msg = management_acc_msg; #ifdef TARGET_ANDROID cb.network_change = management_callback_network_change; #endif diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index bfc2aa2..a9f2463 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -44,8 +44,10 @@ #include "dco.h" #include "push.h" #include "multi.h" +#include "acc.h" #include "memdbg.h" +#include "ssl_ncp.h" #ifdef ENABLE_PKCS11 #include "pkcs11.h" @@ -1080,6 +1082,35 @@ } break; + case IEC_CLIENT_ACC_MSG: + if (man->persist.callback.client_acc_msg) + { + bool status = (*man->persist.callback.client_acc_msg)(man->persist.callback.arg, + man->connection.in_extra_cid, + man->connection.in_extra_kid, + man->connection.in_extra); + man->connection.in_extra = NULL; + report_command_status(status, "client-acc-msg"); + } + else + { + man_command_unsupported("client-acc-msg"); + } + break; + + case IEC_ACC_MSG: + if (man->persist.callback.acc_msg) + { + bool status = (*man->persist.callback.acc_msg)(man->persist.callback.arg, + man->connection.in_extra); + man->connection.in_extra = NULL; + report_command_status(status, "acc-msg"); + } + else + { + man_command_unsupported("acc-msg"); + } + break; case IEC_PK_SIGN: man->connection.ext_key_state = EKS_READY; buffer_list_free(man->connection.ext_key_input); @@ -1204,6 +1235,29 @@ } static void +man_client_acc_msg(struct management *man, const char *cid_str, + const char *kid_str) +{ + struct man_connection *mc = &man->connection; + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; + if (parse_cid(cid_str, &mc->in_extra_cid) + && parse_uint(kid_str, "KID", &mc->in_extra_kid)) + { + mc->in_extra_cmd = IEC_CLIENT_ACC_MSG; + in_extra_reset(mc, IER_NEW); + } +} + +static void +man_acc_msg(struct management *man) +{ + struct man_connection *mc = &man->connection; + mc->in_extra_cmd = IEC_ACC_MSG; + in_extra_reset(mc, IER_NEW); +} + +static void man_client_auth(struct management *man, const char *cid_str, const char *kid_str, const bool extra) { struct man_connection *mc = &man->connection; @@ -1760,6 +1814,17 @@ man_client_pending_auth(man, p[1], p[2], p[3], p[4]); } } + else if (streq(p[0], "client-acc-msg")) + { + if (man_need(man, p, 2, 0)) + { + man_client_acc_msg(man, p[1], p[2]); + } + } + else if (streq(p[0], "acc-msg")) + { + man_acc_msg(man); + } else if (streq(p[0], "rsa-sig")) { man_pk_sig(man, "rsa-sig"); @@ -3121,6 +3186,7 @@ } } + void management_connection_established(struct management *management, struct man_def_auth_context *mdac, const struct env_set *es) @@ -4352,6 +4418,65 @@ } } +bool +management_send_acc_message(struct context *c, struct tls_multi *multi, struct tls_session *session, + const char *negotiated_protocols, struct buffer_list *input) +{ + /* ensure that are enough inputs in the list */ + if (!input || input->size < 3) + { + msg(M_CLIENT, "not enough input lines for acc message"); + return false; + } + + struct gc_arena gc = gc_new(); + + const struct buffer *proto_buf = buffer_list_peek(input); + const char *protocol = string_alloc(BSTR(proto_buf), &gc); + buffer_list_pop(input); + + const struct buffer *encoding_buf = buffer_list_peek(input); + char *encoding = buf_str(encoding_buf); + char *flag = NULL; + bool b64encoding = false; + bool fragment = false; + + while ((flag = strsep(&encoding, ":"))) + { + if (!strcmp(flag, "6")) + { + b64encoding = true; + } + else if (!strcmp(flag, "F")) + { + fragment = true; + } + } + buffer_list_pop(input); + + buffer_list_aggregate_separator(input, 10000, ""); + const struct buffer *acc_msg_buf = buffer_list_peek(input); + + if (!proto_buf || !acc_msg_buf) + { + msg(M_CLIENT, "missing protocol or message"); + goto error; + } + if (!negotiated_protocols || !tls_item_in_cipher_list(protocol, negotiated_protocols)) + { + msg(M_CLIENT, "protocol not in the list of negotiated protocols: %s", np(negotiated_protocols)); + goto error; + } + + bool ret = send_acc_message(c, multi, session, protocol, + fragment, buf_str(acc_msg_buf), + b64encoding); + gc_free(&gc); + return ret; +error: + gc_free(&gc); + return false; +} #else /* ifdef ENABLE_MANAGEMENT */ #include "win32.h" diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 71e88d0..39bdf99 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -184,12 +184,19 @@ int (*n_clients)(void *arg); bool (*send_cc_message)(void *arg, const char *message, const char *parameter); bool (*kill_by_cid)(void *arg, const unsigned long cid, const char *kill_msg); + bool (*client_auth)(void *arg, const unsigned long cid, const unsigned int mda_key_id, const bool auth, const char *reason, const char *client_reason, struct buffer_list *cc_config); /* ownership transferred */ bool (*client_pending_auth)(void *arg, const unsigned long cid, const unsigned int kid, const char *extra, unsigned int timeout); char *(*get_peer_info)(void *arg, const unsigned long cid); + bool (*client_acc_msg)(void *arg, + const unsigned long cid, + const unsigned int mda_key_id, + struct buffer_list *msg); + bool (*acc_msg)(void *arg, + struct buffer_list *msg); bool (*proxy_cmd)(void *arg, const char **p); bool (*remote_cmd)(void *arg, const char **p); #ifdef TARGET_ANDROID @@ -292,13 +299,15 @@ struct command_line *in; struct buffer_list *out; -#define IEC_UNDEF 0 -#define IEC_CLIENT_AUTH 1 +#define IEC_UNDEF 0 +#define IEC_CLIENT_AUTH 1 /* #define IEC_CLIENT_PF 2 *REMOVED FEATURE* */ -#define IEC_RSA_SIGN 3 -#define IEC_CERTIFICATE 4 -#define IEC_PK_SIGN 5 -#define IEC_PASSWORD 6 +#define IEC_RSA_SIGN 3 +#define IEC_CERTIFICATE 4 +#define IEC_PK_SIGN 5 +#define IEC_PASSWORD 6 +#define IEC_CLIENT_ACC_MSG 7 +#define IEC_ACC_MSG 8 int in_extra_cmd; struct buffer_list *in_extra; unsigned long in_extra_cid; @@ -511,4 +520,13 @@ */ void management_sleep(const int n); +/** Parses the ACC message from the input buffer_list and send to the + * session identified by session and multi + */ +struct tls_session; +struct tls_multi; +bool +management_send_acc_message(struct context *c, struct tls_multi *multi, struct tls_session *session, + const char *negotiated_protocols, struct buffer_list *input); + #endif /* ifndef MANAGE_H */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index dda5c6b..ae2b80d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -52,6 +52,7 @@ #include "ssl_util.h" #include "dco.h" #include "reflect_filter.h" +#include "acc.h" /*#define MULTI_DEBUG_EVENT_LOOP*/ @@ -1773,6 +1774,13 @@ o->imported_protocol_flags |= CO_USE_CC_EXIT_NOTIFY; } + if (o->acc_protocols) + { + /* If ACC is enabled server side call to find common acc protocols, + * will be pushed during push message */ + determine_common_acc_protocols(c); + } + /* Select cipher if client supports Negotiable Crypto Parameters */ /* if we have already created our key, we cannot *change* our own @@ -4032,6 +4040,24 @@ } } +static const char * +calculate_app_control_extra(struct context *c, struct gc_arena *gc) +{ + determine_common_acc_protocols(c); + + if (!c->options.acc_negotiated_protocols) + { + msg(M_CLIENT, "ACC: No negotiated protocols"); + return NULL; + } + + struct buffer buf = alloc_buf_gc(1024, gc); + + buf_printf(&buf, "ACC:%d A:6 %s", c->options.app_custom_protocol_len, c->options.acc_negotiated_protocols); + return buf_str(&buf); +} + + static bool management_client_pending_auth(void *arg, const unsigned long cid, const unsigned int mda_key_id, const char *extra, unsigned int timeout) @@ -4042,17 +4068,29 @@ if (mi) { struct tls_multi *multi = mi->context.c2.tls_multi; + + struct gc_arena gc = gc_new(); + if (!strcmp(extra, "ACC")) + { + extra = calculate_app_control_extra(&mi->context, &gc); + if (!extra) + { + gc_free(&gc); + return false; + } + } struct tls_session *session = lookup_session_by_mda_key_id(multi, mda_key_id); if (!session) { return false; } - + /* sends INFO_PRE and AUTH_PENDING messages to client */ bool ret = send_auth_pending_messages(multi, session, extra, timeout); reschedule_multi_process(&mi->context); multi_schedule_context_wakeup(m, mi); + gc_free(&gc); return ret; } return false; @@ -4096,6 +4134,39 @@ return ret; } + +static bool +management_client_acc_msg(void *arg, + const unsigned long cid, + const unsigned int mda_key_id, + struct buffer_list *input) /* ownership transferred */ +{ + struct multi_context *m = (struct multi_context *)arg; + struct multi_instance *mi = lookup_by_cid(m, cid); + + bool ret = false; + if (mi && buffer_list_defined(input)) + { + struct tls_multi *multi = mi->context.c2.tls_multi; + struct tls_session *session = lookup_session_by_mda_key_id(multi, mda_key_id); + if (!session) + { + msg(M_CLIENT, "client session not found"); + buffer_list_free(input); + return false; + } + + struct options *opt = &mi->context.options; + + ret = management_send_acc_message(&mi->context, multi, session, opt->acc_negotiated_protocols, input); + multi_schedule_context_wakeup(m, mi); + } + + buffer_list_free(input); + return ret; +} + + static char * management_get_peer_info(void *arg, const unsigned long cid) { @@ -4132,6 +4203,7 @@ cb.n_clients = management_callback_n_clients; cb.kill_by_cid = management_kill_by_cid; cb.client_auth = management_client_auth; + cb.client_acc_msg = management_client_acc_msg; cb.client_pending_auth = management_client_pending_auth; cb.get_peer_info = management_get_peer_info; cb.push_update_broadcast = management_callback_send_push_update_broadcast; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 4391434..65610a71 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -35,6 +35,7 @@ #include "syshead.h" +#include "acc.h" #include "buffer.h" #include "error.h" #include "common.h" @@ -7132,6 +7133,28 @@ VERIFY_PERMISSION(OPT_P_GENERAL); options->force_key_material_export = true; } + else if (streq(p[0], "app-custom-control") && p[1] && !p[2]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->acc_protocols = p[1]; + } + else if (streq(p[0], "custom-control") && p[1] && p[2] && p[3] && !p[4]) + { + VERIFY_PERMISSION(OPT_P_ACC); + + if (!streq(p[2], "A:6") && !streq(p[2], "6:A")) + { + msg(msglevel, "App custom control encoding must be base64 and ascii"); + goto err; + } + + if (!atoi_constrained(p[1], &options->app_custom_protocol_len, + "app custom message message length", ACC_MIN_MSG_LEN, ACC_MAX_MSG_LEN, msglevel)) + { + goto err; + } + options->acc_negotiated_protocols = p[3]; + } else if (streq(p[0], "prng") && p[1] && !p[3]) { msg(M_WARN, "NOTICE: --prng option ignored (SSL library PRNG is used)"); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index de69c8e..3d3b526 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -711,6 +711,16 @@ /* force using TLS key material export for data channel key generation */ bool force_key_material_export; + /** The app custom control protocols we should announce as supported */ + const char *acc_protocols; + + /** the protocols that are negotiated with the peer. As a client we + * trust the server pushed list here */ + const char *acc_negotiated_protocols; + + /** the maximum length of an ACC control channel message */ + int app_custom_protocol_len; + bool vlan_tagging; enum vlan_acceptable_frames vlan_accept; uint16_t vlan_pvid; @@ -762,6 +772,7 @@ #define OPT_P_INLINE (1u << 29) #define OPT_P_PUSH_MTU (1u << 30) #define OPT_P_ROUTE_TABLE (1u << 31) +#define OPT_P_ACC (1ull << 32) /* Options allowed in the normal configuration file */ #define OPT_P_DEFAULT (~(OPT_P_INSTANCE | OPT_P_PULL_MODE | OPT_P_ACC | OPT_P_PEER_ID)) diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 5b15f71..7697ec2 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -27,6 +27,9 @@ #include "syshead.h" #include "push.h" + +#include "acc.h" +#include "push_util.h" #include "options.h" #include "crypto.h" #include "ssl.h" @@ -220,9 +223,8 @@ #endif } - void -server_pushed_info(const struct buffer *buffer, const int adv) +server_pushed_info(struct context *c, const struct buffer *buffer, const int adv) { const char *m = ""; struct buffer buf = *buffer; @@ -232,6 +234,27 @@ m = BSTR(&buf); } + if (strncmp(m, "ACC:", 4) == 0) + { + int max_acc_len = 0; + + + /* example string to parse: ACC:1400 A:6 flower:happy */ + /* Skip over the ACC: prefix */ + m += 4; + + char *protocols = parse_acc_parameters(m, " ", &max_acc_len, false, &c->options.gc); + if (protocols) + { + c->options.acc_negotiated_protocols = protocols; + c->options.app_custom_protocol_len = max_acc_len; + } + else + { + msg(D_PUSH_ERRORS, "WARNING: Received ACC info command with invalid parameters"); + } + } + #ifdef ENABLE_MANAGEMENT struct gc_arena gc; if (management) @@ -284,6 +307,7 @@ msg(D_PUSH, "CR response was sent by client ('%s')", m); } + /** * Parse the keyword for the AUTH_PENDING request * @param buffer buffer containing the keywords, the buffer's @@ -440,7 +464,6 @@ const char *const peer_info = tls_multi->peer_info; unsigned int proto = extract_iv_proto(peer_info); - /* Calculate the maximum timeout and subtract the time we already waited */ unsigned int max_timeout = max_uint(tls_multi->opt.renegotiate_seconds / 2, tls_multi->opt.handshake_window); @@ -731,6 +754,11 @@ client_max_mtu, o->ce.tun_mtu, o->ce.tun_mtu); } } + + if (c->options.acc_negotiated_protocols) + { + push_option_fmt(gc, push_list, M_USAGE, "custom-control %d A:6 %s", c->options.app_custom_protocol_len, c->options.acc_negotiated_protocols); + } } #if defined(__GNUC__) || defined(__clang__) diff --git a/src/openvpn/push.h b/src/openvpn/push.h index dff945f..3aca8a7 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -52,6 +52,7 @@ int process_incoming_push_request(struct context *c); + /** * @brief Handles the receiving of a push-update message and applies updates to the specified * options. @@ -92,7 +93,7 @@ void receive_exit_message(struct context *c); -void server_pushed_info(const struct buffer *buffer, const int adv); +void server_pushed_info(struct context *c, const struct buffer *buffer, const int adv); void receive_cr_response(struct context *c, const struct buffer *buffer); diff --git a/src/openvpn/push_util.c b/src/openvpn/push_util.c index fcc5411..6462a97 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -370,4 +370,4 @@ RETURN_UPDATE_STATUS(n_sent); } -#endif /* ifdef ENABLE_MANAGEMENT */ +#endif /* ifdef ENABLE_MANAGEMENT */ \ No newline at end of file diff --git a/src/openvpn/push_util.h b/src/openvpn/push_util.h new file mode 100644 index 0000000..c9cc680 --- /dev/null +++ b/src/openvpn/push_util.h @@ -0,0 +1,34 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 OpenVPN Inc <[email protected]> + * Copyright (C) 2025 Arne Schwabe <[email protected]> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "buffer.h" + +/** + * Extract a field from buf that end with the \c sep character. The + * returned string is allocated in the gc_arena. If the seperator character + * is not found, the function returns the nullptr. + */ +char * +extract_field(struct buffer *buf, char sep, struct gc_arena *gc); diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index d5bd602..12e19aa 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -55,6 +55,7 @@ #include "crypto_epoch.h" #include "ssl.h" +#include "acc.h" #include "ssl_verify.h" #include "ssl_backend.h" #include "ssl_ncp.h" @@ -2054,6 +2055,11 @@ } } } + + if (session->opt->acc_protocols) + { + buf_printf(&out, "IV_ACC=%d,6:A,%s\n", ACC_MAX_MSG_LEN, session->opt->acc_protocols); + } } } diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index db61198..3b326a7 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -452,6 +452,9 @@ size_t ekm_size; bool dco_enabled; /**< Whether keys have to be installed in DCO or not */ + + /** The app custom control protocols we should announce as supported */ + const char *acc_protocols; }; /** @addtogroup control_processor diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 5954902..cfa5efc 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -103,9 +103,11 @@ @TEST_CFLAGS@ ssl_testdriver_LDFLAGS = @TEST_LDFLAGS@ $(OPTIONAL_CRYPTO_LIBS) ssl_testdriver_SOURCES = test_ssl.c \ + test_acc.c test_acc.h \ mock_msg.c mock_msg.h test_common.h \ - mock_management.c \ + mock_management.c mock_management.h \ mock_ssl_dependencies.c mock_win32_execve.c \ + $(top_srcdir)/src/openvpn/acc.c \ $(top_srcdir)/src/openvpn/argv.c \ $(top_srcdir)/src/openvpn/base64.c \ $(top_srcdir)/src/openvpn/buffer.c \ @@ -127,6 +129,7 @@ $(top_srcdir)/src/openvpn/run_command.c \ $(top_srcdir)/src/openvpn/ssl_openssl.c \ $(top_srcdir)/src/openvpn/ssl_mbedtls.c \ + $(top_srcdir)/src/openvpn/ssl_ncp.c \ $(top_srcdir)/src/openvpn/ssl_util.c \ $(top_srcdir)/src/openvpn/ssl_verify_mbedtls.c \ $(top_srcdir)/src/openvpn/ssl_verify_openssl.c \ @@ -375,8 +378,10 @@ misc_testdriver_SOURCES = test_misc.c \ test_schedule.c test_schedule.h \ + mock_management.c mock_management.h \ mock_msg.c test_common.h \ mock_get_random.c \ + $(top_srcdir)/src/openvpn/base64.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/options_util.c \ $(top_srcdir)/src/openvpn/ssl_util.c \ @@ -388,6 +393,7 @@ $(top_srcdir)/src/openvpn/siphash.c \ $(top_srcdir)/src/openvpn/siphash_reference.c + push_update_msg_testdriver_CFLAGS = -I$(top_srcdir)/src/openvpn \ -I$(top_srcdir)/src/compat \ -I$(top_srcdir)/tests/unit_tests/openvpn \ diff --git a/tests/unit_tests/openvpn/mock_management.c b/tests/unit_tests/openvpn/mock_management.c index 77f6ce2..e3bf828 100644 --- a/tests/unit_tests/openvpn/mock_management.c +++ b/tests/unit_tests/openvpn/mock_management.c @@ -29,8 +29,11 @@ #include "syshead.h" +#include <stdlib.h> #include "manage.h" +#include "mock_management.h" + #ifdef ENABLE_MANAGEMENT struct management *management; /* GLOBAL */ @@ -61,3 +64,15 @@ management_sleep(const int n) { } + +void +init_mock_management(void) +{ + ALLOC_OBJ_CLEAR(management, struct management); +} + +void +uninit_mock_management(void) +{ + free(management); +} \ No newline at end of file diff --git a/tests/unit_tests/openvpn/mock_management.h b/tests/unit_tests/openvpn/mock_management.h new file mode 100644 index 0000000..e21aec43 --- /dev/null +++ b/tests/unit_tests/openvpn/mock_management.h @@ -0,0 +1,32 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 OpenVPN Inc <[email protected]> + * Copyright (C) 2025 Arne Schwabe <[email protected]> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MOCK_MANAGEMENT_H +#define MOCK_MANAGEMENT_H +void init_mock_management(void); + +void uninit_mock_management(void); + +#endif diff --git a/tests/unit_tests/openvpn/mock_push_dependencies.c b/tests/unit_tests/openvpn/mock_push_dependencies.c index 112bcfd..c16d0cb 100644 --- a/tests/unit_tests/openvpn/mock_push_dependencies.c +++ b/tests/unit_tests/openvpn/mock_push_dependencies.c @@ -43,7 +43,8 @@ uint64_t flags = OPT_P_UP | OPT_P_ROUTE_EXTRAS | OPT_P_SOCKBUF | OPT_P_SOCKFLAGS | OPT_P_SETENV | OPT_P_SHAPER | OPT_P_TIMER | OPT_P_COMP | OPT_P_PERSIST | OPT_P_MESSAGES | OPT_P_EXPLICIT_NOTIFY | OPT_P_ECHO | OPT_P_PULL_MODE - | OPT_P_PEER_ID | OPT_P_NCP | OPT_P_PUSH_MTU | OPT_P_ROUTE | OPT_P_DHCPDNS; + | OPT_P_PEER_ID | OPT_P_NCP | OPT_P_PUSH_MTU | OPT_P_ROUTE | OPT_P_DHCPDNS + | OPT_P_ACC; return flags; } diff --git a/tests/unit_tests/openvpn/test_acc.c b/tests/unit_tests/openvpn/test_acc.c new file mode 100644 index 0000000..2b3e2dc --- /dev/null +++ b/tests/unit_tests/openvpn/test_acc.c @@ -0,0 +1,236 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 OpenVPN Inc <[email protected]> + * Copyright (C) 2025 Arne Schwabe <[email protected]> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "syshead.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "acc.h" +#include "test_acc.h" + +#include "mock_msg.h" + +/* Mock functions */ +bool +send_control_channel_string_dowork(struct tls_session *session, + const char *str, int msglevel) +{ + assert_false(true); + return false; +} + +void +send_push_reply_auth_token(struct tls_multi *multi) +{ +} + +void +auth_set_client_reason(struct tls_multi *multi, const char *reason) +{ +} + +#define MSG_PAYLOAD "FgMBARkBAAEVAwNanUalkEDDxLBpZw9JnjI3tBM" \ + "r4engpg/cDgS48DMaxyCdbzofNjQJU9Y0DD66z/notFT0cF1d60nTS" \ + "4md4OJwoQAwEwITAxMBwCzAMACfzKnMqMyqwCvALwCewCTAKABrwCP" \ + "AJwBnwArAFAA5wAnAEwAzAQAAnP8BAAEAAAsABAMAAQIACgAWABQAH" \ + "QAXAB4AGQAYAQABAQECAQMBBAAWAAAAFwAAAA0AMAAuBAMFAwYDCAc" \ + "ICAgaCBsIHAgJCAoICwgECAUIBgQBBQEGAQMDAwEDAgQCBQIGAgArA" \ + "AUEAwQDAwAtAAIBAQAzACYAJAAdACCk4Tz2PB/2FsxOfOIV4gn/NyJ" \ + "93rQwityP9eKSijXgcQ==" + +void +test_acc_parse_client_messages(void **state) +{ + /* ensure that the messages get to mock_msg */ + x_debug_level = 3; + management = (void *)0x12345678; + struct gc_arena gc = gc_new(); + + /* contains a TLS 1.3 ClientHello */ + const char msg[] = "ACC,cck1,384,6," MSG_PAYLOAD; + + struct buffer msg_buf; + struct context c = { 0 }; + c.options.mode = MODE_SERVER; + + ALLOC_OBJ_CLEAR_GC(c.c2.tls_multi, struct tls_multi, &gc); + ALLOC_OBJ_CLEAR_GC(c.c2.tls_multi, struct tls_multi, &gc); + + c.c2.tls_multi->session[TM_ACTIVE].opt = &c.c2.tls_multi->opt; + c.c2.tls_multi->session[TM_ACTIVE].opt->mda_context = &c.c2.mda_context; + buf_set_read(&msg_buf, (void *)msg, sizeof(msg)); + receive_acc_message(&c, &msg_buf); + + assert_string_equal(mock_msg_buf, "Received custom app control message (protocol 'cck1')"); + assert_string_equal(mock_managment_buf, ">CLIENT:ACC,0,0,cck1,0," MSG_PAYLOAD); + + const char flower_msg[] = "ACC,flower,48,6,SSBhbSAAIEtlcm1pdCD//SB0aGUg77+94oCPZnJvZyEAAA=="; + buf_set_read(&msg_buf, (void *)flower_msg, sizeof(flower_msg)); + receive_acc_message(&c, &msg_buf); + assert_string_equal(mock_msg_buf, "Received custom app control message (protocol 'flower')"); + assert_string_equal(mock_managment_buf, ">CLIENT:ACC,0,0,flower,0,SSBhbSAAIEtlcm1pdCD//SB0aGUg77+94oCPZnJvZyEAAA=="); + + CLEAR(mock_managment_buf); + const char msg_incorrect_len[] = "ACC,fortune,62,6,InsgIm1lIjogImZyb2ciLCAAeGZm/SJtc2ciOiAiSSBhbSAAS2VybWl0IiB9Ig=="; + buf_set_read(&msg_buf, (void *)msg_incorrect_len, sizeof(msg_incorrect_len)); + receive_acc_message(&c, &msg_buf); + const char *error = "WARNING: Received malformed custom app control channel " + "(field length 62, payload length 64 mismatch) message " + "control message: 496e7367 496d316c 496a6f67 496d5a79 " + "62326369 4c434141 65475a6d 2f534a7[more...]"; + + assert_string_equal(mock_msg_buf, error); + assert_string_equal(mock_managment_buf, ""); + + CLEAR(mock_managment_buf); + const char msg_fragment[] = "ACC,power,12,6F,aGVsbG8gdw=="; + buf_set_read(&msg_buf, (void *)msg_fragment, sizeof(msg_fragment)); + receive_acc_message(&c, &msg_buf); + assert_string_equal(mock_msg_buf, "Received custom app control message (protocol 'power', fragment)"); + assert_string_equal(mock_managment_buf, ">CLIENT:ACC,0,0,power,1,aGVsbG8gdw=="); + + CLEAR(mock_managment_buf); + /* check two encoding present */ + const char flower_msg_two_encs[] = "ACC,flower,48,6A,SSBhbSAAIEtlcm1pdCD//SB0aGUg77+94oCPZnJvZyEAAA=="; + buf_set_read(&msg_buf, (void *)flower_msg_two_encs, sizeof(flower_msg_two_encs)); + receive_acc_message(&c, &msg_buf); + + error = "WARNING: Received malformed custom app control channel (number of " + "encodings must be exactly one (B64=1, ASCII=1)) message control " + "message: 53534268 62534141 4945746c 636d3170 6443442f 2f534230 " + "61475567 37372b3[more...]"; + assert_string_equal(mock_msg_buf, error); + assert_string_equal(mock_managment_buf, ""); + + /* no encoding present */ + const char flower_msg_no_encs[] = "ACC,flower,48,,SSBhbSAAIEtlcm1pdCD//SB0aGUg77+94oCPZnJvZyEAAA=="; + buf_set_read(&msg_buf, (void *)flower_msg_no_encs, sizeof(flower_msg_no_encs)); + receive_acc_message(&c, &msg_buf); + + error = "WARNING: Received malformed custom app control channel (number of " + "encodings must be exactly one (B64=0, ASCII=0)) message control " + "message: 53534268 62534141 4945746c 636d3170 6443442f 2f534230 " + "61475567 37372b3[more...]"; + assert_string_equal(mock_msg_buf, error); + assert_string_equal(mock_managment_buf, ""); + + x_debug_level = 0; + management = NULL; + gc_free(&gc); +} + +void +test_parse_acc_parameters(void **state) +{ + struct gc_arena gc = gc_new(); + /* parameters to INFO message */ + const char *info_acc_msg = "4096 A:6 flower:happy"; + + int max_len = 0; + const char *protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, true, &gc); + assert_string_equal(protocols, "flower:happy"); + assert_int_equal(max_len, 1280); + + /* a client does not accept a length longer than it supports */ + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, false, &gc); + assert_null(protocols); + + /* message with lower limit than our, should work for client and server */ + info_acc_msg = "1111 A:6 flower:happy"; + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, false, &gc); + assert_string_equal(protocols, "flower:happy"); + assert_int_equal(max_len, 1111); + + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, true, &gc); + assert_string_equal(protocols, "flower:happy"); + assert_int_equal(max_len, 1111); + + /* more encodings than supported. Server should be okay, client should fail */ + info_acc_msg = "1200 A:6:B flower:happy"; + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, false, &gc); + assert_null(protocols); + + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, true, &gc); + assert_string_equal(protocols, "flower:happy"); + assert_int_equal(max_len, 1200); + + /* extra parameter */ + info_acc_msg = "1400 A:6 flower:happy 77"; + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, true, &gc); + assert_null(protocols); + + protocols = parse_acc_parameters(info_acc_msg, " ", &max_len, false, &gc); + assert_null(protocols); + + + const char *iv_acc_capabilities = "1400,A:6,flower:happy:power"; + protocols = parse_acc_parameters(iv_acc_capabilities, ",", &max_len, true, &gc); + assert_string_equal(protocols, "flower:happy:power"); + + /* too small protocol length */ + info_acc_msg = "32 A:6 flower:happy"; + protocols = parse_acc_parameters(info_acc_msg, ",", &max_len, true, &gc); + assert_null(protocols); + + /* non-numerical protocol length */ + info_acc_msg = "abff A:6 flower:happy 77"; + protocols = parse_acc_parameters(info_acc_msg, ",", &max_len, true, &gc); + assert_null(protocols); + + /* non-numerical protocol length */ + info_acc_msg = "abff A:6 flower:happy 77"; + protocols = parse_acc_parameters(info_acc_msg, ",", &max_len, true, &gc); + assert_null(protocols); + + gc_free(&gc); +} + +void +test_acc_common_protocols(void **state) +{ + struct context c = { 0 }; + c.options.gc = gc_new(); + + ALLOC_OBJ_CLEAR_GC(c.c2.tls_multi, struct tls_multi, &c.options.gc); + + c.c2.tls_multi->peer_info = "IV_PROTO=4321\nIV_ACC=1400,A:6,flower:happy:power"; + c.options.acc_protocols = "power:flower:star"; + + determine_common_acc_protocols(&c); + + assert_int_equal(c.options.app_custom_protocol_len, ACC_MAX_MSG_LEN); + assert_string_equal(c.options.acc_negotiated_protocols, "flower:power"); + + gc_free(&c.options.gc); +} \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_acc.h b/tests/unit_tests/openvpn/test_acc.h new file mode 100644 index 0000000..11ecf32 --- /dev/null +++ b/tests/unit_tests/openvpn/test_acc.h @@ -0,0 +1,33 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 OpenVPN Inc <[email protected]> + * Copyright (C) 2025 Arne Schwabe <[email protected]> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef TEST_ACC_H +#define TEST_ACC_H +void test_acc_parse_client_messages(void **state); + +void test_parse_acc_parameters(void **state); + +void test_acc_common_protocols(void **state); +#endif diff --git a/tests/unit_tests/openvpn/test_misc.c b/tests/unit_tests/openvpn/test_misc.c index 5f03860..18e4356 100644 --- a/tests/unit_tests/openvpn/test_misc.c +++ b/tests/unit_tests/openvpn/test_misc.c @@ -40,11 +40,35 @@ #include "list.h" #include "mock_msg.h" #include "crypto.h" +#include "test_schedule.h" +#include "mock_management.h" +#include "multi.h" + #ifdef _WIN32 #include "win32-util.h" #endif -#include "test_schedule.h" +/* extra mocks */ +bool +apply_push_options(struct context *c, struct options *options, struct buffer *buf, + uint64_t permission_mask, uint64_t *option_types_found, + struct env_set *es, bool is_update) +{ + return true; +} + +bool +send_control_channel_string(struct context *c, const char *str, msglvl_t msglevel) +{ + check_expected_ptr(str); + return true; +} + +struct multi_instance * +lookup_by_cid(struct multi_context *m, const unsigned long cid) +{ + return *(m->instances); +} static void test_compat_lzo_string(void **state) @@ -492,5 +516,8 @@ main(void) { openvpn_unit_test_setup(); - return cmocka_run_group_tests(misc_tests, NULL, NULL); + init_mock_management(); + int ret = cmocka_run_group_tests(misc_tests, NULL, NULL); + uninit_mock_management(); + return ret; } diff --git a/tests/unit_tests/openvpn/test_ssl.c b/tests/unit_tests/openvpn/test_ssl.c index d6b63d0..b3889cd 100644 --- a/tests/unit_tests/openvpn/test_ssl.c +++ b/tests/unit_tests/openvpn/test_ssl.c @@ -52,6 +52,8 @@ #include "ssl_verify.h" #include "openvpn.h" +#include "test_acc.h" + /* Mock function to be allowed to include win32.c which is required for * getting the temp directory */ #ifdef _WIN32 @@ -87,6 +89,16 @@ return; } +/* Define a dummy dco cipher option to avoid linking against all the DCO + * units */ +#if defined(ENABLE_DCO) +const char * +dco_get_supported_ciphers(void) +{ + return "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305"; +} +#endif + /* generated using * openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout - \ * -noenc -sha256 -days 3650 -subj '/CN=ovpn-test-secp384r1' -nodes \ @@ -960,8 +972,10 @@ cmocka_unit_test(test_data_channel_known_vectors_epoch), cmocka_unit_test(test_data_channel_known_vectors_shortpktid), cmocka_unit_test(crypto_test_print_cert_details), - cmocka_unit_test(ssl_test_extract_peer_info) - + cmocka_unit_test(ssl_test_extract_peer_info), + cmocka_unit_test(test_acc_parse_client_messages), + cmocka_unit_test(test_parse_acc_parameters), + cmocka_unit_test(test_acc_common_protocols) }; #if defined(ENABLE_CRYPTO_OPENSSL) -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1858?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: newchange Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I4a6a6ef41e15896b8b79d795b2a6de86b8ae4c84 Gerrit-Change-Number: 1858 Gerrit-PatchSet: 1 Gerrit-Owner: plaisthos <[email protected]> Gerrit-CC: openvpn-devel <[email protected]> _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel