diff mbox series

channel: add tls_group option to restrict TLS key exchange groups

Message ID 20260831154235.278721-1-ayoub.zaki@embetrix.com
State New
Headers show
Series channel: add tls_group option to restrict TLS key exchange groups | expand

Commit Message

Ayoub Zaki Aug. 31, 2026, 3:42 p.m. UTC
Add "tls_group" option mapped to curl CURLOPT_SSL_EC_CURVES taking
a colon separated list example tls_group = "X25519MLKEM768:secp256r1".
The handshake will fail if the server supports none of the listed groups.
Needs at least libcurl 7.73.0 version

Signed-off-by: Ayoub Zaki <ayoub.zaki@embetrix.com>
---
 corelib/channel_curl.c   | 24 ++++++++++++++++++++++++
 corelib/server_utils.c   |  3 +++
 doc/source/suricatta.rst |  3 +++
 include/channel_curl.h   |  1 +
 suricatta/server_lua.c   |  3 +++
 suricatta/suricatta.lua  |  1 +
 6 files changed, 35 insertions(+)
diff mbox series

Patch

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index bae44d85..fb6c16ad 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -785,6 +785,30 @@  channel_op_res_t channel_set_options(channel_t *this, channel_data_t *channel_da
 		}
 	}
 
+	/*
+	 * Check if the TLS key exchange groups are specified, the
+	 * server is required to support one of them. Setting this fails the
+	 * handshake instead of falling back to another group, so it can be
+	 * used to enforce a group
+	 */
+	if (channel_data->tls_group) {
+#if LIBCURL_VERSION_NUM >= 0x074900 /* 7.73.0 */
+		if (curl_easy_setopt(channel_curl->handle,
+				      CURLOPT_SSL_EC_CURVES,
+				      channel_data->tls_group) != CURLE_OK) {
+			ERROR("tls_group set to %s, but not supported by the TLS backend",
+			      channel_data->tls_group);
+			result = CHANNEL_EINIT;
+			goto cleanup;
+		}
+#else
+		ERROR("tls_group set to %s but libcurl %s is too old, minimum 7.73.0 versio required",
+		      channel_data->tls_group, LIBCURL_VERSION);
+		result = CHANNEL_EINIT;
+		goto cleanup;
+#endif
+	}
+
 	if (channel_data->auth_token != NULL) {
 		if (((channel_curl->header = curl_slist_append(
 				channel_curl->header, channel_data->auth_token)) == NULL)) {
diff --git a/corelib/server_utils.c b/corelib/server_utils.c
index 13923d50..dbf934ba 100644
--- a/corelib/server_utils.c
+++ b/corelib/server_utils.c
@@ -50,6 +50,9 @@  int channel_settings(void *elem, void *data)
 	GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "ciphers", tmp);
 	if (strlen(tmp))
 		SETSTRING(chan->ciphers, tmp);
+	GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "tls_group", tmp);
+	if (strlen(tmp))
+		SETSTRING(chan->tls_group, tmp);
 	GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "sslcert", tmp);
 	if (strlen(tmp))
 		SETSTRING(chan->sslcert, tmp);
diff --git a/doc/source/suricatta.rst b/doc/source/suricatta.rst
index 29edf28a..b4800e6d 100644
--- a/doc/source/suricatta.rst
+++ b/doc/source/suricatta.rst
@@ -478,6 +478,9 @@  The available configuration options for the ``gservice`` section are:
 +----------------------------+---------+-------------------------------------------------------------+
 | ``ciphers``                | string  | Allowed ciphers suite list in CURL format.                  |
 +----------------------------+---------+-------------------------------------------------------------+
+| ``tls_group``              | string  | Allowed TLS key exchange groups, colon separated. The       |
+|                            |         | handshake fails if the server supports none of them.        |
++----------------------------+---------+-------------------------------------------------------------+
 | ``proxy``                  | string  | HTTP proxy address to reach the server.                     |
 +----------------------------+---------+-------------------------------------------------------------+
 | ``interface``              | string  | Network interface or IP address to bind communication to.   |
diff --git a/include/channel_curl.h b/include/channel_curl.h
index f4855ce2..0f652ee2 100644
--- a/include/channel_curl.h
+++ b/include/channel_curl.h
@@ -59,6 +59,7 @@  typedef struct {
 	char *sslkeypassword;
 	char *sslcert;
 	char *ciphers;
+	char *tls_group;	/* TLS key exchange groups */
 	char *proxy;
 	char *info;
 	char *auth_token;
diff --git a/suricatta/server_lua.c b/suricatta/server_lua.c
index 0bb18f6c..9a6f2fe4 100644
--- a/suricatta/server_lua.c
+++ b/suricatta/server_lua.c
@@ -524,6 +524,7 @@  static void channel_push_options(lua_State *L, channel_data_t *channel_data)
 	push_to_table(L, "sslkeypassword",     channel_data->sslkeypassword);
 	push_to_table(L, "sslcert",            channel_data->sslcert);
 	push_to_table(L, "ciphers",            channel_data->ciphers);
+	push_to_table(L, "tls_group",          channel_data->tls_group);
 	if (channel_data->proxy && channel_data->proxy == USE_PROXY_ENV) {
 		push_to_table(L, "proxy",      "");
 	} else {
@@ -571,6 +572,7 @@  static void channel_set_options(lua_State *L, channel_data_t *channel_data)
 	get_from_table(L, "sslkeypassword",     channel_data->sslkeypassword, COPY_DEST);
 	get_from_table(L, "sslcert",            channel_data->sslcert, COPY_DEST);
 	get_from_table(L, "ciphers",            channel_data->ciphers, COPY_DEST);
+	get_from_table(L, "tls_group",          channel_data->tls_group, COPY_DEST);
 	get_from_table(L, "info",               channel_data->info, COPY_DEST);
 	get_from_table(L, "auth_token",         channel_data->auth_token, COPY_DEST);
 	get_from_table(L, "content_type",       channel_data->content_type, COPY_DEST);
@@ -627,6 +629,7 @@  static void channel_free_options(channel_data_t *channel_data)
 	free(channel_data->sslkeypassword);
 	free(channel_data->sslcert);
 	free(channel_data->ciphers);
+	free(channel_data->tls_group);
 	if (channel_data->proxy && channel_data->proxy != USE_PROXY_ENV) {
 		free(channel_data->proxy);
 	}
diff --git a/suricatta/suricatta.lua b/suricatta/suricatta.lua
index 01020759..e503f12b 100644
--- a/suricatta/suricatta.lua
+++ b/suricatta/suricatta.lua
@@ -235,6 +235,7 @@  suricatta.channel = {
     --- @field sslkey              string | nil   `CURLOPT_SSLKEY` - private key file for TLS and SSL client cert
     --- @field sslcert             string | nil   `CURLOPT_SSLCERT` - SSL client certificate
     --- @field ciphers             string | nil   `CURLOPT_SSL_CIPHER_LIST` - ciphers to use for TLS
+    --- @field tls_group           string | nil   `CURLOPT_SSL_EC_CURVES` - TLS key exchange groups to require
     --- @field proxy               string | nil   `CURLOPT_PROXY` - proxy to use
     --- @field info                string | nil   `swupdate_request`'s info field as in `include/network_ipc.h`
     --- @field auth_token          string | nil   String appended to Header