@@ -435,6 +435,8 @@
src/compat/compat-strlcpy.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
@@ -826,6 +828,8 @@
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/base64.c
src/openvpn/crypto_epoch.c
@@ -842,6 +846,7 @@
src/openvpn/packet_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
@@ -437,6 +437,12 @@
Additional authentication methods supported by the client.
This may be set by the client UI/GUI using ``--setenv``.
+ :code:`IV_ACC=<max_msg_len>,<flags>,<protocols>`
+ If the option ``--app-custom-control`` is set, the client will
+ send the supported max ACC message length, the supported formats
+ as (:code:`A:6`) and the list of configured protocols (e.g.
+ :code:`flower:happy:power`)
+
The following flags depend on which compression formats are compiled in
and whether compression is allowed by options. See `Protocol options`_
for more details.
@@ -144,3 +144,17 @@
--management-up-down
Report tunnel up/down events to management interface.
+
+--app-custom-control protocol-list
+ Sets the list of supported app custom control protocols.
+ These protocols are announced to the server via the IV_ACC peer info
+ variable when this option is used on a client.
+ If the option is set on a server it will negotiate common protocols
+ with the client.
+
+ The protocol-list is a : separated list of protocol identifiers. Protocol
+ identifiers must consist of alphanumeric characters, :code:`-`, and
+ :code:`_`.
+
+ See management-notes.txt in the OpenVPN distribution and the OpenVPN RFC
+ draft for a description of app custom control protocol support.
@@ -1006,3 +1006,12 @@
X509_1_L=BISHKEK
X509_1_ST=NA
X509_1_C=KG
+
+:code:`acc_protocols`
+ If ``--app-custom-control`` is configured and the client supports any of the
+ protocols that server also supports, this variable contains a list of
+ common protocols separated by :.
+
+:code:`acc_message_len`
+ if :code:`acc_protocols` is set, this variable contains the maximum
+ message length for app custom protocol message.
@@ -828,6 +828,59 @@
that is required to continue the connection (unlike MFA or opening a browser)
this message is not negotiated but can just be sent as a gratuitous message.
+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 server and instruct
+the client that the server will send control messages to the client using
+the ACC protocol.
+
+The server should then use client-acc-msg command to send app custom
+control messages to the client to facilitate the pending authentication.
+
+COMMAND -- client-acc-msg (OpenVPN 2.8 or higher)
+-------------------------------------------------
+
+On an OpenVPN instance running in server mode this command 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 identifier that this message should be
+ tagged with.
+
+flags -- a concatenation of letters for the encoding ('A' or '6')
+ and the lette 'F' if the message is a fragment. E.g.
+ "6F" 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)
+------------------------------------------
+
+When OpenVPN is running in --client mode this command sends an app
+custom command to the server. 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. Since a client has only one peer there is no need to specify the
+client id and key id.
+
+
COMMAND -- client-deny (OpenVPN 2.1 or higher)
-----------------------------------------------
@@ -1302,6 +1355,52 @@
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. 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.
+
+ Note that the OpenVPN implementation operates here with the robustness
+ principle ("be conservative in what you send, be liberal in what you
+ accept" - RFC 793 and others) and allows receiving messages longer than
+ maximum allowed message length. Implementations should be prepared to
+ handle messages that are longer than the maximum allowed message length.
+
+ OpenVPN does not verify that the message is valid base64. A peer that
+ sends invalid base64 payload will be passed to the management interface.
+ An implementation should be prepared to handle invalid base64 and
+ handle accordingly (e.g. disconnect the client via client-deny or
+ ignore such messages).
+
+(7) App custom control protocol message
+
+ This message informs the client about the common ACC
+ protocols and the maximum message length for an ACC message.
+
+ In client mode:
+
+ >ACC_PROTOCOLS:protocols={protocol_list},message_len={max_msg_len}
+
+ protocol_list is the list of supported protocols by both server and client.
+ For example flower:power:sys_info would indicate these three protocols
+ are supported. max_msg_len specifies the maximum message length that the
+ peer supports.
+
+ In server mode these information are available in the acc_protocols and
+ acc_message_len variables of the client environment.
Variables:
@@ -45,6 +45,7 @@
sbin_PROGRAMS = openvpn
openvpn_SOURCES = \
+ acc.c acc.h \
argv.c argv.h \
auth_token.c auth_token.h \
base64.c base64.h \
@@ -124,7 +125,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 \
pushlist.h \
reflect_filter.c reflect_filter.h \
reliable.c reliable.h \
new file mode 100644
@@ -0,0 +1,445 @@
+/*
+ * 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 <sales@openvpn.net>
+ * Copyright (C) 2025 Arne Schwabe <arne@rfc2549.org>
+ *
+ *
+ * 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 "error.h"
+#include "acc.h"
+#include "forward.h"
+#include "options_util.h"
+#include "ssl_util.h"
+#include "ssl_ncp.h"
+#include "multi.h"
+
+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(CBPTR(buf), payload_len, &b64out) >= 0);
+ gc_addspecial(b64out, free, &gc);
+ payload_msg = b64out;
+ }
+ else
+ {
+ payload_msg = CBSTR(buf);
+ }
+
+ if (management)
+ {
+ struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
+
+ if (c->options.mode == MODE_SERVER)
+ {
+ /* server mode. Report client context */
+ struct man_def_auth_context *mda = session->opt->mda_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" : "");
+}
+
+
+int
+parse_acc_message_flags(const char *flags, const char **err_reason, struct gc_arena *gc)
+{
+ 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;
+}
+
+/**
+ * Wrapper around parse_acc_message_flags to also include the step of
+ * extracting the flags from the buffer and forwarding the buffer
+ * beyond the flags field.
+ */
+static int
+parse_acc_message_flags_buf(struct buffer *buf, const char **err_reason, struct gc_arena *gc)
+{
+ const char *flags = buf_extract_field(buf, ',', gc);
+
+ if (!flags)
+ {
+ *err_reason = "could not extract flags field";
+ return -1;
+ }
+ return parse_acc_message_flags(flags, err_reason, gc);
+}
+
+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;
+
+ const struct tls_multi *multi = c->c2.tls_multi;
+ if (!multi->acc_negotiated_protocols)
+ {
+ err_reason = "ACC not enabled or no negotiated protocols";
+ goto err;
+ }
+
+ if (!buf_advance(&buf, strlen("ACC")) || buf_read_u8(&buf) != ',')
+ {
+ err_reason = "missing , after ACC";
+ goto err;
+ }
+
+ /* extract protocol, payload length, flags substrings */
+ const char *protocol = buf_extract_field(&buf, ',', &gc);
+ if (!protocol)
+ {
+ err_reason = "could not extract protocol field";
+ goto err;
+ }
+
+ if (!tls_item_in_cipher_list(protocol, multi->acc_negotiated_protocols))
+ {
+ struct buffer msg_buf = alloc_buf_gc(1024, &gc);
+ buf_printf(&msg_buf, "protocol '%s' not in list of negotiated protocols '%s'",
+ protocol, multi->acc_negotiated_protocols);
+ err_reason = buf_cstr(&msg_buf);
+
+
+ goto err;
+ }
+
+ /* We do not check the length here. We make no promises to support
+ * anything larger than our announced message length but according to
+ * the robustness principle
+ * ("be conservative in what you send, be liberal in what you accept" - RFC 793 and others)
+ * we accept also longer messages */
+
+ int payload_len;
+ const char *payload_len_str = buf_extract_field(&buf, ',', &gc);
+ if (!payload_len_str
+ || !atoi_constrained(payload_len_str, &payload_len, "acc_payload_len", 0, TLS_CHANNEL_BUF_SIZE - 1, D_PUSH_ERRORS))
+ {
+ err_reason = "could not extract payload length field";
+ goto err;
+ }
+
+ int flags = parse_acc_message_flags_buf(&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 "
+ "message (%s): %s",
+ err_reason,
+ format_hex(CBPTR(buffer), BLEN(buffer), 80, &gc));
+ gc_free(&gc);
+}
+
+
+bool
+send_acc_message(struct context *c,
+ struct tls_multi *multi,
+ struct tls_session *session,
+ const char *protocol, bool fragment,
+ const char *acc_msg_payload, bool base64)
+{
+ /*
+ * length of the protocol string + one ','
+ * 2 for the encoding (A or 6), potential F (fragment), and 1 for the ',',
+ * 4 for the message size itself and one comma
+ * 1 for the comma before the message itself */
+ const size_t max_header_size = strlen("ACC,") + strlen(protocol) + 1 + 4 + 1 + 2 + 1;
+ size_t len = max_header_size + strlen(acc_msg_payload);
+
+ if (len > TLS_CHANNEL_BUF_SIZE)
+ {
+ msg(M_CLIENT, "message length (%zu) exceeds maximum possible message length (%d)", len, TLS_CHANNEL_BUF_SIZE);
+ return false;
+ }
+
+ if ((int)len > multi->acc_max_message_length)
+ {
+ msg(M_CLIENT, "header (%zu) + message size (%zu) length (%zu) exceeds "
+ "maximum allowed length negotiated with peer (%d)",
+ max_header_size, strlen(acc_msg_payload), len,
+ multi->acc_max_message_length);
+ return false;
+ }
+
+ struct gc_arena gc = gc_new();
+ /* add 1 for the final \0 */
+ struct buffer buf = alloc_buf_gc(len + 1, &gc);
+
+ /* Example message: ACC,muppets,15,A,I am Miss Piggy */
+ buf_printf(&buf, "ACC,%s,%zu,%s%s,%s", protocol,
+ strlen(acc_msg_payload),
+ base64 ? "6" : "A",
+ fragment ? "F" : "",
+ acc_msg_payload);
+
+ bool ret = send_control_channel_string_dowork(session, BSTR(&buf), D_PUSH);
+ reschedule_multi_process(c);
+ gc_free(&gc);
+ return ret;
+}
+
+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 be 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_ACC.");
+ 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;
+}
+
+struct buffer
+get_list_intersection(struct gc_arena *gc, const char *list1, const char *list2)
+{
+ char *tmp = string_alloc(list1, NULL);
+ char *lasts = NULL;
+ const char *token = strtok_r(tmp, ":", &lasts);
+
+ /* The maximum possible length is the shorter of the two input lists */
+ size_t max_len = min_size(strlen(list1), strlen(list2));
+
+ struct buffer common_protocols = alloc_buf_gc(max_len + 1, gc);
+
+ while (token)
+ {
+ /* check for every items is in both lists and not already in the
+ * common list */
+ if (tls_item_in_cipher_list(token, list2)
+ && !tls_item_in_cipher_list(token, buf_cstr(&common_protocols)))
+ {
+ if (buf_len(&common_protocols) > 0)
+ {
+ buf_write_u8(&common_protocols, ':');
+ }
+ buf_printf(&common_protocols, "%s", token);
+ }
+ token = strtok_r(NULL, ":", &lasts);
+ }
+ free(tmp);
+ return common_protocols;
+}
+
+void
+determine_common_acc_protocols(struct tls_multi *multi, struct tls_session *session)
+{
+ if (!multi->opt.acc_protocols)
+ {
+ /* No app custom protocols have been configured server-side. */
+ return;
+ }
+
+ if (multi->acc_negotiated_protocols)
+ {
+ /* We already have determined the common protocols. */
+ return;
+ }
+
+ struct gc_arena gc = gc_new();
+
+ /* example IV_ACC: IV_ACC=2048,6:A,flower:power */
+ const char *peer_info = multi->peer_info;
+ const char *iv_acc = extract_var_peer_info(peer_info, "IV_ACC=", &gc);
+ if (!iv_acc)
+ {
+ gc_free(&gc);
+ return;
+ }
+
+ int max_acc_len = 0;
+ const 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;
+ }
+
+ struct buffer common_protocols = get_list_intersection(&gc, multi->opt.acc_protocols, protocols);
+
+ if (buf_len(&common_protocols) == 0)
+ {
+ msg(D_PUSH_DEBUG, "WARNING: No common app custom control protocol.");
+ gc_free(&gc);
+ return;
+ }
+
+ multi->acc_negotiated_protocols = string_alloc(buf_str(&common_protocols), NULL);
+ multi->acc_max_message_length = max_acc_len;
+
+ setenv_str(session->opt->es, "acc_protocols", multi->acc_negotiated_protocols);
+ setenv_int(session->opt->es, "acc_message_len", multi->acc_max_message_length);
+
+ gc_free(&gc);
+}
+
+void
+notify_client_acc_protocols(struct tls_multi *multi)
+{
+#ifdef ENABLE_MANAGEMENT
+ if (management)
+ {
+ msg(M_CLIENT, ">ACC_PROTOCOLS:protocols=%s,message_len=%d",
+ multi->acc_negotiated_protocols, multi->acc_max_message_length);
+ }
+#endif
+}
new file mode 100644
@@ -0,0 +1,130 @@
+/*
+ * 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 <sales@openvpn.net>
+ * Copyright (C) 2025 Arne Schwabe <arne@rfc2549.org>
+ *
+ *
+ * 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 multi.acc_negotiated_protocols
+ * to the common protocols of server and client and
+ * multi.acc_max_message_length 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 multi.acc_negotiated_protocols will be set to NULL.
+ *
+ * @param multi the multi instance to use the set the app custom protocols.
+ * @param session the session to read the client env from
+ */
+void
+determine_common_acc_protocols(struct tls_multi *multi, struct tls_session *session);
+
+/**
+ * Flags returned by the parse_acc_message_flags method
+ */
+enum acc_message_flag
+{
+ ACC_MESSAGE_FLAGS_ASCII = 1,
+ ACC_MESSAGE_FLAGS_BASE64 = 2,
+ ACC_MESSAGE_FLAGS_FRAGMENT = 4,
+};
+
+/**
+ * Parses the flags field of an app control message If there is an
+ * error parsing the flags field the method returns -1 and
+ * puts the error reason in err_reason
+ */
+int
+parse_acc_message_flags(const char *flags, const char **err_reason, struct gc_arena *gc);
+
+/**
+ * Calculates the intersection of two list that are both separated by :
+ * Items in the result will have the same ordering as in the list1
+ * parameters
+ * @param gc Returned buffer will be allocated in this gc
+ * @param list1 First list to intersect
+ * @param list2 Second list to intersect.
+ * @return A (potentially empty) buffer containing an intersection list
+ */
+struct buffer
+get_list_intersection(struct gc_arena *gc, const char *list1, const char *list2);
+
+/**
+ * Notifies a client via management interface of the negotiated app
+ * custom protocols
+ * @param multi the tls_multi instance of the connection
+ */
+void
+notify_client_acc_protocols(struct tls_multi *multi);
+#endif
@@ -42,6 +42,7 @@
#include "dco.h"
#include "auth_token.h"
#include "tun_afunix.h"
+#include "acc.h"
#include "memdbg.h"
@@ -257,11 +258,11 @@
}
else if (buf_string_match_head_str(buf, "INFO_PRE"))
{
- server_pushed_info(buf, 8);
+ peer_pushed_info(c, buf, 8);
}
else if (buf_string_match_head_str(buf, "INFO"))
{
- server_pushed_info(buf, 4);
+ peer_pushed_info(c, buf, 4);
}
else if (buf_string_match_head_str(buf, "CR_RESPONSE"))
{
@@ -271,6 +272,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);
@@ -56,6 +56,7 @@
#include "schedule.h"
#include "options_string.h"
#include "resolve.h"
+#include "acc.h"
#include "memdbg.h"
@@ -2279,6 +2280,13 @@
}
}
+ struct tls_multi *multi = c->c2.tls_multi;
+ if (multi && multi->acc_negotiated_protocols)
+ {
+ buf_printf(&out, " custom-control %d A:6 %s",
+ multi->acc_max_message_length, multi->acc_negotiated_protocols);
+ }
+
if (BLENZ(&out) > strlen(header))
{
msg(D_HANDSHAKE, "%s", BSTR(&out));
@@ -2522,6 +2530,11 @@
flags |= (OPT_P_ROUTE | OPT_P_DHCPDNS);
}
+ if (c->options.acc_protocols)
+ {
+ flags |= OPT_P_ACC;
+ }
+
return flags;
}
@@ -2730,6 +2743,18 @@
frame->tun_mtu = min_int(frame->tun_max_mtu, c->options.ce.tun_mtu);
}
+ if (found & OPT_P_ACC && c->options.pushed_acc_negotiated_protocols)
+ {
+ struct tls_multi *multi = c->c2.tls_multi;
+ free(multi->acc_negotiated_protocols);
+ multi->acc_negotiated_protocols = string_alloc(c->options.pushed_acc_negotiated_protocols, NULL);
+ multi->acc_max_message_length = c->options.pushed_acc_max_message_length;
+
+ msg(D_PUSH, "OPTIONS IMPORT: ACC protocols: %s, msg len: %d",
+ multi->acc_negotiated_protocols, multi->acc_max_message_length);
+ notify_client_acc_protocols(multi);
+ }
+
return true;
}
@@ -3352,6 +3377,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;
@@ -4306,6 +4332,24 @@
}
#endif /* ifdef TARGET_ANDROID */
+static bool
+management_acc_msg(void *arg,
+ struct buffer_list *input) /* ownership transferred */
+{
+ struct context *c = arg;
+
+ bool ret = false;
+ if (buffer_list_defined(input) && c->c2.tls_multi)
+ {
+ struct tls_multi *multi = c->c2.tls_multi;
+ struct tls_session *session = &multi->session[TM_ACTIVE];
+
+ ret = management_send_acc_message(c, multi, session, input);
+ }
+
+ buffer_list_free(input);
+ return ret;
+}
#endif /* ifdef ENABLE_MANAGEMENT */
void
@@ -4322,6 +4366,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
@@ -45,9 +45,11 @@
#include "push.h"
#include "multi.h"
#include "resolve.h"
+#include "acc.h"
#include "memdbg.h"
#include "run_command.h"
+#include "ssl_ncp.h"
#ifdef ENABLE_PKCS11
#include "pkcs11.h"
@@ -1081,6 +1083,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);
@@ -1205,6 +1236,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;
@@ -1759,6 +1813,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(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");
@@ -2975,7 +3040,9 @@
"bytes_sent=",
"bytes_received=",
"session_id=",
- "session_state=" };
+ "session_state=",
+ "acc_protocols=",
+ "acc_message_len=" };
if (env_filter_level == 0)
{
@@ -3130,6 +3197,7 @@
}
}
+
void
management_connection_established(struct management *man, struct man_def_auth_context *mdac,
const struct env_set *es)
@@ -4372,6 +4440,58 @@
}
}
+bool
+management_send_acc_message(struct context *c, struct tls_multi *multi, struct tls_session *session,
+ struct buffer_list *input)
+{
+ /* also ensures that are enough inputs in the list to safely call
+ * buffer_list_peek without extra NULL checks */
+ if (!input || input->size < 3)
+ {
+ msg(M_CLIENT, "not enough input lines for sending acc message, "
+ "missing protocol, flag and/or messages lines");
+ return false;
+ }
+
+ struct gc_arena gc = gc_new();
+
+ const struct buffer *proto_buf = buffer_list_peek(input);
+ const char *protocol = string_alloc(CBSTR(proto_buf), &gc);
+ buffer_list_pop(input);
+
+ const struct buffer *encoding_buf = buffer_list_peek(input);
+
+ const char *acc_err_reason = NULL;
+ int acc_flags = parse_acc_message_flags(buf_cstr(encoding_buf), &acc_err_reason, &gc);
+ if (acc_flags < 0)
+ {
+ msg(M_CLIENT, "ERROR: parsing acc message flags failed: %s", acc_err_reason);
+ goto error;
+ }
+
+ buffer_list_pop(input);
+
+ buffer_list_aggregate_separator(input, 10000, "");
+ const struct buffer *acc_msg_buf = buffer_list_peek(input);
+
+ if (!multi->acc_negotiated_protocols
+ || !tls_item_in_cipher_list(protocol, multi->acc_negotiated_protocols))
+ {
+ msg(M_CLIENT, "ERROR: protocol not in the list of negotiated protocols: %s",
+ np(multi->acc_negotiated_protocols));
+ goto error;
+ }
+
+ bool ret = send_acc_message(c, multi, session, protocol,
+ acc_flags & ACC_MESSAGE_FLAGS_FRAGMENT,
+ buf_cstr(acc_msg_buf),
+ acc_flags & ACC_MESSAGE_FLAGS_BASE64);
+ gc_free(&gc);
+ return ret;
+error:
+ gc_free(&gc);
+ return false;
+}
#else /* ifdef ENABLE_MANAGEMENT */
#include "win32.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;
@@ -503,6 +512,14 @@
void man_persist_client_stats(struct management *man, struct context *c);
+/** 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, struct buffer_list *input);
#endif /* ifdef ENABLE_MANAGEMENT */
/**
@@ -53,6 +53,7 @@
#include "ssl_util.h"
#include "dco.h"
#include "reflect_filter.h"
+#include "acc.h"
/*#define MULTI_DEBUG_EVENT_LOOP*/
@@ -4085,6 +4086,26 @@
}
}
+static const char *
+calculate_app_control_extra(struct multi_instance *mi, struct gc_arena *gc)
+{
+ struct context *c = &mi->context;
+ struct tls_multi *multi = c->c2.tls_multi;
+
+ if (!multi->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", multi->acc_max_message_length,
+ multi->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)
@@ -4095,10 +4116,22 @@
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, &gc);
+ if (!extra)
+ {
+ gc_free(&gc);
+ return false;
+ }
+ }
struct tls_session *session = lookup_session_by_mda_key_id(multi, mda_key_id);
if (!session)
{
+ gc_free(&gc);
return false;
}
@@ -4106,6 +4139,7 @@
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;
@@ -4149,6 +4183,37 @@
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;
+ }
+
+ ret = management_send_acc_message(&mi->context, multi, session, 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)
{
@@ -4185,6 +4250,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;
@@ -35,6 +35,7 @@
#include "syshead.h"
+#include "acc.h"
#include "buffer.h"
#include "error.h"
#include "common.h"
@@ -2552,6 +2553,8 @@
o->push_option_types_found = 0;
o->push_update_options_found = 0;
o->imported_protocol_flags = 0;
+ o->pushed_acc_max_message_length = 0;
+ o->pushed_acc_negotiated_protocols = NULL;
}
static void
@@ -7107,6 +7110,47 @@
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);
+ if (!string_class(p[1], CC_ALNUM | CC_COLON | CC_UNDERBAR | CC_DASH, 0))
+ {
+ msg(msglevel, "App custom control protocols must only contain "
+ "alphanumeric characters, '-', and '_'.");
+ goto err;
+ }
+ 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);
+
+ /* check if the list from the server is a real subset of our list */
+ struct gc_arena gc_local = gc_new();
+ struct buffer common_list = get_list_intersection(&gc_local, p[3], options->acc_protocols);
+
+ if (buf_len(&common_list) == 0 || !streq(p[3], buf_cstr(&common_list)))
+ {
+ msg(msglevel, "App custom control list from server contains "
+ "unsupported protocols");
+ gc_free(&gc_local);
+ goto err;
+ }
+ gc_free(&gc_local);
+
+ if (!streq(p[2], "6:A") && !streq(p[2], "A:6"))
+ {
+ msg(msglevel, "App custom control encoding must be base64 and ascii");
+ goto err;
+ }
+
+ if (!atoi_constrained(p[1], &options->pushed_acc_max_message_length,
+ "app custom message message length", ACC_MIN_MSG_LEN, ACC_MAX_MSG_LEN, msglevel))
+ {
+ goto err;
+ }
+ options->pushed_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)");
@@ -715,6 +715,18 @@
/* 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. This is a
+ * a temporary variable since options.c push parser cannot access
+ * tls multi struct */
+ const char *pushed_acc_negotiated_protocols;
+
+ /** the maximum length of an ACC control channel message via push message
+ * */
+ int pushed_acc_max_message_length;
+
bool vlan_tagging;
enum vlan_acceptable_frames vlan_accept;
uint16_t vlan_pvid;
@@ -764,8 +776,10 @@
#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)
-#define OPT_P_DEFAULT (~(OPT_P_INSTANCE | OPT_P_PULL_MODE | OPT_P_PEER_ID | 0x0ull))
+/* Options allowed in the normal configuration file (vs pushed options or in ccd files) */
+#define OPT_P_DEFAULT (~(OPT_P_INSTANCE | OPT_P_PULL_MODE | OPT_P_PEER_ID | OPT_P_ACC))
#define PULL_DEFINED(opt) ((opt)->pull)
@@ -27,6 +27,8 @@
#include "syshead.h"
#include "push.h"
+
+#include "acc.h"
#include "options.h"
#include "crypto.h"
#include "ssl.h"
@@ -221,9 +223,56 @@
#endif
}
+static void
+parse_server_pushed_info_acc(struct context *c, struct buffer *buf)
+{
+ int max_acc_len = 0;
+ struct tls_multi *multi = c->c2.tls_multi;
+
+ if (multi->acc_negotiated_protocols)
+ {
+ return;
+ }
+
+ if (!multi->opt.acc_protocols || !c->options.pull)
+ {
+ msg(D_PUSH_ERRORS, "Peer tried to push app custom control list "
+ "without client announcing support for it");
+ return;
+ }
+
+ /* example string to parse: ACC:1400 A:6 flower:happy */
+ /* Skip over the ACC: prefix */
+ buf_advance(buf, 4);
+
+ struct gc_arena gc = gc_new();
+ const char *protocols = parse_acc_parameters(buf_cstr(buf), " ", &max_acc_len, false, &gc);
+ if (!protocols)
+ {
+ msg(D_PUSH_ERRORS, "WARNING: Received ACC info command with invalid parameters");
+ gc_free(&gc);
+ return;
+ }
+
+ /* check if the list from the server is a real subset of our list */
+ struct buffer common_list = get_list_intersection(&gc, protocols, multi->opt.acc_protocols);
+
+ if (buf_len(&common_list) == 0 || !streq(protocols, buf_cstr(&common_list)))
+ {
+ msg(D_PUSH_ERRORS, "App custom control list from server contains "
+ "unsupported protocols");
+ gc_free(&gc);
+ return;
+ }
+
+ multi->acc_negotiated_protocols = string_alloc(protocols, NULL);
+ multi->acc_max_message_length = max_acc_len;
+ notify_client_acc_protocols(multi);
+ gc_free(&gc);
+}
void
-server_pushed_info(const struct buffer *buffer, const int adv)
+peer_pushed_info(struct context *c, const struct buffer *buffer, const int adv)
{
const char *m = "";
struct buffer buf = *buffer;
@@ -233,6 +282,12 @@
m = BSTR(&buf);
}
+ if (strncmp(m, "ACC:", 4) == 0)
+ {
+ parse_server_pushed_info_acc(c, &buf);
+ return;
+ }
+
#ifdef ENABLE_MANAGEMENT
struct gc_arena gc;
if (management)
@@ -257,7 +312,7 @@
gc_free(&gc);
}
#endif
- msg(D_PUSH, "Info command was pushed by server ('%s')", m);
+ msg(D_PUSH, "Info command was pushed by peer ('%s')", m);
}
void
@@ -285,6 +340,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
@@ -441,7 +497,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);
@@ -732,6 +787,12 @@
client_max_mtu, o->ce.tun_mtu, o->ce.tun_mtu);
}
}
+
+ if (tls_multi->acc_negotiated_protocols)
+ {
+ push_option_fmt(gc, push_list, M_USAGE, "custom-control %d A:6 %s",
+ tls_multi->acc_max_message_length, tls_multi->acc_negotiated_protocols);
+ }
}
static bool
@@ -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,15 @@
void receive_exit_message(struct context *c);
-void server_pushed_info(const struct buffer *buffer, const int adv);
+/**
+ * This message is called when an INFO or INFO_PRE message is received from
+ * a peer.
+ *
+ * @param c Context of the peer.
+ * @param buffer Buffer containing the INFO/INFO_PRE message.
+ * @param adv number of character to advance past the INFO/INFO_PRE header
+ */
+void peer_pushed_info(struct context *c, const struct buffer *buffer, const int adv);
void receive_cr_response(struct context *c, const struct buffer *buffer);
@@ -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"
@@ -1279,6 +1280,7 @@
free(multi->locked_cn);
free(multi->locked_username);
free(multi->locked_original_username);
+ free(multi->acc_negotiated_protocols);
cert_hash_free(multi->locked_cert_hash_set);
@@ -2098,6 +2100,11 @@
#endif
}
+ if (session->opt->acc_protocols)
+ {
+ buf_printf(&out, "IV_ACC=%d,A:6,%s\n", ACC_MAX_MSG_LEN, session->opt->acc_protocols);
+ }
+
if (session->opt->push_peer_info_detail > 1)
{
struct env_set *es = session->opt->es;
@@ -2398,6 +2405,8 @@
multi->remote_ciphername = string_alloc("none", NULL);
}
+ determine_common_acc_protocols(multi, session);
+
if (username_len < 0 || password_len < 0)
{
msg(D_TLS_ERRORS, "TLS Error: Username (%d) or password (%d) too long", abs(username_len),
@@ -457,6 +457,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
@@ -730,6 +733,12 @@
int dco_peer_id;
dco_context_t *dco;
+
+ /** the protocols that are negotiated with the peer. */
+ char *acc_negotiated_protocols;
+
+ /** the maximum length of an ACC control channel message */
+ int acc_max_message_length;
};
/** gets an item of \c key_state objects in the
@@ -107,10 +107,12 @@
@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_ssl_dependencies.c mock_win32_execve.c \
$(top_srcdir)/src/compat/compat-strlcpy.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 \
@@ -132,6 +134,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 \
@@ -29,6 +29,7 @@
#include "syshead.h"
+#include <stdlib.h>
#include "manage.h"
#ifdef ENABLE_MANAGEMENT
@@ -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;
}
new file mode 100644
@@ -0,0 +1,353 @@
+/*
+ * 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 <sales@openvpn.net>
+ * Copyright (C) 2025 Arne Schwabe <arne@rfc2549.org>
+ *
+ *
+ * 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 "test_common.h"
+
+#include "mock_msg.h"
+#include "multi.h"
+
+char control_channel_message_sent[2048];
+
+/* Mock functions */
+bool
+send_control_channel_string_dowork(struct tls_session *session,
+ const char *str, unsigned int msglevel)
+{
+ assert_int_in_range(strlen(str), 0, 2047);
+
+ strcpy(control_channel_message_sent, str);
+ return true;
+}
+
+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)
+{
+#ifndef ENABLE_MANAGEMENT
+ skip();
+#else
+ /* 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);
+
+ 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, "WARNING: Received malformed custom app control channel message (ACC not enabled or no negotiated protocols): 4143432c 63636b31 2c333834 2c362c46 674d4241 526b4241 41455641 774e616[more...]");
+ assert_string_equal(mock_managment_buf, "");
+
+ c.c2.tls_multi->acc_negotiated_protocols = "no:common:protocol";
+ receive_acc_message(&c, &msg_buf);
+ assert_string_equal(mock_msg_buf, "WARNING: Received malformed custom app control channel message (protocol 'cck1' not in list of negotiated protocols 'no:common:protocol'): 4143432c 63636b31 2c333834 2c362c46 674d4241 526b4241 41455641 774e616[more...]");
+ assert_string_equal(mock_managment_buf, "");
+
+ c.c2.tls_multi->acc_negotiated_protocols = "cck1:flower:power:fortune";
+ 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 "
+ "message (field length 62, payload length 64 mismatch): "
+ "4143432c 666f7274 756e652c 36322c36 2c496e73 67496d31 "
+ "6c496a6f 67496d5[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 message "
+ "(number of encodings must be exactly one (B64=1, ASCII=1)): "
+ "4143432c 666c6f77 65722c34 382c3641 2c535342 68625341 41494574 "
+ "6c636d3[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 message "
+ "(number of encodings must be exactly one (B64=0, ASCII=0)): "
+ "4143432c 666c6f77 65722c34 382c2c53 53426862 53414149 45746c63 "
+ "6d31706[more...]";
+ assert_string_equal(mock_msg_buf, error);
+ assert_string_equal(mock_managment_buf, "");
+
+ x_debug_level = 0;
+ management = NULL;
+ gc_free(&gc);
+#endif
+}
+
+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";
+ 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 multi_instance mi = { 0 };
+ struct context *c = &mi.context;
+ 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";
+ struct tls_multi *multi = c->c2.tls_multi;
+ multi->opt.acc_protocols = c->options.acc_protocols;
+ multi->session[TM_ACTIVE].opt = &multi->opt;
+ multi->opt.es = env_set_create(&c->options.gc);
+
+ determine_common_acc_protocols(multi, &c->c2.tls_multi->session[TM_ACTIVE]);
+
+ assert_int_equal(multi->acc_max_message_length, ACC_MAX_MSG_LEN);
+ assert_string_equal(multi->acc_negotiated_protocols, "power:flower");
+
+ assert_string_equal(env_set_get(multi->opt.es, "acc_protocols"), "acc_protocols=power:flower");
+ assert_string_equal(env_set_get(multi->opt.es, "acc_message_len"), "acc_message_len=1280");
+
+ free(multi->acc_negotiated_protocols);
+ gc_free(&c->options.gc);
+}
+
+void
+test_acc_send_message(void **state)
+{
+ struct multi_instance mi = { 0 };
+ struct context *c = &mi.context;
+ c->options.gc = gc_new();
+
+ ALLOC_OBJ_CLEAR_GC(c->c2.tls_multi, struct tls_multi, &c->options.gc);
+ bool ret;
+ struct tls_multi *multi = c->c2.tls_multi;
+ c->options.acc_protocols = "power:flower:emergency";
+ multi->opt.acc_protocols = c->options.acc_protocols;
+ multi->session[TM_ACTIVE].opt = &multi->opt;
+ multi->opt.es = env_set_create(&c->options.gc);
+
+ c->c2.tls_multi->peer_info = "IV_PROTO=4321\nIV_ACC=1400,A:6,flower:emergency:power";
+ determine_common_acc_protocols(multi, &c->c2.tls_multi->session[TM_ACTIVE]);
+
+ struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
+
+ ret = send_acc_message(c, c->c2.tls_multi, session, "emergency", true,
+ "EMERGENCY BROADCAST MESSAGE - THIS IS NOT A DRILL:", false);
+
+ assert_true(ret);
+ assert_string_equal(control_channel_message_sent,
+ "ACC,emergency,50,AF,EMERGENCY BROADCAST MESSAGE - THIS IS NOT A DRILL:");
+
+ ret = send_acc_message(c, c->c2.tls_multi, session, "emergency", false,
+ "THE GOVERNMENT HAS ISSUED A RAINBOW COLOURED ALERT FOR YOUR AREA.", false);
+ assert_true(ret);
+ assert_string_equal(control_channel_message_sent,
+ "ACC,emergency,65,A,THE GOVERNMENT HAS ISSUED A RAINBOW COLOURED ALERT FOR YOUR AREA.");
+
+
+ /* test a long message (> 1000) with fragment to get the maximum header size */
+ char long_message[1200] = { 0 };
+
+ for (int i = 0; i < 1024; i++)
+ {
+ long_message[i] = (char)(' ' + (i % 95));
+ }
+
+ /* last char of the message is a i and then a \0 */
+ assert_int_equal(long_message[1023], 'i');
+ assert_int_equal(long_message[1024], '\0');
+
+ ret = send_acc_message(c, c->c2.tls_multi, session, "emergency", true,
+ long_message, false);
+
+ assert_true(ret);
+ size_t cc_len = strlen(control_channel_message_sent);
+ assert_int_equal(control_channel_message_sent[cc_len - 1], 'i');
+ const char *expected_start_cc = "ACC,emergency,1024,AF, !\"#$%&'()*+,-./012345";
+ assert_memory_equal(control_channel_message_sent, expected_start_cc, strlen(expected_start_cc));
+
+ free(multi->acc_negotiated_protocols);
+ gc_free(&c->options.gc);
+}
+
+void
+test_list_intersection(void **state)
+{
+ const char *empty = "";
+ const char *protos = "power:flower:emergency";
+
+ struct gc_arena gc = gc_new();
+
+ struct buffer t1 = get_list_intersection(&gc, empty, protos);
+ assert_int_equal(buf_len(&t1), 0);
+
+ struct buffer t2 = get_list_intersection(&gc, protos, empty);
+ assert_int_equal(buf_len(&t2), 0);
+
+ struct buffer t3 = get_list_intersection(&gc, protos, protos);
+ assert_string_equal(buf_cstr(&t3), protos);
+
+ const char *protos2 = "flower:emergency:power:mario";
+ struct buffer t4 = get_list_intersection(&gc, protos2, protos);
+ assert_string_equal(buf_cstr(&t4), "flower:emergency:power");
+
+ const char *protos3 = "super:paper:mario:flower";
+ struct buffer t5 = get_list_intersection(&gc, protos2, protos3);
+ assert_string_equal(buf_cstr(&t5), "flower:mario");
+
+ const char *proto4 = "flower:flower:flower:flower";
+ struct buffer t6 = get_list_intersection(&gc, proto4, "flower");
+ assert_string_equal(buf_cstr(&t6), "flower");
+
+
+ gc_free(&gc);
+}
new file mode 100644
@@ -0,0 +1,35 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#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);
+
+void test_acc_send_message(void **state);
+
+void test_list_intersection(void **state);
+#endif
@@ -43,11 +43,13 @@
#define LargestIntegralType uintmax_t
#endif
/* redefine 2.x API in terms of 1.x API */
-#define CMockaValueData LargestIntegralType
-#define check_expected_uint check_expected
-#define expect_uint_value expect_value
-#define expect_check_data expect_check
-#define cast_ptr_to_cmocka_value(x) (x)
+#define CMockaValueData LargestIntegralType
+#define check_expected_uint check_expected
+#define expect_uint_value expect_value
+#define expect_check_data expect_check
+#define cast_ptr_to_cmocka_value(x) (x)
+#define assert_int_in_range(value, min, max) assert_in_range((value), (min), (max))
+#define assert_uint_in_range(value, min, max) assert_in_range((value), (min), (max))
#endif
/**
@@ -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
@@ -86,6 +88,20 @@
{
return;
}
+void
+reschedule_multi_process(struct context *c)
+{
+}
+
+/* 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 - \
@@ -1111,8 +1127,12 @@
cmocka_unit_test(ssl_test_extract_last_matching_field),
cmocka_unit_test(ssl_test_reject_null_in_cert_subject),
cmocka_unit_test(ssl_test_escape_rfc_2253_chars_in_subject),
- 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),
+ cmocka_unit_test(test_acc_send_message),
+ cmocka_unit_test(test_list_intersection)
};
#if defined(ENABLE_CRYPTO_OPENSSL)