This is an automated email from the ASF dual-hosted git repository.

cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 76d5071207e4525b113ddc584e001adb41a2f08e
Author: Jasmine Emanouel <[email protected]>
AuthorDate: Fri May 30 07:25:26 2025 +1000

    Add server_group_list option to sni (#12223)
    
    (cherry picked from commit fd02ff97ecc06df9a48038169f7bc58f5381dfd1)
---
 doc/admin-guide/files/sni.yaml.en.rst       |   5 ++
 include/iocore/net/TLSBasicSupport.h        |   1 +
 include/iocore/net/YamlSNIConfig.h          |   2 +
 src/iocore/net/SNIActionPerformer.cc        |  19 +++++
 src/iocore/net/SNIActionPerformer.h         |  15 ++++
 src/iocore/net/TLSBasicSupport.cc           |   7 ++
 src/iocore/net/YamlSNIConfig.cc             |   8 ++-
 tests/gold_tests/tls/tls_sni_groups.test.py | 104 ++++++++++++++++++++++++++++
 8 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/doc/admin-guide/files/sni.yaml.en.rst 
b/doc/admin-guide/files/sni.yaml.en.rst
index 2640f2f3e8..c83bb20c7a 100644
--- a/doc/admin-guide/files/sni.yaml.en.rst
+++ b/doc/admin-guide/files/sni.yaml.en.rst
@@ -167,6 +167,11 @@ server_TLSv1_3_cipher_suites             Inbound   
Specifies an override to the
                                                    documentation. Note that 
this configures the cipher suite used for inbound TLSv1.3 and
                                                    above connections.
 
+server_group_list                        Inbound   Specifies an override to 
the global :ts:cv:`proxy.config.ssl.server.groups_list`
+                                                   :file:`records.yaml` 
configuration. See the
+                                                   `OpenSSL 
SSL_CTX_set_groups_list 
<https://docs.openssl.org/3.5/man3/SSL_CTX_set1_curves/>`_
+                                                   documentation.
+
 host_sni_policy                          Inbound   One of the values 
:code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.
 
                                                    If not specified, the value 
of :ts:cv:`proxy.config.http.host_sni_policy` is used.
diff --git a/include/iocore/net/TLSBasicSupport.h 
b/include/iocore/net/TLSBasicSupport.h
index 799cda55c0..64ff16ea1a 100644
--- a/include/iocore/net/TLSBasicSupport.h
+++ b/include/iocore/net/TLSBasicSupport.h
@@ -63,6 +63,7 @@ public:
   void set_valid_tls_protocols(unsigned long proto_mask, unsigned long 
max_mask);
   void set_legacy_cipher_suite(std::string const &cipher_suite);
   void set_cipher_suite(std::string const &cipher_suite);
+  bool set_groups_list(std::string const &groups_list);
 
   /**
    * Give the plugin access to the data structure passed in during the 
underlying
diff --git a/include/iocore/net/YamlSNIConfig.h 
b/include/iocore/net/YamlSNIConfig.h
index 3a11dc5a57..c8396a76e7 100644
--- a/include/iocore/net/YamlSNIConfig.h
+++ b/include/iocore/net/YamlSNIConfig.h
@@ -58,6 +58,7 @@ TSDECL(client_key);
 TSDECL(client_sni_policy);
 TSDECL(server_cipher_suite);
 TSDECL(server_TLSv1_3_cipher_suites);
+TSDECL(server_groups_list);
 TSDECL(ip_allow);
 TSDECL(valid_tls_versions_in);
 TSDECL(valid_tls_version_min_in);
@@ -105,6 +106,7 @@ struct YamlSNIConfig {
     std::string         client_sni_policy;
     std::string         server_cipher_suite;
     std::string         server_TLSv1_3_cipher_suites;
+    std::string         server_groups_list;
     std::string         ip_allow;
     bool                protocol_unset = true;
     unsigned long       protocol_mask;
diff --git a/src/iocore/net/SNIActionPerformer.cc 
b/src/iocore/net/SNIActionPerformer.cc
index 3a83a5772a..107a1552b3 100644
--- a/src/iocore/net/SNIActionPerformer.cc
+++ b/src/iocore/net/SNIActionPerformer.cc
@@ -493,3 +493,22 @@ ServerTLSv1_3CipherSuites::SNIAction(SSL &ssl, const 
Context & /* ctx ATS_UNUSED
   tbs->set_cipher_suite(server_TLSV1_3_cipher_suites);
   return SSL_TLSEXT_ERR_OK;
 }
+
+int
+ServerGroupsList::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) 
const
+{
+  if (server_groups_list.empty()) {
+    return SSL_TLSEXT_ERR_OK;
+  }
+  auto tbs = TLSBasicSupport::getInstance(&ssl);
+  if (tbs == nullptr) {
+    return SSL_TLSEXT_ERR_OK;
+  }
+  Dbg(dbg_ctl_ssl_sni, "Setting groups list from server_groups_list to %s", 
server_groups_list.c_str());
+
+  if (!tbs->set_groups_list(server_groups_list)) {
+    Error("Invalid server_groups_list: %s", server_groups_list.c_str());
+    return SSL_TLSEXT_ERR_ALERT_WARNING;
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
diff --git a/src/iocore/net/SNIActionPerformer.h 
b/src/iocore/net/SNIActionPerformer.h
index f2e8b60e26..c173caacaa 100644
--- a/src/iocore/net/SNIActionPerformer.h
+++ b/src/iocore/net/SNIActionPerformer.h
@@ -342,3 +342,18 @@ public:
 private:
   std::string const server_TLSV1_3_cipher_suites{};
 };
+
+/**
+   Override proxy.config.ssl.server.groups_list by server_groups_list in 
sni.yaml
+ */
+class ServerGroupsList : public ActionItem
+{
+public:
+  ServerGroupsList(std::string const &p) : server_groups_list(p) {}
+  ~ServerGroupsList() override {}
+
+  int SNIAction(SSL &ssl, const Context &ctx) const override;
+
+private:
+  std::string const server_groups_list{};
+};
diff --git a/src/iocore/net/TLSBasicSupport.cc 
b/src/iocore/net/TLSBasicSupport.cc
index 0c3a5de50d..dc3a053657 100644
--- a/src/iocore/net/TLSBasicSupport.cc
+++ b/src/iocore/net/TLSBasicSupport.cc
@@ -188,6 +188,13 @@ TLSBasicSupport::set_cipher_suite([[maybe_unused]] 
std::string const &cipher_sui
 #endif
 }
 
+bool
+TLSBasicSupport::set_groups_list(std::string const &groups_list)
+{
+  auto ssl = this->_get_ssl_object();
+  return SSL_set1_groups_list(ssl, groups_list.c_str());
+}
+
 int
 TLSBasicSupport::verify_certificate(X509_STORE_CTX *ctx)
 {
diff --git a/src/iocore/net/YamlSNIConfig.cc b/src/iocore/net/YamlSNIConfig.cc
index c5dfd70c7d..bbc0eb4ace 100644
--- a/src/iocore/net/YamlSNIConfig.cc
+++ b/src/iocore/net/YamlSNIConfig.cc
@@ -161,6 +161,9 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t 
&actions)
   if (!server_TLSv1_3_cipher_suites.empty()) {
     
actions.push_back(std::make_unique<ServerTLSv1_3CipherSuites>(server_TLSv1_3_cipher_suites));
   }
+  if (!server_groups_list.empty()) {
+    actions.push_back(std::make_unique<ServerGroupsList>(server_groups_list));
+  }
   if (http2_buffer_water_mark.has_value()) {
     
actions.push_back(std::make_unique<HTTP2BufferWaterMark>(http2_buffer_water_mark.value()));
   }
@@ -226,6 +229,7 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
 #if TS_USE_TLS_SET_CIPHERSUITES
                                                TS_server_TLSv1_3_cipher_suites,
 #endif
+                                               TS_server_groups_list,
                                                TS_http2,
                                                TS_http2_buffer_water_mark,
                                                TS_http2_initial_window_size_in,
@@ -458,7 +462,9 @@ template <> struct convert<YamlSNIConfig::Item> {
     if (node[TS_server_TLSv1_3_cipher_suites]) {
       item.server_TLSv1_3_cipher_suites = 
node[TS_server_TLSv1_3_cipher_suites].as<std::string>();
     }
-
+    if (node[TS_server_groups_list]) {
+      item.server_groups_list = node[TS_server_groups_list].as<std::string>();
+    }
     if (node[TS_ip_allow]) {
       item.ip_allow = node[TS_ip_allow].as<std::string>();
     }
diff --git a/tests/gold_tests/tls/tls_sni_groups.test.py 
b/tests/gold_tests/tls/tls_sni_groups.test.py
new file mode 100644
index 0000000000..870f07de0e
--- /dev/null
+++ b/tests/gold_tests/tls/tls_sni_groups.test.py
@@ -0,0 +1,104 @@
+'''
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+Test.Summary = '''
+Test SNI configuration server_groups_list
+'''
+# The groups function was added in OpenSSL 1.1.1
+Test.SkipUnless(Condition.HasOpenSSLVersion("1.1.1"))
+
+# Define default ATS
+ts = Test.MakeATSProcess("ts", enable_tls=True)
+server = Test.MakeOriginServer("server", ssl=True)
+
+request_header = {"headers": "GET / HTTP/1.1\r\n\r\n", "timestamp": 
"1469733493.993", "body": ""}
+response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", 
"timestamp": "1469733493.993", "body": "foo ok"}
+server.addResponse("sessionlog.json", request_header, response_header)
+
+# add ssl materials like key, certificates for the server
+ts.addSSLfile("ssl/server.pem")
+ts.addSSLfile("ssl/server.key")
+
+# Need no remap rules.  Everything should be processed by sni
+
+# Make sure the TS server certs are different from the origin certs
+ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem 
ssl_key_name=server.key')
+
+ts.Disk.records_config.update(
+    {
+        'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+        'proxy.config.ssl.server.private_key.path': 
'{0}'.format(ts.Variables.SSLDir),
+        'proxy.config.ssl.client.CA.cert.path': 
'{0}'.format(ts.Variables.SSLDir),
+        'proxy.config.diags.debug.enabled': 1,
+        'proxy.config.diags.debug.tags': 'ssl_sni',
+    })
+
+ts.Disk.sni_yaml.AddLines(
+    [
+        'sni:',
+        '- fqdn: aaa.com',
+        '  server_groups_list: X25519MLKEM768',
+        '  valid_tls_versions_in: [ TLSv1_3 ]',
+        '  server_TLSv1_3_cipher_suites: TLS_AES_256_GCM_SHA384',
+        '- fqdn: bbb.com',
+        '  server_groups_list: x25519',
+        '  valid_tls_versions_in: [ TLSv1_2 ]',
+        '  server_cipher_suite: ECDHE-RSA-AES256-GCM-SHA384',
+        '- fqdn: ccc.com',
+        '  server_groups_list: ABC123',
+        '  valid_tls_versions_in: [ TLSv1_2 ]',
+        '  server_cipher_suite: ECDHE-RSA-AES256-GCM-SHA384',
+    ])
+
+tr = Test.AddTestRun("Test 0: x25519")
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(Test.Processes.ts)
+tr.MakeCurlCommand(
+    "-v --ciphers ECDHE-RSA-AES256-GCM-SHA384 --resolve 
'bbb.com:{0}:127.0.0.1' -k  https://bbb.com:{0}".format(
+        ts.Variables.ssl_port))
+tr.ReturnCode = 0
+tr.StillRunningAfter = ts
+ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+    "Setting groups list from server_groups_list to x25519", "Should log 
setting the server groups")
+tr.Processes.Default.Streams.all = Testers.IncludesExpression(
+    f"SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 / x25519", 
"Curl should log using x25519 in the SSL connection")
+
+tr = Test.AddTestRun("Test 1: fail")
+tr.MakeCurlCommand(
+    "-v --ciphers ECDHE-RSA-AES256-GCM-SHA384 --resolve 
'ccc.com:{0}:127.0.0.1' -k  https://ccc.com:{0}".format(
+        ts.Variables.ssl_port))
+# The error code is 35, which indicates there was a ssl connection error
+tr.ReturnCode = 35
+tr.StillRunningAfter = ts
+tr.StillRunningAfter = server
+ts.Disk.diags_log.Content = Testers.ContainsExpression(
+    "ERROR: Invalid server_groups_list: ABC123", "Curl attempt should have 
failed")
+
+# Hybrid ECDH PQ key exchange TLS groups were added in OpenSSL 3.5
+if Condition.HasOpenSSLVersion("3.5.0"):
+    tr = Test.AddTestRun("Test 2: X25519MLKEM768")
+    tr.MakeCurlCommand(
+        "-v --tls13-ciphers TLS_AES_256_GCM_SHA384 --resolve 
'aaa.com:{0}:127.0.0.1' -k  https://aaa.com:{0}".format(
+            ts.Variables.ssl_port))
+    tr.ReturnCode = 0
+    tr.StillRunningAfter = ts
+    ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+        "Setting groups list from server_groups_list to X25519MLKEM768", 
"Should log setting the server groups")
+    tr.Processes.Default.Streams.all = Testers.IncludesExpression(
+        f"SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / 
X25519MLKEM768",
+        f"Curl should log using X25519MLKEM768 in the SSL connection")

Reply via email to