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

remm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new f487a90199 Add certificate selection code specific to TLS 1.3
f487a90199 is described below

commit f487a90199a023ae51605c845dd14025a7514729
Author: remm <r...@apache.org>
AuthorDate: Tue Sep 9 15:53:31 2025 +0200

    Add certificate selection code specific to TLS 1.3
    
    The signature algorithms preferences sent in the client hello should be
    enough to select the appropriate certificate (groups can be used as
    well, but it seems redundant and more complex).
    BZ69800: Add groups configuration on SSLHostConfig and pass that to JSSE
    (unsure about the actual usefulness though, I don't see any reason to
    disable a group at this point).
    Remove the OpenSSL specific code extracted from mod_ssl since it is
    supposed to be doing the same thing.
    No backport plans for now (the OpenSSL specific technique is probably
    safer for a while). Will add some client hello tests.
---
 .../apache/tomcat/util/net/AbstractEndpoint.java   | 69 +++++++++++-----------
 java/org/apache/tomcat/util/net/SSLHostConfig.java | 38 ++++++++++++
 .../tomcat/util/net/SSLHostConfigCertificate.java  |  8 ++-
 .../apache/tomcat/util/net/SecureNioChannel.java   | 15 ++++-
 .../util/net/openssl/ciphers/Authentication.java   |  1 +
 webapps/docs/changelog.xml                         | 15 +++--
 webapps/docs/config/http.xml                       |  9 +++
 7 files changed, 112 insertions(+), 43 deletions(-)

diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java 
b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
index a063fdb1ab..23e109d293 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -55,8 +55,9 @@ import org.apache.tomcat.util.collections.SynchronizedStack;
 import org.apache.tomcat.util.modeler.Registry;
 import org.apache.tomcat.util.net.Acceptor.AcceptorState;
 import org.apache.tomcat.util.net.SSLHostConfigCertificate.StoreType;
-import org.apache.tomcat.util.net.openssl.OpenSSLStatus;
 import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
+import org.apache.tomcat.util.net.openssl.ciphers.Group;
+import org.apache.tomcat.util.net.openssl.ciphers.SignatureAlgorithm;
 import org.apache.tomcat.util.res.StringManager;
 import org.apache.tomcat.util.threads.LimitLatch;
 import org.apache.tomcat.util.threads.ResizableExecutor;
@@ -397,24 +398,6 @@ public abstract class AbstractEndpoint<S, U> {
      */
     protected void createSSLContext(SSLHostConfig sslHostConfig) throws 
IllegalArgumentException {
 
-        boolean useHybridSslContext = false;
-        if (sslHostConfig.getProtocols().contains(Constants.SSL_PROTO_TLSv1_3) 
&& OpenSSLStatus.isAvailable()) {
-            // If TLS 1.3 is enabled, check if a hybrid scheme using a single 
SSL context
-            // should be attempted
-            boolean nonMldsaFound = false;
-            boolean mldsaFound = false;
-            for (SSLHostConfigCertificate certificate : 
sslHostConfig.getCertificates(true)) {
-                if 
(certificate.getType().equals(SSLHostConfigCertificate.Type.MLDSA)) {
-                    mldsaFound = true;
-                } else {
-                    nonMldsaFound = true;
-                }
-            }
-            if (mldsaFound && nonMldsaFound) {
-                useHybridSslContext = true;
-            }
-        }
-
         boolean firstCertificate = true;
         for (SSLHostConfigCertificate certificate : 
sslHostConfig.getCertificates(true)) {
             SSLUtil sslUtil = sslImplementation.getSSLUtil(certificate);
@@ -442,18 +425,6 @@ public abstract class AbstractEndpoint<S, U> {
                 certificate.setSslContextGenerated(sslContext);
             }
 
-            // If using a hybrid scheme, add any MLDSA certificates to all 
other SSL contexts
-            if (useHybridSslContext && 
!certificate.getType().equals(SSLHostConfigCertificate.Type.MLDSA)) {
-                for (SSLHostConfigCertificate certificateToAdd : 
sslHostConfig.getCertificates(true)) {
-                    // Add additional certificate to all non MLDSA contexts
-                    if 
(certificateToAdd.getType().equals(SSLHostConfigCertificate.Type.MLDSA)) {
-                        if (!sslUtil.addSecondCertificate(sslContext, 
certificateToAdd)) {
-                            throw new 
IllegalArgumentException(sm.getString("endpoint.errorCreatingSSLContext"));
-                        }
-                    }
-                }
-            }
-
             logCertificate(certificate);
         }
 
@@ -534,10 +505,12 @@ public abstract class AbstractEndpoint<S, U> {
     }
 
     protected SSLEngine createSSLEngine(String sniHostName, List<Cipher> 
clientRequestedCiphers,
-            List<String> clientRequestedApplicationProtocols) {
+            List<String> clientRequestedApplicationProtocols, List<String> 
clientRequestedProtocols,
+            List<Group> clientSupportedGroups, List<SignatureAlgorithm> 
clientSignatureAlgorithms) {
         SSLHostConfig sslHostConfig = getSSLHostConfig(sniHostName);
 
-        SSLHostConfigCertificate certificate = 
selectCertificate(sslHostConfig, clientRequestedCiphers);
+        SSLHostConfigCertificate certificate = 
selectCertificate(sslHostConfig, clientRequestedCiphers,
+                clientRequestedProtocols, clientSignatureAlgorithms);
 
         SSLContext sslContext = certificate.getSslContext();
         if (sslContext == null) {
@@ -563,6 +536,21 @@ public abstract class AbstractEndpoint<S, U> {
                 sslParameters.setApplicationProtocols(commonProtocolsArray);
             }
         }
+        // Merge server groups with the client groups
+        List<String> supportedGroups = new ArrayList<>();
+        LinkedHashSet<Group> serverSupportedGroups = 
sslHostConfig.getGroupList();
+        if (serverSupportedGroups != null) {
+            for (Group group : clientSupportedGroups) {
+                if (serverSupportedGroups.contains(group)) {
+                    supportedGroups.add(group.toString());
+                }
+            }
+        } else {
+            for (Group group : clientSupportedGroups) {
+                supportedGroups.add(group.toString());
+            }
+        }
+        sslParameters.setNamedGroups(supportedGroups.toArray(new String[0]));
         switch (sslHostConfig.getCertificateVerification()) {
             case NONE:
                 sslParameters.setNeedClientAuth(false);
@@ -583,13 +571,26 @@ public abstract class AbstractEndpoint<S, U> {
     }
 
 
-    private SSLHostConfigCertificate selectCertificate(SSLHostConfig 
sslHostConfig, List<Cipher> clientCiphers) {
+    private SSLHostConfigCertificate selectCertificate(SSLHostConfig 
sslHostConfig, List<Cipher> clientCiphers,
+            List<String> clientRequestedProtocols, List<SignatureAlgorithm> 
clientSignatureAlgorithms) {
 
         Set<SSLHostConfigCertificate> certificates = 
sslHostConfig.getCertificates(true);
         if (certificates.size() == 1) {
             return certificates.iterator().next();
         }
 
+        // Use signature algorithm for cipher matching with TLS 1.3
+        if ((clientRequestedProtocols.contains(Constants.SSL_PROTO_TLSv1_3)) &&
+                
sslHostConfig.getProtocols().contains(Constants.SSL_PROTO_TLSv1_3)) {
+            for (SignatureAlgorithm signatureAlgorithm : 
clientSignatureAlgorithms) {
+                for (SSLHostConfigCertificate certificate : certificates) {
+                    if 
(certificate.getType().isCompatibleWith(signatureAlgorithm)) {
+                        return certificate;
+                    }
+                }
+            }
+        }
+
         LinkedHashSet<Cipher> serverCiphers = sslHostConfig.getCipherList();
 
         List<Cipher> candidateCiphers = new ArrayList<>();
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java 
b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index 00b7d4f2fa..b6d643aecc 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -39,6 +39,7 @@ import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.openssl.OpenSSLConf;
 import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
+import org.apache.tomcat.util.net.openssl.ciphers.Group;
 import 
org.apache.tomcat.util.net.openssl.ciphers.OpenSSLCipherConfigurationParser;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -110,6 +111,8 @@ public class SSLHostConfig implements Serializable {
     // Values <0 mean use the implementation default
     private int sessionCacheSize = -1;
     private int sessionTimeout = 86400;
+    private String groups = null;
+    private LinkedHashSet<Group> groupList = null;
     // JSSE
     private String keyManagerAlgorithm = 
KeyManagerFactory.getDefaultAlgorithm();
     private boolean revocationEnabled = false;
@@ -524,8 +527,43 @@ public class SSLHostConfig implements Serializable {
     }
 
 
+    /**
+     * @return the configured named groups
+     */
+    public String getGroups() {
+        return groups;
+    }
+
+
+    /**
+     * Set the enabled named groups.
+     * @param groupsString the case sensitive comma separated list of groups
+     */
+    public void setGroups(String groupsString) {
+        if (groupsString != null) {
+            LinkedHashSet<Group> groupList = new LinkedHashSet<>();
+            String[] groupNames = groupsString.split(",");
+            for (String groupName : groupNames) {
+                Group group = Group.valueOf(groupName.trim());
+                groupList.add(group);
+            }
+            this.groups = groupsString;
+            this.groupList = groupList;
+        }
+    }
+
+
+    /**
+     * @return the groupList
+     */
+    public LinkedHashSet<Group> getGroupList() {
+        return this.groupList;
+    }
+
+
     // ---------------------------------- JSSE specific configuration 
properties
 
+
     public void setKeyManagerAlgorithm(String keyManagerAlgorithm) {
         setProperty("keyManagerAlgorithm", Type.JSSE);
         this.keyManagerAlgorithm = keyManagerAlgorithm;
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java 
b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
index 77bbd3e615..fd44b5419d 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
@@ -31,6 +31,7 @@ import javax.net.ssl.X509KeyManager;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.openssl.ciphers.Authentication;
+import org.apache.tomcat.util.net.openssl.ciphers.SignatureAlgorithm;
 import org.apache.tomcat.util.res.StringManager;
 
 public class SSLHostConfigCertificate implements Serializable {
@@ -319,7 +320,7 @@ public class SSLHostConfigCertificate implements 
Serializable {
         RSA(Authentication.RSA),
         DSA(Authentication.DSS),
         EC(Authentication.ECDH, Authentication.ECDSA),
-        MLDSA;
+        MLDSA(Authentication.MLDSA);
 
         private final Set<Authentication> compatibleAuthentications;
 
@@ -333,6 +334,11 @@ public class SSLHostConfigCertificate implements 
Serializable {
         public boolean isCompatibleWith(Authentication au) {
             return compatibleAuthentications.contains(au);
         }
+
+        public boolean isCompatibleWith(SignatureAlgorithm al) {
+            return al.toString().toUpperCase().startsWith(toString());
+        }
+
     }
 
     enum StoreType {
diff --git a/java/org/apache/tomcat/util/net/SecureNioChannel.java 
b/java/org/apache/tomcat/util/net/SecureNioChannel.java
index 489a7909c1..cf20903047 100644
--- a/java/org/apache/tomcat/util/net/SecureNioChannel.java
+++ b/java/org/apache/tomcat/util/net/SecureNioChannel.java
@@ -41,6 +41,8 @@ import org.apache.tomcat.util.buf.ByteBufferUtils;
 import org.apache.tomcat.util.net.NioEndpoint.NioSocketWrapper;
 import org.apache.tomcat.util.net.TLSClientHelloExtractor.ExtractorResult;
 import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
+import org.apache.tomcat.util.net.openssl.ciphers.Group;
+import org.apache.tomcat.util.net.openssl.ciphers.SignatureAlgorithm;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -272,6 +274,8 @@ public class SecureNioChannel extends NioChannel {
         String hostName = null;
         List<Cipher> clientRequestedCiphers = null;
         List<String> clientRequestedApplicationProtocols = null;
+        List<Group> clientSupportedGroups = null;
+        List<SignatureAlgorithm> clientSignatureAlgorithms = null;
         switch (extractor.getResult()) {
             case COMPLETE:
                 hostName = extractor.getSNIValue();
@@ -279,6 +283,8 @@ public class SecureNioChannel extends NioChannel {
                 //$FALL-THROUGH$ to set the client requested ciphers
             case NOT_PRESENT:
                 clientRequestedCiphers = extractor.getClientRequestedCiphers();
+                clientSupportedGroups = extractor.getClientSupportedGroups();
+                clientSignatureAlgorithms = 
extractor.getClientSignatureAlgorithms();
                 break;
             case NEED_READ:
                 return SelectionKey.OP_READ;
@@ -302,7 +308,8 @@ public class SecureNioChannel extends NioChannel {
             log.trace(sm.getString("channel.nio.ssl.sniHostName", sc, 
hostName));
         }
 
-        createSSLEngine(hostName, clientRequestedCiphers, 
clientRequestedApplicationProtocols);
+        createSSLEngine(hostName, clientRequestedCiphers, 
clientRequestedApplicationProtocols,
+                extractor.getClientRequestedProtocols(), 
clientSupportedGroups, clientSignatureAlgorithms);
 
         // Populate additional TLS attributes obtained from the handshake that
         // aren't available from the session
@@ -922,8 +929,10 @@ public class SecureNioChannel extends NioChannel {
     }
 
     protected void createSSLEngine(String hostName, List<Cipher> 
clientRequestedCiphers,
-            List<String> clientRequestedApplicationProtocols) {
-        sslEngine = endpoint.createSSLEngine(hostName, clientRequestedCiphers, 
clientRequestedApplicationProtocols);
+            List<String> clientRequestedApplicationProtocols, List<String> 
clientRequestedProtocols,
+            List<Group> clientSupportedGroups, List<SignatureAlgorithm> 
clientSignatureAlgorithms) {
+        sslEngine = endpoint.createSSLEngine(hostName, clientRequestedCiphers, 
clientRequestedApplicationProtocols,
+                clientRequestedProtocols, clientSupportedGroups, 
clientSignatureAlgorithms);
     }
 
 
diff --git 
a/java/org/apache/tomcat/util/net/openssl/ciphers/Authentication.java 
b/java/org/apache/tomcat/util/net/openssl/ciphers/Authentication.java
index ec192db6fb..052808a36b 100644
--- a/java/org/apache/tomcat/util/net/openssl/ciphers/Authentication.java
+++ b/java/org/apache/tomcat/util/net/openssl/ciphers/Authentication.java
@@ -29,5 +29,6 @@ public enum Authentication {
     GOST01 /* GOST R 34.10-2001 */,
     FZA /* Fortezza */,
     SRP /* Secure Remote Password */,
+    MLDSA /* ML-DSA */,
     ANY /* TLS 1.3 */
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 418bd453ef..9b67ad3421 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -195,13 +195,18 @@
       <update>
         Remove NIO2 connector. (remm)
       </update>
-      <!-- Entries for backport and removal before 12.0.0-M1 below this line 
-->
       <update>
-        Add hybrid PQC support to OpenSSL, based on code from
-        <code>mod_ssl</code>. Using this OpenSSL specific code path,
-        additional PQC certificates defined with type <code>MLDSA</code> are
-        added to contexts which use classic certificates. (jfclere/remm)
+        Add specific certificate selection code for TLS 1.3 supporting post
+        quantum cryptography. Certificates defined with type
+        <code>MLDSA</code> will be selected depending on the TLS client hello.
+        (remm)
+      </update>
+      <update>
+        Add <code>groups</code> attribute on <code>SSLHostConfig</code>
+        allowing to restrict which groups can be enabled on the SSL engine.
+        (remm)
       </update>
+      <!-- Entries for backport and removal before 12.0.0-M1 below this line 
-->
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 262d3635dd..bad983fe07 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1261,6 +1261,15 @@
       not the full chain.</p>
     </attribute>
 
+    <attribute name="groups" required="false">
+      <p>JSSE only.</p>
+      <p>Allows only allowing certain named groups. The value should be a case
+      sensitive comma separated list of the names of the groups.</p>
+      <p>. If not specified, the default named groups of the provider will be
+      used, and any named groups specified by the client will be passed to it.
+      </p>
+    </attribute>
+
     <attribute name="honorCipherOrder" required="false">
       <p>Set to <code>true</code> to enforce the server's cipher order
       (from the <code>ciphers</code> setting) instead of allowing


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to