Skip to content
Open
6 changes: 6 additions & 0 deletions src/cli_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ int cliSecureConnection(valkeyContext *c, cliSSLconfig config, const char **err)
*err = "Error while setting cypher suites";
goto error;
}
#endif
#if CLI_TLS_SUPPORTS_GROUPS
if (config.groups && !cliSslCtxSetGroupsList(ssl_ctx, config.groups)) {
*err = "Error while configuring TLS groups";
goto error;
}
#endif
}

Expand Down
21 changes: 21 additions & 0 deletions src/cli_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@
#include "sds.h"
#include <stdint.h>

/* TLS named groups configuration.
* CLI_TLS_SUPPORTS_GROUPS is enabled when OpenSSL supports setting named groups.
* Define TLS_NO_GROUPS at build time to disable. */
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#endif

#if defined(TLS_NO_GROUPS)
#define CLI_TLS_SUPPORTS_GROUPS 0
#elif defined(SSL_CTX_set1_groups_list)
#define CLI_TLS_SUPPORTS_GROUPS 1
#define cliSslCtxSetGroupsList(ctx, list) SSL_CTX_set1_groups_list((ctx), (list))
#elif defined(SSL_CTX_set1_curves_list)
#define CLI_TLS_SUPPORTS_GROUPS 1
#define cliSslCtxSetGroupsList(ctx, list) SSL_CTX_set1_curves_list((ctx), (list))
#else
#define CLI_TLS_SUPPORTS_GROUPS 0
#endif

typedef struct cliSSLconfig {
/* Requested SNI, or NULL */
char *sni;
Expand All @@ -22,6 +41,8 @@ typedef struct cliSSLconfig {
char *ciphers;
/* Preferred ciphersuites list, or NULL (applies only to TLSv1.3) */
char *ciphersuites;
/* Colon-separated named groups for key exchange (--tls-groups), or NULL */
char *groups;
} cliSSLconfig;


Expand Down
1 change: 1 addition & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -3716,6 +3716,7 @@ standardConfig static_configs[] = {
createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, applyTlsCfg),
createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, applyTlsCfg),
createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, applyTlsCfg),
createStringConfig("tls-groups", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.groups, NULL, NULL, applyTlsCfg),

/* Special configs */
createSpecialConfig("dir", NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG | DENY_LOADING_CONFIG, setConfigDirOption, getConfigDirOption, rewriteConfigDirOption, NULL),
Expand Down
1 change: 1 addition & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,7 @@ typedef struct serverTLSContextConfig {
char *protocols;
char *ciphers;
char *ciphersuites;
char *groups; /* TLS key-exchange groups (e.g. X25519MLKEM768/x25519) */
int prefer_server_ciphers;
int session_caching;
int session_cache_size;
Expand Down
35 changes: 35 additions & 0 deletions src/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2)
#endif

/* Support for TLS key-exchange groups (named groups / curves).
*
* CONN_TLS_SUPPORTS_GROUPS is enabled when OpenSSL provides an API to configure
* named groups on an SSL context (SSL_CTX_set1_groups_list or the older
* SSL_CTX_set1_curves_list alias).
*
* Define TLS_NO_GROUPS to compile without group configuration support.
* If neither API is present and TLS_NO_GROUPS is not set, a compile-time
* error is raised so that an operator-requested group list is not silently ignored. */
#if defined(TLS_NO_GROUPS)
#define CONN_TLS_SUPPORTS_GROUPS 0
#elif defined(SSL_CTX_set1_groups_list)
#define CONN_TLS_SUPPORTS_GROUPS 1
#define valkeyTlsCtxSetGroupsList(ctx, list) SSL_CTX_set1_groups_list((ctx), (list))
#elif defined(SSL_CTX_set1_curves_list)
#define CONN_TLS_SUPPORTS_GROUPS 1
#define valkeyTlsCtxSetGroupsList(ctx, list) SSL_CTX_set1_curves_list((ctx), (list))
#else
#error "tls-groups requires SSL_CTX_set1_groups_list or SSL_CTX_set1_curves_list. Build with -DTLS_NO_GROUPS to disable."
#endif

SSL_CTX *valkey_tls_ctx = NULL;
SSL_CTX *valkey_tls_client_ctx = NULL;

Expand Down Expand Up @@ -734,6 +755,20 @@ static SSL_CTX *createSSLContext(serverTLSContextConfig *ctx_config, int protoco
}
#endif

#if CONN_TLS_SUPPORTS_GROUPS
if (ctx_config->groups && !valkeyTlsCtxSetGroupsList(ctx, ctx_config->groups)) {
serverLog(LL_WARNING, "Failed to configure TLS groups: %s", ctx_config->groups);
goto error;
}
#else
if (ctx_config->groups) {
serverLog(LL_WARNING, "tls-groups is configured but this build does not support "
"setting TLS groups; recompile without -DTLS_NO_GROUPS "
"or upgrade OpenSSL");
goto error;
}
#endif

/* Loading the alternate certificate moved the current certificate to its slot,
* and outgoing TLS 1.2 connections pick their client certificate from there.
* Leave it on the lowest slot, which is where it sat before dual certificates. */
Expand Down
10 changes: 10 additions & 0 deletions src/valkey-benchmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,11 @@ int parseOptions(int argc, char **argv) {
if (lastarg) goto invalid;
config.sslconfig.ciphersuites = strdup(argv[++i]);
#endif
#if CLI_TLS_SUPPORTS_GROUPS
} else if (!strcmp(argv[i], "--tls-groups")) {
if (lastarg) goto invalid;
config.sslconfig.groups = strdup(argv[++i]);
#endif
#endif
#ifdef USE_RDMA
} else if (!strcmp(argv[i], "--rdma")) {
Expand Down Expand Up @@ -2076,6 +2081,11 @@ int parseOptions(int argc, char **argv) {
" information about the syntax of this string, and\n"
" specifically for TLSv1.3 ciphersuites.\n"
#endif
#if CLI_TLS_SUPPORTS_GROUPS
" --tls-groups <list> Sets the list of preferred TLS groups\n"
" in order of preference from highest to lowest separated by colon (':').\n"
" See the SSL_CTX_set1_groups_list(3ssl) manpage for syntax.\n"
#endif
#endif
"";

Expand Down
9 changes: 9 additions & 0 deletions src/valkey-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -2852,6 +2852,10 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i], "--tls-ciphersuites") && !lastarg) {
config.sslconfig.ciphersuites = argv[++i];
#endif
#if CLI_TLS_SUPPORTS_GROUPS
} else if (!strcmp(argv[i], "--tls-groups") && !lastarg) {
config.sslconfig.groups = argv[++i];
#endif
#endif
#ifdef USE_RDMA
} else if (!strcmp(argv[i], "--rdma")) {
Expand Down Expand Up @@ -3005,6 +3009,11 @@ static void usage(int err) {
" See the ciphers(1ssl) manpage for more information about the syntax of this string,\n"
" and specifically for TLSv1.3 ciphersuites.\n"
#endif
#if CLI_TLS_SUPPORTS_GROUPS
" --tls-groups <list> Sets the list of preferred TLS groups\n"
" in order of preference from highest to lowest separated by colon (':').\n"
" See the SSL_CTX_set1_groups_list(3ssl) manpage for syntax.\n"
#endif
#endif
"";
const char *rdma_usage =
Expand Down
1 change: 1 addition & 0 deletions tests/unit/introspection.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,7 @@ start_server {tags {"introspection"}} {
tls-protocols
tls-ciphers
tls-ciphersuites
tls-groups
tls-port
}
}
Expand Down
64 changes: 64 additions & 0 deletions tests/unit/tls.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,70 @@ start_server {tags {"tls"}} {
r CONFIG SET tls-ciphers "DEFAULT"
}

proc check_tls_groups_supported {} {
if {[catch {r CONFIG SET tls-groups prime256v1}] &&
[count_log_message 0 "this build does not support"] > 0} {
skip "tls-groups not supported in this build (TLS_NO_GROUPS)"
}
r CONFIG SET tls-groups ""
}

# Helper: run valkey-cli with a specific tls-groups value and return output.
# Uses ECDHE-RSA-AES128-GCM-SHA256 so we can test TLSv1.2 group selection
# independently of the cipher negotiation.
proc connect_with_tls_groups {host port groups} {
set cli_cmd [list $::VALKEY_CLI_BIN \
-h $host -p $port \
{*}[valkeycli_tls_config "./tests"] \
--tls-ciphers ECDHE-RSA-AES128-GCM-SHA256 \
--tls-groups $groups PING]
exec {*}$cli_cmd 2>@1
}

test {TLS: tls-groups rejects invalid group names at CONFIG SET time} {
check_tls_groups_supported

catch {r CONFIG SET tls-groups "invalid-group"} e
assert_match {*Unable to update TLS configuration*} $e

r CONFIG SET tls-groups ""
}

test {TLS: tls-groups with overlapping groups completes handshake} {
check_tls_groups_supported

set ecdhe_ciphers ECDHE-RSA-AES128-GCM-SHA256
r CONFIG SET tls-protocols TLSv1.2
r CONFIG SET tls-ciphers $ecdhe_ciphers
r CONFIG SET tls-groups prime256v1

# Server is restricted to prime256v1; client requests the same
# group, so TLS negotiation must succeed without HRR.
assert_equal {PONG} [connect_with_tls_groups [srv 0 host] [srv 0 port] prime256v1]

r CONFIG SET tls-groups ""
r CONFIG SET tls-protocols ""
r CONFIG SET tls-ciphers DEFAULT
}

test {TLS: tls-groups with no shared group causes handshake failure} {
check_tls_groups_supported

set ecdhe_ciphers ECDHE-RSA-AES128-GCM-SHA256
r CONFIG SET tls-protocols TLSv1.2
r CONFIG SET tls-ciphers $ecdhe_ciphers
r CONFIG SET tls-groups prime256v1

# Server allows only prime256v1; client offers only secp384r1.
# No group is common so the server must reject the handshake.
assert_equal 1 [catch {connect_with_tls_groups [srv 0 host] [srv 0 port] secp384r1} e]
assert_match {*alert handshake failure*} $e

r CONFIG SET tls-groups ""
r CONFIG SET tls-protocols ""
r CONFIG SET tls-ciphers DEFAULT
}

test {TLS: Verify tls-prefer-server-ciphers behaves as expected} {
r CONFIG SET tls-protocols TLSv1.2
r CONFIG SET tls-ciphers "AES128-SHA256:AES256-SHA256"
Expand Down
10 changes: 10 additions & 0 deletions valkey.conf
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,16 @@ tcp-keepalive 300
#
# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256

# Configure TLS named group preferences for key exchange. The value is passed
# directly to OpenSSL, so it accepts the syntax supported by
# SSL_CTX_set1_groups_list(3ssl). Groups within the same security tier are
# separated by ":" and different tiers by "/". Hybrid post-quantum groups
# (e.g. X25519MLKEM768) should be listed before classical fallbacks.
#
# Example enabling PQC hybrid key exchange with classical fallback:
#
# tls-groups X25519MLKEM768/x25519

# When choosing a cipher, use the server's preference instead of the client
# preference. By default, the server follows the client's preference.
#
Expand Down
Loading